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