001 // Copyright (C) 2002 IAIK 002 // https://jce.iaik.tugraz.at 003 // 004 // Copyright (C) 2003 - 2025 Stiftung Secure Information and 005 // Communication Technologies SIC 006 // https://sic.tech 007 // 008 // All rights reserved. 009 // 010 // Redistribution and use in source and binary forms, with or without 011 // modification, are permitted provided that the following conditions 012 // are met: 013 // 1. Redistributions of source code must retain the above copyright 014 // notice, this list of conditions and the following disclaimer. 015 // 2. Redistributions in binary form must reproduce the above copyright 016 // notice, this list of conditions and the following disclaimer in the 017 // documentation and/or other materials provided with the distribution. 018 // 019 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 020 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 021 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 022 // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 023 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 024 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 025 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 026 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 027 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 028 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 029 // SUCH DAMAGE. 030 031 // Copyright (C) 2002 IAIK 032 // https://sic.tech/ 033 // 034 // Copyright (C) 2003 - 2025 Stiftung Secure Information and 035 // Communication Technologies SIC 036 // https://sic.tech/ 037 // 038 // All rights reserved. 039 // 040 // This source is provided for inspection purposes and recompilation only, 041 // unless specified differently in a contract with IAIK. This source has to 042 // be kept in strict confidence and must not be disclosed to any third party 043 // under any circumstances. Redistribution in source and binary forms, with 044 // or without modification, are <not> permitted in any case! 045 // 046 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 047 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 048 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 049 // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 050 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 051 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 052 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 053 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 054 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 055 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 056 // SUCH DAMAGE. 057 // 058 // $Header: /IAIK-CMS/current/src/demo/cms/ecc/ECDHAuthenticatedDataDemo.java 5 12.02.25 17:58 Dbratko $ 059 // $Revision: 5 $ 060 // 061 062 063 package demo.cms.ecc; 064 065 import iaik.asn1.ObjectID; 066 import iaik.asn1.structures.AlgorithmID; 067 import iaik.asn1.structures.Attribute; 068 import iaik.cms.AuthenticatedData; 069 import iaik.cms.AuthenticatedDataOutputStream; 070 import iaik.cms.AuthenticatedDataStream; 071 import iaik.cms.CMSException; 072 import iaik.cms.ContentInfo; 073 import iaik.cms.ContentInfoOutputStream; 074 import iaik.cms.ContentInfoStream; 075 import iaik.cms.IssuerAndSerialNumber; 076 import iaik.cms.KeyIdentifier; 077 import iaik.cms.OriginatorInfo; 078 import iaik.cms.RecipientInfo; 079 import iaik.cms.RecipientKeyIdentifier; 080 import iaik.cms.attributes.CMSContentType; 081 import iaik.utils.Util; 082 import iaik.x509.X509Certificate; 083 084 import java.io.ByteArrayInputStream; 085 import java.io.ByteArrayOutputStream; 086 import java.io.IOException; 087 import java.io.InputStream; 088 import java.security.InvalidKeyException; 089 import java.security.Key; 090 import java.security.NoSuchAlgorithmException; 091 092 import javax.crypto.SecretKey; 093 094 import demo.DemoUtil; 095 096 /** 097 * Demonstrates the usage of class {@link iaik.cms.AuthenticatedDataStream}, 098 * {@link iaik.cms.AuthenticatedData} and {@link iaik.cms.AuthenticatedDataOutputStream} 099 * for authenticated encrypting data using the CMS type 100 * AuthenticatedData by using Static-Static ECDH according to <a href = 101 * "http://www.ietf.org/rfc/rfc6278.txt" target="_blank">6278</a> as 102 * key agreement method. 103 * <p> 104 * Any keys/certificates required for this demo are read from a keystore 105 * file "cmsecc.keystore" located in your current working directory. If 106 * the keystore file does not exist you can create it by running the 107 * {@link demo.cms.ecc.keystore.SetupCMSEccKeyStore SetupCMSEccKeyStore} 108 * program. 109 * <p> 110 * Additionally to <code>iaik_cms.jar</code> you also must have 111 * <code>iaik_jce_(full).jar</code> (IAIK-JCE, <a href = 112 * "https://sic.tech/products/core-crypto-toolkits/jca-jce/" target="_blank"> 113 * https://sic.tech/products/core-crypto-toolkits/jca-jce/</a>), 114 * and <code>iaik_eccelarate.jar</code> (IAIK-ECCelerate<sup><small>TM</small></sup>, <a href = 115 * "https://sic.tech/products/core-crypto-toolkits/eccelerate/" target="_blank"> 116 * https://sic.tech/products/core-crypto-toolkits/eccelerate/</a>) 117 * in your classpath. 118 * 119 * @see iaik.cms.AuthenticatedDataStream 120 * @see iaik.cms.AuthenticatedData 121 * @see iaik.cms.AuthenticatedDataOutputStream 122 * @see iaik.cms.RecipientInfo 123 * @see iaik.cms.KeyAgreeRecipientInfo 124 * @see demo.cms.ecc.keystore.SetupCMSEccKeyStore 125 */ 126 public class ECDHAuthenticatedDataDemo extends StaticStaticECDHDemo { 127 128 /** 129 * Setup the demo certificate chains. 130 * 131 * Keys and certificates are retrieved from the demo keyStore file 132 * "cmsecc.keystore" located in your current working directory. If 133 * the keystore file does not exist you can create it by running the 134 * {@link demo.cms.ecc.keystore.SetupCMSEccKeyStore SetupCMSEccKeyStore} 135 * program. 136 * 137 * @throws IOException if keys/certificates cannot be read from the keystore 138 */ 139 public ECDHAuthenticatedDataDemo() throws IOException { 140 super(); 141 System.out.println(); 142 System.out.println("**********************************************************************************"); 143 System.out.println("* ECDH AuthenticatedData demo *"); 144 System.out.println("* (shows the usage of the CMS AuthenticatedData type implementation for ECDH) *"); 145 System.out.println("**********************************************************************************"); 146 System.out.println(); 147 148 149 } 150 151 /** 152 * Creates a CMS <code>AuthenticatedDataStream</code> for the given message message. 153 * 154 * @param message the message to be authenticated, as byte representation 155 * @param macAlgorithm the mac algorithm to be used 156 * @param macKeyLength the length of the temporary MAC key to be generated 157 * @param digestAlgorithm the digest algorithm to be used to calculate a digest 158 * from the content if authenticated attributes should 159 * be included 160 * @param mode whether to include the content into the AuthenticatedData ({@link 161 * AuthenticatedDataStream#IMPLICIT implicit}) or to not include it 162 * ({@link AuthenticatedDataStream#EXPLICIT explicit}) 163 * 164 * @return the BER encoding of the <code>AuthenticatedData</code> object just created 165 * 166 * @throws CMSException if the <code>AuthenticatedData</code> object cannot 167 * be created 168 * @throws IOException if an I/O error occurs 169 */ 170 public byte[] createAuthenticatedDataStream(byte[] message, 171 AlgorithmID macAlgorithm, 172 int macKeyLength, 173 AlgorithmID digestAlgorithm, 174 int mode) 175 throws CMSException, IOException { 176 177 AlgorithmID macAlg = (AlgorithmID)macAlgorithm.clone(); 178 AlgorithmID digestAlg = null; 179 if (digestAlgorithm != null) { 180 digestAlg = (AlgorithmID)digestAlgorithm.clone(); 181 } 182 ObjectID contentType = ObjectID.cms_data; 183 184 AuthenticatedDataStream authenticatedData; 185 186 // we are testing the stream interface 187 ByteArrayInputStream is = new ByteArrayInputStream(message); 188 // create a new AuthenticatedData object 189 try { 190 authenticatedData = new AuthenticatedDataStream(contentType, 191 is, 192 macAlg, 193 macKeyLength, 194 null, 195 digestAlg, 196 mode); 197 } catch (NoSuchAlgorithmException ex) { 198 throw new CMSException(ex.toString()); 199 } 200 201 // static-static mode: set OriginatorInfo 202 OriginatorInfo originator = new OriginatorInfo(); 203 originator.setCertificates(ecdhOriginatorCerts_); 204 authenticatedData.setOriginatorInfo(originator); 205 // create the recipient infos 206 RecipientInfo[] recipients = createRecipients(); 207 // specify the recipients of the authenticated message 208 authenticatedData.setRecipientInfos(recipients); 209 210 if (digestAlgorithm != null) { 211 // create some authenticated attributes 212 // (the message digest attribute is automatically added) 213 try { 214 Attribute[] attributes = { new Attribute(new CMSContentType(contentType)) }; 215 authenticatedData.setAuthenticatedAttributes(attributes); 216 } catch (Exception ex) { 217 throw new CMSException("Error creating attribute: " + ex.toString()); 218 } 219 } 220 221 // in explicit mode get the content and write it to any out-of-band place 222 if (mode == AuthenticatedDataStream.EXPLICIT) { 223 InputStream data_is = authenticatedData.getInputStream(); 224 byte[] buf = new byte[2048]; 225 int r; 226 while ((r = data_is.read(buf)) > 0) { 227 ; // skip data 228 } 229 } 230 231 // return the AuthenticatedData as BER encoded byte array with block size 16 232 // (just for testing; in real application we will use a proper blocksize, 233 // e.g. 2048, 4096,..) 234 authenticatedData.setBlockSize(16); 235 // return the AuthenticatedDate as BER encoded byte array with block size 2048 236 ByteArrayOutputStream os = new ByteArrayOutputStream(); 237 // wrap into ContentInfo 238 ContentInfoStream contentInfo = new ContentInfoStream(authenticatedData); 239 contentInfo.writeTo(os); 240 return os.toByteArray(); 241 } 242 243 /** 244 * Creates a CMS <code>AuthenticatedDataOutputStream</code> for the given message message. 245 * 246 * @param message the message to be authenticated, as byte representation 247 * @param macAlgorithm the mac algorithm to be used 248 * @param macKeyLength the length of the temporary MAC key to be generated 249 * @param digestAlgorithm the digest algorithm to be used to calculate a digest 250 * from the content if authenticated attributes should 251 * be included 252 * @param mode whether to include the content into the AuthenticatedData ({@link 253 * AuthenticatedDataStream#IMPLICIT implicit}) or to not include it 254 * ({@link AuthenticatedDataStream#EXPLICIT explicit}) 255 * 256 * @return the BER encoding of the <code>AuthenticatedData</code> object just created, 257 * wrapped into a ContentInfo 258 * 259 * @throws CMSException if the <code>AuthenticatedData</code> object cannot 260 * be created 261 * @throws IOException if an I/O error occurs 262 */ 263 public byte[] createAuthenticatedDataOutputStream(byte[] message, 264 AlgorithmID macAlgorithm, 265 int macKeyLength, 266 AlgorithmID digestAlgorithm, 267 int mode) 268 throws CMSException, IOException { 269 270 AlgorithmID macAlg = (AlgorithmID)macAlgorithm.clone(); 271 AlgorithmID digestAlg = null; 272 if (digestAlgorithm != null) { 273 digestAlg = (AlgorithmID)digestAlgorithm.clone(); 274 } 275 ObjectID contentType = ObjectID.cms_data; 276 277 AuthenticatedDataOutputStream authenticatedData; 278 279 // a stream from which to read the data to be authenticated 280 ByteArrayInputStream is = new ByteArrayInputStream(message); 281 282 // the stream to which to write the AuthenticatedData 283 ByteArrayOutputStream resultStream = new ByteArrayOutputStream(); 284 285 // wrap AuthenticatedData into a ContentInfo 286 ContentInfoOutputStream contentInfoStream = 287 new ContentInfoOutputStream(ObjectID.cms_authData, resultStream); 288 289 // create AuthenticatedDataOutputStream 290 try { 291 authenticatedData = new AuthenticatedDataOutputStream(contentType, 292 contentInfoStream, 293 macAlg, 294 macKeyLength, 295 null, 296 digestAlg, 297 mode); 298 } catch (NoSuchAlgorithmException ex) { 299 throw new CMSException(ex.toString()); 300 } 301 302 // static-static mode: set OriginatorInfo 303 OriginatorInfo originator = new OriginatorInfo(); 304 originator.setCertificates(ecdhOriginatorCerts_); 305 authenticatedData.setOriginatorInfo(originator); 306 // create the recipient infos 307 RecipientInfo[] recipients = createRecipients(); 308 // specify the recipients of the authenticated message 309 authenticatedData.setRecipientInfos(recipients); 310 311 if (digestAlgorithm != null) { 312 // create some authenticated attributes 313 // (the message digest attribute is automatically added) 314 try { 315 Attribute[] attributes = { new Attribute(new CMSContentType(contentType)) }; 316 authenticatedData.setAuthenticatedAttributes(attributes); 317 } catch (Exception ex) { 318 throw new CMSException("Error creating attribute: " + ex.toString()); 319 } 320 } 321 322 int blockSize = 20; // in real world we would use a block size like 2048 323 // write in the data to be signed 324 byte[] buffer = new byte[blockSize]; 325 int bytesRead; 326 while ((bytesRead = is.read(buffer)) != -1) { 327 authenticatedData.write(buffer, 0, bytesRead); 328 } 329 330 // closing the stream adds auth/unauth attributes, calculates and adds the mac value, . 331 authenticatedData.close(); 332 return resultStream.toByteArray(); 333 } 334 335 336 /** 337 * Decrypts the encrypted MAC key for the recipient identified by its index 338 * into the recipientInfos field and uses the MAC key to verify 339 * the authenticated data. 340 * <p> 341 * This way of decrypting the MAC key and verifying the content may be used for 342 * any type of RecipientInfo (KeyTransRecipientInfo, KeyAgreeRecipientInfo, 343 * KEKRecipientInfo, PasswordRecipeintInfo, OtherRecipientInfo), but requires to 344 * know at what index of the recipientInfos field the RecipientInfo for the 345 * particular recipient in mind can be found. 346 * If the recipient in mind uses a RecipientInfo of type KeyAgreeRecipientInfo 347 * some processing overhead may take place because a KeyAgreeRecipientInfo may 348 * contain encrypted mac keys for more than only one recipient; since the 349 * recipientInfoIndex only specifies the RecipientInfo but not the encrypted 350 * mac key -- if there are more than only one -- repeated decryption runs may be 351 * required as long as the decryption process completes successfully. 352 * 353 * @param encoding the <code>AuthenticatedData</code> object as BER encoded byte array 354 * @param message the content message, if transmitted by other means (explicit mode) 355 * @param key the key to decrypt the mac key 356 * @param recipientInfoIndex the index of the right <code>RecipientInfo</code> to 357 * which the given key belongs 358 * 359 * @return the verified message, as byte array 360 * 361 * @throws CMSException if the authenticated data cannot be verified 362 * @throws IOException if a stream read/write error occurs 363 */ 364 public byte[] getAuthenticatedDataStream(byte[] encoding, 365 byte[] message, 366 Key key, 367 int recipientInfoIndex) 368 throws CMSException, IOException { 369 370 // create the AuthenticatedData object from a DER encoded byte array 371 // we are testing the stream interface 372 ByteArrayInputStream is = new ByteArrayInputStream(encoding); 373 AuthenticatedDataStream authenticatedData = new AuthenticatedDataStream(is); 374 375 if (authenticatedData.getMode() == AuthenticatedDataStream.EXPLICIT) { 376 // in explicit mode explicitly supply the content for hash/mac computation 377 authenticatedData.setInputStream(new ByteArrayInputStream(message)); 378 } 379 380 System.out.println("\nThis message can be verified by the following recipients:"); 381 RecipientInfo[] recipients = authenticatedData.getRecipientInfos(); 382 383 // for demonstration purposes we only look one time for all recipients included: 384 if (recipientInfoIndex == 0) { 385 int k = 0; 386 for (int i=0; i<recipients.length; i++) { 387 KeyIdentifier[] recipientIDs = recipients[i].getRecipientIdentifiers(); 388 for (int j = 0; j < recipientIDs.length; j++) { 389 System.out.println("Recipient "+(++k)+":"); 390 System.out.println(recipientIDs[j]); 391 } 392 } 393 } 394 // decrypt the mac key and verify the mac for the indented recipient 395 try { 396 authenticatedData.setupMac(key, recipientInfoIndex); 397 InputStream contentStream = authenticatedData.getInputStream(); 398 ByteArrayOutputStream os = new ByteArrayOutputStream(); 399 Util.copyStream(contentStream, os, null); 400 401 if (authenticatedData.verifyMac() == false) { 402 throw new CMSException("Mac verification error!"); 403 } 404 System.out.println("Mac successfully verified!"); 405 406 return os.toByteArray(); 407 408 } catch (InvalidKeyException ex) { 409 throw new CMSException("Key error: "+ex.getMessage()); 410 } catch (NoSuchAlgorithmException ex) { 411 throw new CMSException(ex.toString()); 412 } 413 } 414 415 /** 416 * Decrypts the encrypted MAC key for the recipient identified by recipient identifier 417 * and uses the MAC key to verify the authenticated data. 418 * <p> 419 * This way of decrypting the mac key may be used for any type of RecipientInfo 420 * (KeyTransRecipientInfo, KeyAgreeRecipientInfo, KEKRecipientInfo). The 421 * recipient in mind is identified by its recipient identifier. 422 * 423 * @param encoding the <code>AuthenticatedData</code> object as BER encoded byte array 424 * @param message the content message, if transmitted by other means (explicit mode) 425 * @param key the key to decrypt the encrypted mac key 426 * @param recipientID the recipient identifier uniquely identifying the key of the 427 * recipient 428 * 429 * @return the verified message, as byte array 430 * 431 * @throws CMSException if the authenticated data cannot be verified 432 * @throws IOException if a stream read/write error occurs 433 */ 434 public byte[] getAuthenticatedDataStream(byte[] encoding, 435 byte[] message, 436 Key key, 437 KeyIdentifier recipientID) 438 throws CMSException, IOException { 439 440 // create the AuthenticatedData object from a BER encoded byte array 441 // we are testing the stream interface 442 ByteArrayInputStream is = new ByteArrayInputStream(encoding); 443 AuthenticatedDataStream authenticatedData = new AuthenticatedDataStream(is); 444 445 if (authenticatedData.getMode() == AuthenticatedDataStream.EXPLICIT) { 446 // in explicit mode explicitly supply the content for hash/mac computation 447 authenticatedData.setInputStream(new ByteArrayInputStream(message)); 448 } 449 450 // get the right RecipientInfo 451 System.out.println("\nSearch for RecipientInfo:"); 452 RecipientInfo recipient = authenticatedData.getRecipientInfo(recipientID); 453 if (recipient != null) { 454 System.out.println("RecipientInfo: " + recipient); 455 } else { 456 throw new CMSException("No recipient with ID: " + recipientID); 457 } 458 // decrypt the mac key and verify the content mac 459 try { 460 System.out.println("Decrypt encrypted mac key..."); 461 SecretKey cek = recipient.decryptKey(key, recipientID); 462 System.out.println("Verify content mac with decrypted mac key..."); 463 authenticatedData.setupMac(cek); 464 InputStream contentStream = authenticatedData.getInputStream(); 465 ByteArrayOutputStream os = new ByteArrayOutputStream(); 466 Util.copyStream(contentStream, os, null); 467 468 if (authenticatedData.verifyMac() == false) { 469 throw new CMSException("Mac verification error!"); 470 } 471 System.out.println("Mac successfully verified!"); 472 473 return os.toByteArray(); 474 475 } catch (InvalidKeyException ex) { 476 throw new CMSException("Key error: "+ex.getMessage()); 477 } catch (NoSuchAlgorithmException ex) { 478 throw new CMSException(ex.toString()); 479 } 480 } 481 482 /** 483 * Decrypts the encrypted content of the given <code>AuthenticatedData</code> object for 484 * the recipient identified by its recipient certificate. 485 * <p> 486 * 487 * @param encoding the <code>AuthenticatedData</code> object as DER encoded byte array 488 * @param message the content message, if transmitted by other means (explicit mode) 489 * @param key the key to decrypt the message 490 * @param recipientCert the certificate of the recipient having a RecipientInfo of 491 * type KeyTransRecipientInfo or KeyAgreeRecipientInfo 492 * 493 * @return the recovered message, as byte array 494 * @throws CMSException if the message cannot be recovered 495 * @throws IOException if a stream read/write error occurs 496 */ 497 public byte[] getAuthenticatedDataStream(byte[] encoding, 498 byte[] message, 499 Key key, 500 X509Certificate recipientCert) 501 throws CMSException, IOException { 502 503 // create the AuthenticatedData object from a DER encoded byte array 504 // we are testing the stream interface 505 ByteArrayInputStream is = new ByteArrayInputStream(encoding); 506 AuthenticatedDataStream authenticatedData = new AuthenticatedDataStream(is); 507 508 if (authenticatedData.getMode() == AuthenticatedDataStream.EXPLICIT) { 509 // in explicit mode explicitly supply the content for hash/mac computation 510 authenticatedData.setInputStream(new ByteArrayInputStream(message)); 511 } 512 513 514 // decrypt the mac key and verify the content mac 515 try { 516 System.out.println("Verify mac..."); 517 authenticatedData.setupMac(key, recipientCert); 518 InputStream contentStream = authenticatedData.getInputStream(); 519 ByteArrayOutputStream os = new ByteArrayOutputStream(); 520 Util.copyStream(contentStream, os, null); 521 522 if (authenticatedData.verifyMac() == false) { 523 throw new CMSException("Mac verification error!"); 524 } 525 System.out.println("Mac successfully verified!"); 526 527 return os.toByteArray(); 528 529 } catch (InvalidKeyException ex) { 530 throw new CMSException("Key error: "+ex.getMessage()); 531 } catch (NoSuchAlgorithmException ex) { 532 throw new CMSException(ex.toString()); 533 } 534 } 535 536 537 // non stream 538 539 /** 540 * Creates a CMS <code>AuthenticatedData</code> for the given message message. 541 * 542 * @param message the message to be authenticated, as byte representation 543 * @param macAlgorithm the mac algorithm to be used 544 * @param macKeyLength the length of the temporary MAC key to be generated 545 * @param digestAlgorithm the digest algorithm to be used to calculate a digest 546 * from the content if authenticated attributes should 547 * be included 548 * @param mode whether to include the content into the AuthenticatedData ({@link 549 * AuthenticatedDataStream#IMPLICIT implicit}) or to not include it 550 * ({@link AuthenticatedDataStream#EXPLICIT explicit}) 551 * 552 * @return the BER encoding of the <code>AuthenticatedData</code> object just created 553 * 554 * @throws CMSException if the <code>AuthenticatedData</code> object cannot 555 * be created 556 */ 557 public byte[] createAuthenticatedData(byte[] message, 558 AlgorithmID macAlgorithm, 559 int macKeyLength, 560 AlgorithmID digestAlgorithm, 561 int mode) 562 throws CMSException { 563 564 AlgorithmID macAlg = (AlgorithmID)macAlgorithm.clone(); 565 AlgorithmID digestAlg = null; 566 if (digestAlgorithm != null) { 567 digestAlg = (AlgorithmID)digestAlgorithm.clone(); 568 } 569 ObjectID contentType = ObjectID.cms_data; 570 571 AuthenticatedData authenticatedData; 572 573 // create a new AuthenticatedData object 574 try { 575 authenticatedData = new AuthenticatedData(contentType, 576 message, 577 macAlg, 578 macKeyLength, 579 null, 580 digestAlg, 581 mode); 582 } catch (NoSuchAlgorithmException ex) { 583 throw new CMSException(ex.toString()); 584 } 585 586 // static-static mode: set OriginatorInfo 587 OriginatorInfo originator = new OriginatorInfo(); 588 originator.setCertificates(ecdhOriginatorCerts_); 589 authenticatedData.setOriginatorInfo(originator); 590 // create the recipient infos 591 RecipientInfo[] recipients = createRecipients(); 592 // specify the recipients of the authenticated message 593 authenticatedData.setRecipientInfos(recipients); 594 595 if (digestAlgorithm != null) { 596 // create some authenticated attributes 597 // (the message digest attribute is automatically added) 598 try { 599 Attribute[] attributes = { new Attribute(new CMSContentType(contentType)) }; 600 authenticatedData.setAuthenticatedAttributes(attributes); 601 } catch (Exception ex) { 602 throw new CMSException("Error creating attribute: " + ex.toString()); 603 } 604 } 605 606 // wrap into ContentInfo 607 ContentInfo contentInfo = new ContentInfo(authenticatedData); 608 // return encoded EnvelopedData 609 return contentInfo.getEncoded(); 610 611 } 612 613 /** 614 * Decrypts the encrypted MAC key for the recipient identified by its index 615 * into the recipientInfos field and uses the MAC key to verify 616 * the authenticated data. 617 * <p> 618 * This way of decrypting the MAC key and verifying the content may be used for 619 * any type of RecipientInfo (KeyTransRecipientInfo, KeyAgreeRecipientInfo, 620 * KEKRecipientInfo), but requires to know at what index of the recipientInfos 621 * field the RecipientInfo for the particular recipient in mind can be found. 622 * If the recipient in mind uses a RecipientInfo of type KeyAgreeRecipientInfo 623 * some processing overhead may take place because a KeyAgreeRecipientInfo may 624 * contain encrypted mac keys for more than only one recipient; since the 625 * recipientInfoIndex only specifies the RecipientInfo but not the encrypted 626 * mac key -- if there are more than only one -- repeated decryption runs may be 627 * required as long as the decryption process completes successfully. 628 * 629 * @param encoding the <code>AuthenticatedData</code> object as BER encoded byte array 630 * @param message the content message, if transmitted by other means (explicit mode) 631 * @param key the key to decrypt the mac key 632 * @param recipientInfoIndex the index of the right <code>RecipientInfo</code> to 633 * which the given key belongs 634 * 635 * @return the verified message, as byte array 636 * @throws CMSException if the authenticated data cannot be verified 637 * @throws IOException if a IO read/write error occurs 638 */ 639 public byte[] getAuthenticatedData(byte[] encoding, 640 byte[] message, 641 Key key, 642 int recipientInfoIndex) 643 throws CMSException, IOException { 644 645 // create the AuthenticatedData object from a DER encoded byte array 646 ByteArrayInputStream is = new ByteArrayInputStream(encoding); 647 AuthenticatedData authenticatedData = new AuthenticatedData(is); 648 649 if (authenticatedData.getMode() == AuthenticatedData.EXPLICIT) { 650 // in explicit mode explicitly supply the content for hash/mac computation 651 authenticatedData.setContent(message); 652 } 653 654 System.out.println("\nThis message can be verified by the owners of the following recipients:"); 655 RecipientInfo[] recipients = authenticatedData.getRecipientInfos(); 656 657 // for demonstration purposes we only look one time for all recipients included: 658 if (recipientInfoIndex == 0) { 659 int k = 0; 660 for (int i=0; i<recipients.length; i++) { 661 KeyIdentifier[] recipientIDs = recipients[i].getRecipientIdentifiers(); 662 for (int j = 0; j < recipientIDs.length; j++) { 663 System.out.println("Recipient "+(++k)+":"); 664 System.out.println(recipientIDs[j]); 665 } 666 } 667 } 668 // decrypt the mac key and verify the mac for the first recipient 669 try { 670 authenticatedData.setupMac(key, recipientInfoIndex); 671 if (authenticatedData.verifyMac() == false) { 672 throw new CMSException("Mac verification error!"); 673 } 674 System.out.println("Mac successfully verified!"); 675 676 return authenticatedData.getContent(); 677 678 } catch (InvalidKeyException ex) { 679 throw new CMSException("Key error: "+ex.getMessage()); 680 } catch (NoSuchAlgorithmException ex) { 681 throw new CMSException(ex.toString()); 682 } 683 } 684 685 /** 686 * Decrypts the encrypted content of the given <code>AuthenticatedData</code> object for 687 * the recipient identified by recipient identifier. 688 * <p> 689 * This way of decrypting the content may be used for any type of RecipientInfo 690 * (KeyTransRecipientInfo, KeyAgreeRecipientInfo, KEKRecipientInfo). The 691 * recipient in mind is identified by its recipient identifier. 692 * 693 * @param encoding the DER encoeded <code>AuthenticatedData</code> object# 694 * @param message the content message, if transmitted by other means (explicit mode) 695 * @param key the key to decrypt the message 696 * @param recipientID the recipient identifier uniquely identifying the key of the 697 * recipient 698 * 699 * @return the recovered message, as byte array 700 * @throws CMSException if the message cannot be recovered 701 * @throws IOException if an I/O error occurs 702 */ 703 public byte[] getAuthenticatedData(byte[] encoding, 704 byte[] message, 705 Key key, 706 KeyIdentifier recipientID) 707 throws CMSException, IOException { 708 709 // create the AuthenticatedData object from a DER encoded byte array 710 ByteArrayInputStream is = new ByteArrayInputStream(encoding); 711 AuthenticatedData authenticatedData = new AuthenticatedData(is); 712 713 if (authenticatedData.getMode() == AuthenticatedData.EXPLICIT) { 714 // in explicit mode explicitly supply the content for hash/mac computation 715 authenticatedData.setContent(message); 716 } 717 718 // get the right RecipientInfo 719 System.out.println("\nSearch for RecipientInfo:"); 720 RecipientInfo recipient = authenticatedData.getRecipientInfo(recipientID); 721 if (recipient != null) { 722 System.out.println("RecipientInfo: " + recipient); 723 } else { 724 throw new CMSException("No recipient with ID " + recipientID); 725 } 726 // decrypt the mac key and verify the content mac 727 try { 728 System.out.println("Decrypt encrypted mac key..."); 729 SecretKey cek = recipient.decryptKey(key, recipientID); 730 System.out.println("Verify content mac with decrypted mac key..."); 731 authenticatedData.setupMac(cek); 732 733 if (authenticatedData.verifyMac() == false) { 734 throw new CMSException("Mac verification error!"); 735 } 736 System.out.println("Mac successfully verified!"); 737 738 return authenticatedData.getContent(); 739 740 } catch (InvalidKeyException ex) { 741 throw new CMSException("Key error: "+ex.getMessage()); 742 } 743 } 744 745 /** 746 * Decrypts the encrypted content of the given <code>AuthenticatedData</code> object for 747 * the recipient identified by its recipient certificate. 748 * <p> 749 * 750 * @param encoding the DER encoded <code>AuthenticatedData</code> ASN.1 object 751 * @param message the content message, if transmitted by other means (explicit mode) 752 * @param key the key to decrypt the message 753 * @param recipientCert the certificate of the recipient having a RecipientInfo of 754 * type KeyTransRecipientInfo or KeyAgreeRecipientInfo 755 * 756 * @return the recovered message, as byte array 757 * @throws CMSException if the message cannot be recovered 758 */ 759 public byte[] getAuthenticatedData(byte[] encoding, 760 byte[] message, 761 Key key, 762 X509Certificate recipientCert) 763 throws CMSException, IOException { 764 765 // create the AuthenticatedData object from a DER encoded byte array 766 ByteArrayInputStream is = new ByteArrayInputStream(encoding); 767 AuthenticatedData authenticatedData = new AuthenticatedData(is); 768 769 if (authenticatedData.getMode() == AuthenticatedData.EXPLICIT) { 770 // in explicit mode explicitly supply the content for hash/mac computation 771 authenticatedData.setContent(message); 772 } 773 774 // decrypt the mac key and verify the content mac 775 try { 776 System.out.println("Verify mac..."); 777 authenticatedData.setupMac(key, recipientCert); 778 if (authenticatedData.verifyMac() == false) { 779 throw new CMSException("Mac verification error!"); 780 } 781 System.out.println("Mac successfully verified!"); 782 783 return authenticatedData.getContent(); 784 785 } catch (InvalidKeyException ex) { 786 throw new CMSException("Key error: "+ex.getMessage()); 787 } catch (NoSuchAlgorithmException ex) { 788 throw new CMSException(ex.toString()); 789 } 790 } 791 792 /** 793 * Parses an AuthenticatedData and verifies the mac for all recipients 794 * using the index into the recipientInfos field for identifying the recipient. 795 * 796 * @param stream whether to use AuthenticatedDataStream or AuthenticatedData 797 * @param encodedAuthenticatedData the encoded AuthenticatedData object 798 * @param message the content message, if transmitted by other means (explicit mode) 799 * 800 * @throws Exception if some error occurs during decoding/decryption 801 */ 802 public void parseAuthenticatedDataWithRecipientInfoIndex(boolean stream, 803 byte[] encodedAuthenticatedData, byte[] message) throws Exception { 804 byte[] receivedMessage; 805 if (stream) { 806 // ecdhUser1 807 System.out.println("\nDecrypt for ecdhUser1:"); 808 receivedMessage = getAuthenticatedDataStream(encodedAuthenticatedData, message, ecdhUser1Pk_, 0); 809 System.out.print("\nDecrypted content: "); 810 System.out.println(new String(receivedMessage)); 811 // ecdhUser2 812 System.out.println("\nDecrypt for ecdhUser2:"); 813 receivedMessage = getAuthenticatedDataStream(encodedAuthenticatedData, message, ecdhUser2Pk_, 0); 814 System.out.print("\nDecrypted content: "); 815 System.out.println(new String(receivedMessage)); 816 } else { 817 // ecdhUser1 818 System.out.println("\nDecrypt for ecdhUser1:"); 819 receivedMessage = getAuthenticatedData(encodedAuthenticatedData, message, ecdhUser1Pk_, 0); 820 System.out.print("\nDecrypted content: "); 821 System.out.println(new String(receivedMessage)); 822 // ecdhUser2 823 System.out.println("\nDecrypt for ecdhUser2:"); 824 receivedMessage = getAuthenticatedData(encodedAuthenticatedData, message, ecdhUser2Pk_, 0); 825 System.out.print("\nDecrypted content: "); 826 System.out.println(new String(receivedMessage)); 827 } 828 } 829 830 /** 831 * Parses an AuthenticatedData and verifies the mac for all recipients 832 * using their recipient identifiers for identifying the recipient. 833 * 834 * @param stream whether to use AuthenticatedDataStream or AuthenticatedData 835 * @param encodedAuthenticatedData the encoded AuthenticatedData object 836 * @param message the content message, if transmitted by other means (explicit mode) 837 * 838 * @throws Exception if some error occurs during decoding/decryption 839 */ 840 public void parseAuthenticatedDataWithRecipientIdentifier(boolean stream, byte[] encodedAuthenticatedData, 841 byte[] message) throws Exception { 842 byte[] receivedMessage; 843 if (stream) { 844 // ecdhUser1 845 System.out.println("\nDecrypt for ecdhUser1:"); 846 receivedMessage = getAuthenticatedDataStream(encodedAuthenticatedData, message, ecdhUser1Pk_, new IssuerAndSerialNumber(ecdhUser1_)); 847 System.out.print("\nDecrypted content: "); 848 System.out.println(new String(receivedMessage)); 849 // ecdhUser2 850 System.out.println("\nDecrypt for ecdhUser2:"); 851 receivedMessage = getAuthenticatedDataStream(encodedAuthenticatedData, message, ecdhUser2Pk_, new RecipientKeyIdentifier(ecdhUser2_)); 852 System.out.print("\nDecrypted content: "); 853 System.out.println(new String(receivedMessage)); 854 } else { 855 // ecdhUser1 856 System.out.println("\nDecrypt for ecdhUser1:"); 857 receivedMessage = getAuthenticatedData(encodedAuthenticatedData, message, ecdhUser1Pk_, new IssuerAndSerialNumber(ecdhUser1_)); 858 System.out.print("\nDecrypted content: "); 859 System.out.println(new String(receivedMessage)); 860 // ecdhUser2 861 System.out.println("\nDecrypt for ecdhUser2:"); 862 receivedMessage = getAuthenticatedData(encodedAuthenticatedData, message, ecdhUser2Pk_, new RecipientKeyIdentifier(ecdhUser2_)); 863 System.out.print("\nDecrypted content: "); 864 System.out.println(new String(receivedMessage)); 865 } 866 } 867 868 /** 869 * Parses an AuthenticatedData and verifies the mac for all recipients 870 * using the recipient certificate for identifying the recipient. 871 * 872 * @param stream whether to use AuthenticatedDataStream or AuthenticatedData 873 * @param encodedAuthenticatedData the encoded AuthenticatedData object 874 * @param message the content message, if transmitted by other means (explicit mode) 875 * 876 * @throws Exception if some error occurs during decoding/decryption 877 */ 878 public void parseAuthenticatedDataWithRecipientCertificate(boolean stream, byte[] encodedAuthenticatedData, 879 byte[] message) throws Exception { 880 byte[] receivedMessage; 881 if (stream) { 882 // ecdhUser1 883 System.out.println("\nDecrypt for ecdhUser1:"); 884 receivedMessage = getAuthenticatedDataStream(encodedAuthenticatedData, message, ecdhUser1Pk_, ecdhUser1_); 885 System.out.print("\nDecrypted content: "); 886 System.out.println(new String(receivedMessage)); 887 // ecdhUser2 888 System.out.println("\nDecrypt for ecdhUser2:"); 889 receivedMessage = getAuthenticatedDataStream(encodedAuthenticatedData, message, ecdhUser2Pk_, ecdhUser2_); 890 System.out.print("\nDecrypted content: "); 891 System.out.println(new String(receivedMessage)); 892 } else { 893 // ecdhUser1 894 System.out.println("\nDecrypt for ecdhUser1:"); 895 receivedMessage = getAuthenticatedData(encodedAuthenticatedData, message, ecdhUser1Pk_, ecdhUser1_); 896 System.out.print("\nDecrypted content: "); 897 System.out.println(new String(receivedMessage)); 898 // ecdhUser2 899 System.out.println("\nDecrypt for ecdhUser2:"); 900 receivedMessage = getAuthenticatedData(encodedAuthenticatedData, message, ecdhUser2Pk_, ecdhUser2_); 901 System.out.print("\nDecrypted content: "); 902 System.out.println(new String(receivedMessage)); 903 } 904 } 905 906 907 908 /** 909 * Starts the demo. 910 */ 911 public void start() { 912 // the test message 913 String m = "This is the test message."; 914 System.out.println("Test message: \""+m+"\""); 915 System.out.println(); 916 byte[] message = m.getBytes(); 917 918 AlgorithmID macAlgorithm = (AlgorithmID)AlgorithmID.hMAC_SHA256.clone(); 919 int macKeyLength = 32; 920 AlgorithmID digestAlgorithm = (AlgorithmID)AlgorithmID.sha256.clone(); 921 922 try { 923 byte[] encoding; 924 System.out.println("Stream implementation demos"); 925 System.out.println("==========================="); 926 927 928 // the stream implementation 929 // 930 // test CMS AuthenticatedDataStream 931 // 932 933 int[] modes = { AuthenticatedDataStream.IMPLICIT, AuthenticatedDataStream.EXPLICIT }; 934 935 for (int i = 0; i < modes.length; i++) { 936 int mode = modes[i]; 937 if (mode == AuthenticatedDataStream.IMPLICIT) { 938 System.out.print("Implicit "); 939 } else { 940 System.out.print("Explicit "); 941 } 942 // with authenticated attributes 943 System.out.println("AuthenticatedDataStream demo with authenticated attributes [create]:\n"); 944 encoding = createAuthenticatedDataStream(message, 945 macAlgorithm, 946 macKeyLength, 947 digestAlgorithm, 948 mode); 949 // transmit data 950 System.out.println("\nAuthenticatedDataStream demo with authenticated attributes [parse]:\n"); 951 System.out.println("Parse for the several recipients using their index into the recipientInfos field."); 952 parseAuthenticatedDataWithRecipientInfoIndex(true, encoding, message); 953 System.out.println("Parse for the several recipients using their RecipientIdentifier."); 954 parseAuthenticatedDataWithRecipientIdentifier(true, encoding, message); 955 System.out.println("Parse for the several recipients using their certificate."); 956 parseAuthenticatedDataWithRecipientCertificate(true, encoding, message); 957 958 // without authenticated attributes 959 System.out.println("AuthenticatedDataStream demo without authenticated attributes [create]:\n"); 960 encoding = createAuthenticatedDataStream(message, 961 macAlgorithm, 962 macKeyLength, 963 null, 964 mode); 965 // transmit data 966 System.out.println("\nAuthenticatedDataStream demo without authenticated attributes [parse]:\n"); 967 System.out.println("Parse for the several recipients using their index into the recipientInfos field."); 968 parseAuthenticatedDataWithRecipientInfoIndex(true, encoding, message); 969 System.out.println("Parse for the several recipients using their RecipientIdentifier."); 970 parseAuthenticatedDataWithRecipientIdentifier(true, encoding, message); 971 System.out.println("Parse for the several recipients using their certificate."); 972 parseAuthenticatedDataWithRecipientCertificate(true, encoding, message); 973 974 } 975 976 System.out.println("\nOutputStream implementation demos"); 977 System.out.println("================================="); 978 979 980 // the output stream implementation 981 // 982 // test CMS AuthenticatedDataOutputStream 983 // 984 for (int i = 0; i < modes.length; i++) { 985 int mode = modes[i]; 986 if (mode == AuthenticatedDataStream.IMPLICIT) { 987 System.out.print("Implicit "); 988 } else { 989 System.out.print("Explicit "); 990 } 991 // with authenticated attributes 992 System.out.println("AuthenticatedDataOutputStream demo with authenticated attributes [create]:\n"); 993 encoding = createAuthenticatedDataOutputStream(message, 994 macAlgorithm, 995 macKeyLength, 996 digestAlgorithm, 997 mode); 998 // transmit data 999 System.out.println("\nAuthenticatedDataStream demo with authenticated attributes [parse]:\n"); 1000 System.out.println("Parse for the several recipients using their index into the recipientInfos field."); 1001 parseAuthenticatedDataWithRecipientInfoIndex(true, encoding, message); 1002 System.out.println("Parse for the several recipients using their RecipientIdentifier."); 1003 parseAuthenticatedDataWithRecipientIdentifier(true, encoding, message); 1004 System.out.println("Parse for the several recipients using their certificate."); 1005 parseAuthenticatedDataWithRecipientCertificate(true, encoding, message); 1006 1007 // without authenticated attributes 1008 System.out.println("AuthenticatedDataOutputStream demo without authenticated attributes [create]:\n"); 1009 encoding = createAuthenticatedDataOutputStream(message, 1010 macAlgorithm, 1011 macKeyLength, 1012 null, 1013 mode); 1014 // transmit data 1015 System.out.println("\nAuthenticatedDataOutputStream demo without authenticated attributes [parse]:\n"); 1016 System.out.println("Parse for the several recipients using their index into the recipientInfos field."); 1017 parseAuthenticatedDataWithRecipientInfoIndex(true, encoding, message); 1018 System.out.println("Parse for the several recipients using their RecipientIdentifier."); 1019 parseAuthenticatedDataWithRecipientIdentifier(true, encoding, message); 1020 System.out.println("Parse for the several recipients using their certificate."); 1021 parseAuthenticatedDataWithRecipientCertificate(true, encoding, message); 1022 1023 } 1024 System.out.println("==============================="); 1025 1026 1027 // 1028 // test CMS AuthenticatedData 1029 // 1030 for (int i = 0; i < modes.length; i++) { 1031 int mode = modes[i]; 1032 if (mode == AuthenticatedDataStream.IMPLICIT) { 1033 System.out.print("Implicit "); 1034 } else { 1035 System.out.print("Explicit "); 1036 } 1037 // with authenticated attributes 1038 System.out.println("AuthenticatedData demo with authenticated attributes [create]:\n"); 1039 encoding = createAuthenticatedData(message, 1040 macAlgorithm, 1041 macKeyLength, 1042 digestAlgorithm, 1043 mode); 1044 // transmit data 1045 System.out.println("\nAuthenticatedDataStream demo with authenticated attributes [parse]:\n"); 1046 System.out.println("Parse for the several recipients using their index into the recipientInfos field."); 1047 parseAuthenticatedDataWithRecipientInfoIndex(false, encoding, message); 1048 System.out.println("Parse for the several recipients using their RecipientIdentifier."); 1049 parseAuthenticatedDataWithRecipientIdentifier(false, encoding, message); 1050 System.out.println("Parse for the several recipients using their certificate."); 1051 parseAuthenticatedDataWithRecipientCertificate(false, encoding, message); 1052 1053 // without authenticated attributes 1054 System.out.println("AuthenticatedData demo without authenticated attributes [create]:\n"); 1055 encoding = createAuthenticatedData(message, 1056 macAlgorithm, 1057 macKeyLength, 1058 null, 1059 mode); 1060 // transmit data 1061 System.out.println("\nAuthenticatedData demo without authenticated attributes [parse]:\n"); 1062 System.out.println("Parse for the several recipients using their index into the recipientInfos field."); 1063 parseAuthenticatedDataWithRecipientInfoIndex(false, encoding, message); 1064 System.out.println("Parse for the several recipients using their RecipientIdentifier."); 1065 parseAuthenticatedDataWithRecipientIdentifier(false, encoding, message); 1066 System.out.println("Parse for the several recipients using their certificate."); 1067 parseAuthenticatedDataWithRecipientCertificate(false, encoding, message); 1068 1069 } 1070 1071 1072 1073 } catch (Exception ex) { 1074 ex.printStackTrace(); 1075 throw new RuntimeException(ex.toString()); 1076 } 1077 } 1078 1079 /** 1080 * Main method. 1081 * 1082 * @throws IOException 1083 * if an I/O error occurs when reading required keys 1084 * and certificates from the keystore file 1085 */ 1086 public static void main(String argv[]) throws Exception { 1087 1088 DemoUtil.initDemos(); 1089 ECCDemoUtil.installIaikEccProvider(); 1090 (new ECDHAuthenticatedDataDemo()).start(); 1091 System.out.println("\nReady!"); 1092 System.in.read(); 1093 } 1094 }