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/EdDHAuthEnvelopedDataDemo.java 5 12.02.25 17:58 Dbratko $ 029// $Revision: 5 $ 030// 031 032 033package demo.cms.ecc; 034 035import iaik.asn1.CodingException; 036import iaik.asn1.ObjectID; 037import iaik.asn1.structures.AlgorithmID; 038import iaik.asn1.structures.Attribute; 039import iaik.cms.AuthEnvelopedData; 040import iaik.cms.AuthEnvelopedDataOutputStream; 041import iaik.cms.AuthEnvelopedDataStream; 042import iaik.cms.CMSAlgorithmID; 043import iaik.cms.CMSException; 044import iaik.cms.CertificateIdentifier; 045import iaik.cms.ContentInfo; 046import iaik.cms.ContentInfoOutputStream; 047import iaik.cms.ContentInfoStream; 048import iaik.cms.EncryptedContentInfo; 049import iaik.cms.EncryptedContentInfoStream; 050import iaik.cms.IssuerAndSerialNumber; 051import iaik.cms.KeyAgreeRecipientInfo; 052import iaik.cms.KeyIdentifier; 053import iaik.cms.RecipientInfo; 054import iaik.cms.RecipientKeyIdentifier; 055import iaik.cms.attributes.CMSContentType; 056import iaik.security.random.SecRandom; 057import iaik.utils.Util; 058import iaik.x509.X509Certificate; 059 060import java.io.ByteArrayInputStream; 061import java.io.ByteArrayOutputStream; 062import java.io.IOException; 063import java.io.InputStream; 064import java.security.InvalidKeyException; 065import java.security.Key; 066import java.security.NoSuchAlgorithmException; 067import java.security.PrivateKey; 068import java.security.SecureRandom; 069 070import javax.crypto.SecretKey; 071 072import demo.DemoUtil; 073import demo.cms.ecc.keystore.CMSEccKeyStore; 074 075 076/** 077 * Demonstrates the usage of class {@link iaik.cms.AuthEnvelopedDataStream}, 078 * {@link iaik.cms.AuthEnvelopedData} and {@link iaik.cms.AuthEnvelopedDataOutputStream} 079 * for authenticated encrypting data with the CMS content type AuthEnvelopedData using the 080 * Elliptic Curve Diffie-Hellman (ECDH) key agreement algorithm with curve25519 and 081 * curve448 according to <a href = "http://www.ietf.org/rfc/rfc5083.txt" target="_blank">RFC 5083</a> 082 * and <a href = "http://www.ietf.org/rfc/rfc8418.txt" target="_blank">RFC 8418</a>. 083 * <p> 084 * This demo uses the AES-CCM and AES-GCM authenticated encryption algorithms 085 * as specified by <a href = "http://www.ietf.org/rfc/rfc5084.txt" target="_blank">RFC 5084</a>. 086 * The demo creates an AuthEnvelopedData object and subsequently shows several 087 * ways that may be used for decrypting the content and verifying the message 088 * authentication code for some particular recipient. 089 * <br> 090 * Since AES-CCM and AES-GCM are not implemented by IAIK-JCE versions prior 3.17, this demo 091 * at least may require IAIK-JCE 3.17 as cryptographic service provider. 092 * <p> 093 * Any keys/certificates required for this demo are read from a keystore 094 * file "cmsecc.keystore" located in your current working directory. If 095 * the keystore file does not exist you can create it by running the 096 * {@link demo.cms.ecc.keystore.SetupCMSEccKeyStore SetupCMSEccKeyStore} 097 * program. 098 * <p> 099 * Additionally to <code>iaik_cms.jar</code> you also must have 100 * <code>iaik_jce_(full).jar</code> (IAIK-JCE, <a href = 101 * "https://sic.tech/products/core-crypto-toolkits/jca-jce/" target="_blank"> 102 * https://sic.tech/products/core-crypto-toolkits/jca-jce/</a>), 103 * and <code>iaik_eccelarate.jar</code> (IAIK-ECCelerate<sup><small>TM</small></sup>, <a href = 104 * "https://sic.tech/products/core-crypto-toolkits/eccelerate/" target="_blank"> 105 * https://sic.tech/products/core-crypto-toolkits/eccelerate/</a>) 106 * in your classpath. 107 * 108 * @see iaik.cms.AuthEnvelopedDataStream 109 * @see iaik.cms.AuthEnvelopedData 110 * @see iaik.cms.AuthEnvelopedDataOutputStream 111 * @see iaik.cms.RecipientInfo 112 * @see iaik.cms.KeyAgreeRecipientInfo 113 */ 114public class EdDHAuthEnvelopedDataDemo { 115 116 //certificate of x25519 user 117 X509Certificate x25519User; 118 // private key of x25519 user 119 PrivateKey x25519User_pk; 120 // certificate of x448 user 121 X509Certificate x448User; 122 // private key of x448 user 123 PrivateKey x448User_pk; 124 125 // secure random number generator 126 SecureRandom random; 127 128 /** 129 * Setup the demo certificate chains. 130 * 131 * Keys and certificates are retrieved from the demo KeyStore ("cms.keystore") 132 * file which has to be located in your current working directory and may be 133 * created by running {@link demo.keystore.SetupCMSKeyStore 134 * SetupCMSKeyStore}. 135 * 136 * @throws IOException if an file read error occurs 137 */ 138 public EdDHAuthEnvelopedDataDemo() throws IOException { 139 140 System.out.println(); 141 System.out.println("***************************************************************************************************"); 142 System.out.println("* EdDHAuthEnvelopedDataDemo *"); 143 System.out.println("* (shows the usage of the CMS AuthEnvelopedData type implementation with curve25519 and curve448) *"); 144 System.out.println("***************************************************************************************************"); 145 System.out.println(); 146 147 // get keys and certificate from the demo keystore 148 x25519User = CMSEccKeyStore.getCertificateChain(CMSEccKeyStore.ECDH, CMSEccKeyStore.SZ_X25519)[0]; 149 x25519User_pk = CMSEccKeyStore.getPrivateKey(CMSEccKeyStore.ECDH, CMSEccKeyStore.SZ_X25519); 150 x448User = CMSEccKeyStore.getCertificateChain(CMSEccKeyStore.ECDH, CMSEccKeyStore.SZ_X448)[0]; 151 x448User_pk = CMSEccKeyStore.getPrivateKey(CMSEccKeyStore.ECDH, CMSEccKeyStore.SZ_X448); 152 random = SecRandom.getDefault(); 153 154 } 155 156 157 /** 158 * Creates a CMS <code>AuthEnvelopedDataStream</code> message. 159 * 160 * @param message the message to be authenticated-enveloped, as byte representation 161 * @param keyEA the key encryption (key agreement) algorithm used for creating 162 * a shared key encryption key for encrypting the secret content 163 * encryption key with it 164 * @param keyWrapAlg the key wrap algorithm to be used for wrapping (encrypting) 165 * the content encryption key 166 * @param kekLength the length of the key encryption key to be created for 167 * encrypting the content encryption key with it 168 * @param contentAuthEncAlg the id of the content-authenticated encryption algorithm 169 * 170 * @return the BER encoding of the <code>AuthEnvelopedData</code> object just created 171 * 172 * @throws CMSException if the <code>AuthEnvelopedData</code> object cannot 173 * be created 174 * @throws IOException if an I/O error occurs 175 */ 176 public byte[] createAuthEnvelopedDataStream(byte[] message, 177 AlgorithmID keyEA, 178 AlgorithmID keyWrapAlg, 179 int kekLength, 180 AlgorithmID contentAuthEncAlg) 181 throws CMSException, IOException { 182 183 // we are testing the stream interface 184 ByteArrayInputStream is = new ByteArrayInputStream(message); 185 // create a new AuthEnvelopedData object 186 AuthEnvelopedDataStream authEnvelopedData = new AuthEnvelopedDataStream(is, contentAuthEncAlg); 187 188 if (contentAuthEncAlg.equals(AlgorithmID.aes128_CCM) || 189 contentAuthEncAlg.equals(AlgorithmID.aes192_CCM) || 190 contentAuthEncAlg.equals(AlgorithmID.aes256_CCM)) { 191 // for aes-ccm we need to know the data input length in advance 192 authEnvelopedData.setInputLength(message.length); 193 } 194 195 // create some authenticated attributes 196 try { 197 Attribute[] attributes = { new Attribute(new CMSContentType(ObjectID.cms_data)) }; 198 authEnvelopedData.setAuthenticatedAttributes(attributes); 199 } catch (Exception ex) { 200 throw new CMSException("Error creating attribute: " + ex.toString()); 201 } 202 203 // create the recipient infos 204 RecipientInfo[] recipients = createRecipients(keyEA, keyWrapAlg, kekLength); 205 // specify the recipients of the encrypted message 206 authEnvelopedData.setRecipientInfos(recipients); 207 208 // return the AuthEnvelopedDate as BER encoded byte array with block size 16 209 // (just for testing; in real application we will use a proper blocksize, 210 // e.g. 2048, 4096,..) 211 authEnvelopedData.setBlockSize(16); 212 // wrap into ContentInfo 213 ContentInfoStream contentInfo = new ContentInfoStream(authEnvelopedData); 214 ByteArrayOutputStream os = new ByteArrayOutputStream(); 215 contentInfo.writeTo(os); 216 return os.toByteArray(); 217 } 218 219 /** 220 * Creates a CMS <code>AuthEnvelopedData</code> message using the 221 * {@link iaik.cms.AuthEnvelopedDataOutputStream AuthEnvelopedDataOutputStream} 222 * class. 223 * 224 * @param message the message to be authenticated-enveloped, as byte representation 225 * @param keyEA the key encryption (key agreement) algorithm used for creating 226 * a shared key encryption key for encrypting the secret content 227 * encryption key with it 228 * @param keyWrapAlg the key wrap algorithm to be used for wrapping (encrypting) 229 * the content encryption key 230 * @param kekLength the length of the key encryption key to be created for 231 * encrypting the content encryption key with it 232 * @param contentAuthEncAlg the id of the content-authenticated encryption algorithm 233 * 234 * @return the BER encoding of the <code>AuthEnvelopedData</code> object just created 235 * 236 * @throws CMSException if the <code>AuthEnvelopedData</code> object cannot 237 * be created 238 * @throws IOException if an I/O error occurs 239 */ 240 public byte[] createAuthEnvelopedDataOutputStream(byte[] message, 241 AlgorithmID keyEA, 242 AlgorithmID keyWrapAlg, 243 int kekLength, 244 AlgorithmID contentAuthEncAlg) 245 throws CMSException, IOException { 246 247 // a stream from which to read the data to be encrypted 248 ByteArrayInputStream is = new ByteArrayInputStream(message); 249 250 // the stream to which to write the AuthEnvelopedData 251 ByteArrayOutputStream resultStream = new ByteArrayOutputStream(); 252 253 // wrap AuthEnvelopedData into a ContentInfo 254 ContentInfoOutputStream contentInfoStream = 255 new ContentInfoOutputStream(ObjectID.cms_authEnvelopedData, resultStream); 256 257 // create a new AuthEnvelopedData object 258 AuthEnvelopedDataOutputStream authEnvelopedData = 259 new AuthEnvelopedDataOutputStream(contentInfoStream, contentAuthEncAlg); 260 261 if (contentAuthEncAlg.equals(AlgorithmID.aes128_CCM) || 262 contentAuthEncAlg.equals(AlgorithmID.aes192_CCM) || 263 contentAuthEncAlg.equals(AlgorithmID.aes256_CCM)) { 264 // for aes-ccm we need to know the data input length in advance 265 authEnvelopedData.setInputLength(message.length); 266 } 267 268 // create some authenticated attributes 269 try { 270 Attribute[] attributes = { new Attribute(new CMSContentType(ObjectID.cms_data)) }; 271 authEnvelopedData.setAuthenticatedAttributes(attributes); 272 } catch (Exception ex) { 273 throw new CMSException("Error creating attribute: " + ex.toString()); 274 } 275 276 // create the recipient infos 277 RecipientInfo[] recipients = createRecipients(keyEA, keyWrapAlg, kekLength); 278 // specify the recipients of the encrypted message 279 authEnvelopedData.setRecipientInfos(recipients); 280 281 int blockSize = 16; // in real world we would use a block size like 2048 282 // write in the data to be encrypted 283 byte[] buffer = new byte[blockSize]; 284 int bytesRead; 285 while ((bytesRead = is.read(buffer)) != -1) { 286 authEnvelopedData.write(buffer, 0, bytesRead); 287 } 288 289 // closing the stream finishes encryption and closes the underlying stream 290 authEnvelopedData.close(); 291 return resultStream.toByteArray(); 292 } 293 294 295 /** 296 * Decrypts the encrypted content of the given <code>AuthEnvelopedData</code> object for 297 * the recipient identified by its index into the recipientInfos field and verifies 298 * the message authentication code. 299 * <p> 300 * This way of decrypting the content may be used for any type of RecipientInfo 301 * (KeyTransRecipientInfo, KeyAgreeRecipientInfo, KEKRecipientInfo), but requires to 302 * know at what index of the recipientInfo field the RecipientInfo for the 303 * particular recipient in mind can be found. If the recipient in mind uses 304 * a RecipientInfo of type KeyAgreeRecipientInfo some processing overhead may 305 * take place because a KeyAgreeRecipientInfo may contain encrypted content-encryption 306 * keys for more than only one recipient; since the recipientInfoIndex only 307 * specifies the RecipientInfo but not the encrypted content encryption key 308 * -- if there are more than only one -- repeated decryption runs may be 309 * required as long as the decryption process completes successfully. 310 * 311 * @param encoding the <code>AuthEnvelopedData</code> object as DER encoded byte array 312 * @param key the key to decrypt the message 313 * @param recipientInfoIndex the index into the <code>RecipientInfo</code> array 314 * to which the specified key belongs 315 * 316 * @return the recovered message, as byte array 317 * @throws CMSException if the message cannot be recovered or MAC verification fails 318 * @throws IOException if a stream read/write error occurs 319 */ 320 public byte[] getAuthEnvelopedDataStream(byte[] encoding, Key key, int recipientInfoIndex) 321 throws CMSException, IOException { 322 323 // create the AuthEnvelopedData object from a BER encoded byte array 324 ByteArrayInputStream is = new ByteArrayInputStream(encoding); 325 AuthEnvelopedDataStream authEnvelopedData = new AuthEnvelopedDataStream(is); 326 327 System.out.println("Information about the authenticated encrypted data:"); 328 EncryptedContentInfoStream eci = authEnvelopedData.getEncryptedContentInfo(); 329 System.out.println("Content type: "+eci.getContentType().getName()); 330 System.out.println("Content encryption algorithm: "+eci.getContentEncryptionAlgorithm().getName()); 331 332 System.out.println("\nThis message can be decrypted by the owners of the following certificates:"); 333 RecipientInfo[] recipients = authEnvelopedData.getRecipientInfos(); 334 335 // for demonstration purposes we only look one time for all recipients included: 336 if (recipientInfoIndex == 0) { 337 int k = 0; 338 for (int i=0; i<recipients.length; i++) { 339 KeyIdentifier[] recipientIDs = recipients[i].getRecipientIdentifiers(); 340 for (int j = 0; j < recipientIDs.length; j++) { 341 System.out.println("Recipient "+(++k)+":"); 342 System.out.println(recipientIDs[j]); 343 } 344 } 345 } 346 // decrypt the message for the first recipient and verify mac 347 try { 348 authEnvelopedData.setupCipher(key, recipientInfoIndex); 349 InputStream decrypted = authEnvelopedData.getInputStream(); 350 ByteArrayOutputStream os = new ByteArrayOutputStream(); 351 Util.copyStream(decrypted, os, null); 352 byte[] content = os.toByteArray(); 353 354 // get authenticated attributes 355 Attribute contentTypeAttribute = authEnvelopedData.getAuthenticatedAttribute(ObjectID.contentType); 356 if (contentTypeAttribute != null) { 357 CMSContentType contentType = (CMSContentType)contentTypeAttribute.getAttributeValue(); 358 System.out.println("Authenticated content type attribute included: " + contentType.get().getName()); 359 } 360 361 return content; 362 } catch (InvalidKeyException ex) { 363 throw new CMSException("Private key error: "+ex.toString()); 364 } catch (NoSuchAlgorithmException ex) { 365 throw new CMSException("Content encryption algorithm not implemented: "+ex.toString()); 366 } catch (CodingException ex) { 367 throw new CMSException("Error reading authenticated attributes: "+ex.toString()); 368 } 369 } 370 371 /** 372 * Decrypts the encrypted content of the given <code>EnvelopedData</code> object for 373 * the recipient identified by recipient identifier and verifies the message 374 * authentication code. 375 * <p> 376 * This way of decrypting the content may be used for any type of RecipientInfo 377 * (KeyTransRecipientInfo, KeyAgreeRecipientInfo, KEKRecipientInfo). The 378 * recipient in mind is identified by its recipient identifier. 379 * 380 * @param encoding the <code>AuthEnvelopedData</code> object as BER encoded byte array 381 * @param key the key to decrypt the message 382 * @param recipientID the recipient identifier uniquely identifying the key of the 383 * recipient 384 * 385 * @return the recovered message, as byte array 386 * @throws CMSException if the message cannot be recovered 387 * @throws IOException if a stream read/write error occurs 388 */ 389 public byte[] getAuthEnvelopedDataStream(byte[] encoding, Key key, KeyIdentifier recipientID) 390 throws CMSException, IOException { 391 392 // create the AuthEnvelopedData object from a DER encoded byte array 393 ByteArrayInputStream is = new ByteArrayInputStream(encoding); 394 AuthEnvelopedDataStream authEnvelopedData = new AuthEnvelopedDataStream(is); 395 396 System.out.println("Information about the encrypted data:"); 397 EncryptedContentInfoStream eci = authEnvelopedData.getEncryptedContentInfo(); 398 System.out.println("Content type: "+eci.getContentType().getName()); 399 System.out.println("Content encryption algorithm: "+eci.getContentEncryptionAlgorithm().getName()); 400 401 // get the right RecipientInfo 402 System.out.println("\nSearch for RecipientInfo:"); 403 RecipientInfo recipient = authEnvelopedData.getRecipientInfo(recipientID); 404 if (recipient != null) { 405 System.out.println("RecipientInfo: " + recipient); 406 } else { 407 throw new CMSException("No recipient with ID: " + recipientID); 408 } 409 // decrypt the content encryption key and the content; verify mac 410 try { 411 System.out.println("Decrypt encrypted content encryption key..."); 412 SecretKey cek = recipient.decryptKey(key, recipientID); 413 System.out.println("Decrypt content with decrypted content encryption key..."); 414 authEnvelopedData.setupCipher(cek); 415 InputStream decrypted = authEnvelopedData.getInputStream(); 416 ByteArrayOutputStream os = new ByteArrayOutputStream(); 417 Util.copyStream(decrypted, os, null); 418 byte[] content = os.toByteArray(); 419 420 // get authenticated attributes 421 Attribute contentTypeAttribute = authEnvelopedData.getAuthenticatedAttribute(ObjectID.contentType); 422 if (contentTypeAttribute != null) { 423 CMSContentType contentType = (CMSContentType)contentTypeAttribute.getAttributeValue(); 424 System.out.println("Authenticated content type attribute included: " + contentType.get().getName()); 425 } 426 427 return content; 428 } catch (InvalidKeyException ex) { 429 throw new CMSException("Private key error: "+ex.toString()); 430 } catch (NoSuchAlgorithmException ex) { 431 throw new CMSException("Content encryption algorithm not implemented: "+ex.toString()); 432 } catch (CodingException ex) { 433 throw new CMSException("Error reading authenticated attributes: "+ex.toString()); 434 } 435 } 436 437 /** 438 * Decrypts the encrypted content of the given <code>AuthEnvelopedData</code> object for 439 * the recipient identified by its recipient certificate and verifies the message 440 * authentication code. 441 * 442 * @param encoding the <code>AuthEnvelopedData</code> object as BER encoded byte array 443 * @param key the key to decrypt the message 444 * @param recipientCert the certificate of the recipient 445 * 446 * @return the recovered message, as byte array 447 * @throws CMSException if the message cannot be recovered 448 * @throws IOException if a stream read/write error occurs 449 */ 450 public byte[] getAuthEnvelopedDataStream(byte[] encoding, Key key, X509Certificate recipientCert) 451 throws CMSException, IOException { 452 453 // create the AuthEnvelopedData object from a BER encoded byte array 454 ByteArrayInputStream is = new ByteArrayInputStream(encoding); 455 AuthEnvelopedDataStream authEnvelopedData = new AuthEnvelopedDataStream(is); 456 457 System.out.println("Information about the encrypted data:"); 458 EncryptedContentInfoStream eci = (EncryptedContentInfoStream)authEnvelopedData.getEncryptedContentInfo(); 459 System.out.println("Content type: "+eci.getContentType().getName()); 460 System.out.println("Content encryption algorithm: "+eci.getContentEncryptionAlgorithm().getName()); 461 462 // decrypt the content encryption key and the content; verify mac 463 try { 464 System.out.println("Decrypt the content..."); 465 authEnvelopedData.setupCipher(key, recipientCert); 466 InputStream decrypted = authEnvelopedData.getInputStream(); 467 ByteArrayOutputStream os = new ByteArrayOutputStream(); 468 Util.copyStream(decrypted, os, null); 469 byte[] content = os.toByteArray(); 470 471 // get authenticated attributes 472 Attribute contentTypeAttribute = authEnvelopedData.getAuthenticatedAttribute(ObjectID.contentType); 473 if (contentTypeAttribute != null) { 474 CMSContentType contentType = (CMSContentType)contentTypeAttribute.getAttributeValue(); 475 System.out.println("Authenticated content type attribute included: " + contentType.get().getName()); 476 } 477 478 return content; 479 } catch (InvalidKeyException ex) { 480 throw new CMSException("Private key error: "+ex.toString()); 481 } catch (NoSuchAlgorithmException ex) { 482 throw new CMSException("Content encryption algorithm not implemented: "+ex.toString()); 483 } catch (CodingException ex) { 484 throw new CMSException("Error reading authenticated attributes: "+ex.toString()); 485 } 486 } 487 488 489 // non stream 490 491 /** 492 * Creates a CMS <code>AuthEnvelopedData</code> message. 493 * 494 * @param message the message to be enveloped, as byte representation 495 * @param keyEA the key encryption (key agreement) algorithm used for creating 496 * a shared key encryption key for encrypting the secret content 497 * encryption key with it 498 * @param keyWrapAlg the key wrap algorithm to be used for wrapping (encrypting) 499 * the content encryption key 500 * @param kekLength the length of the key encryption key to be created for 501 * encrypting the content encryption key with it 502 * @param contentAuthEncAlg the id of the content-authenticated encryption algorithm 503 * 504 * 505 * @return the encoded <code>AuthEnvelopedData</code>, as byte array 506 * 507 * @throws CMSException if the <code>AuthEnvelopedData</code> object cannot 508 * be created 509 */ 510 public byte[] createAuthEnvelopedData(byte[] message, AlgorithmID keyEA, AlgorithmID keyWrapAlg, 511 int kekLength, AlgorithmID contentAuthEncAlg) 512 throws CMSException { 513 514 AuthEnvelopedData authEnvelopedData; 515 516 // create a new AuthEnvelopedData object 517 authEnvelopedData = new AuthEnvelopedData(message, contentAuthEncAlg); 518 519 // create some authenticated attributes 520 try { 521 Attribute[] attributes = { new Attribute(new CMSContentType(ObjectID.cms_data)) }; 522 authEnvelopedData.setAuthenticatedAttributes(attributes); 523 } catch (Exception ex) { 524 throw new CMSException("Error creating attribute: " + ex.toString()); 525 } 526 527 // set the RecipientInfos 528 RecipientInfo[] recipients = createRecipients(keyEA, keyWrapAlg, kekLength); 529 authEnvelopedData.setRecipientInfos(recipients); 530 531 // wrap into ContentInfo 532 ContentInfo contentInfo = new ContentInfo(authEnvelopedData); 533 // return encoded EnvelopedData 534 return contentInfo.getEncoded(); 535 } 536 537 538 /** 539 * Decrypts the encrypted content of the given <code>AuthEnvelopedData</code> object for 540 * the recipient identified by its index into the recipientInfos field and verifies 541 * the message authentication code. 542 * <p> 543 * This way of decrypting the content may be used for any type of RecipientInfo 544 * (KeyTransRecipientInfo, KeyAgreeRecipientInfo, KEKRecipientInfo), but requires to 545 * know at what index of the recipientInfo field the RecipientInfo for the 546 * particular recipient in mind can be found. If the recipient in mind uses 547 * a RecipientInfo of type KeyAgreeRecipientInfo some processing overhead may 548 * take place because a KeyAgreeRecipientInfo may contain encrypted content-encryption 549 * keys for more than only one recipient; since the recipientInfoIndex only 550 * specifies the RecipientInfo but not the encrypted content encryption key 551 * -- if there are more than only one -- repeated decryption runs may be 552 * required as long as the decryption process completes successfully. 553 * 554 * @param enc the encoded <code>AuthEnvelopedData</code> 555 * 556 * @param key the key to decrypt the message 557 * 558 * @param recipientInfoIndex the index into the <code>RecipientInfo</code> array 559 * to which the specified key belongs 560 * 561 * @return the recovered message, as byte array 562 * 563 * @throws CMSException if the message cannot be recovered 564 * @throws IOException if an I/O error occurs 565 */ 566 public byte[] getAuthEnvelopedData(byte[] enc, Key key, int recipientInfoIndex) 567 throws CMSException, IOException { 568 ByteArrayInputStream bais = new ByteArrayInputStream(enc); 569 AuthEnvelopedData authEnvelopedData = new AuthEnvelopedData(bais); 570 571 System.out.println("Information about the encrypted data:"); 572 EncryptedContentInfo eci = (EncryptedContentInfo)authEnvelopedData.getEncryptedContentInfo(); 573 System.out.println("Content type: "+eci.getContentType().getName()); 574 System.out.println("Content encryption algorithm: "+eci.getContentEncryptionAlgorithm().getName()); 575 576 System.out.println("\nThis message can be decrypted by the owners of the following certificates:"); 577 RecipientInfo[] recipients = authEnvelopedData.getRecipientInfos(); 578 579 // for demonstration purposes we only look one time for all recipients included: 580 if (recipientInfoIndex == 0) { 581 int k = 0; 582 for (int i=0; i<recipients.length; i++) { 583 KeyIdentifier[] recipientIDs = recipients[i].getRecipientIdentifiers(); 584 for (int j = 0; j < recipientIDs.length; j++) { 585 System.out.println("Recipient "+(++k)+":"); 586 System.out.println(recipientIDs[j]); 587 } 588 } 589 } 590 591 // decrypt the message and verify the mac 592 try { 593 authEnvelopedData.setupCipher(key, recipientInfoIndex); 594 byte[] content = authEnvelopedData.getContent(); 595 596 // get authenticated attributes 597 Attribute contentTypeAttribute = authEnvelopedData.getAuthenticatedAttribute(ObjectID.contentType); 598 if (contentTypeAttribute != null) { 599 CMSContentType contentType = (CMSContentType)contentTypeAttribute.getAttributeValue(); 600 System.out.println("Authenticated content type attribute included: " + contentType.get().getName()); 601 } 602 603 return content; 604 } catch (InvalidKeyException ex) { 605 throw new CMSException("Private key error: "+ex.toString()); 606 } catch (NoSuchAlgorithmException ex) { 607 throw new CMSException("Content encryption algorithm not implemented: "+ex.toString()); 608 } catch (CodingException ex) { 609 throw new CMSException("Error reading authenticated attributes: "+ex.toString()); 610 } 611 } 612 613 /** 614 * Decrypts the encrypted content of the given <code>AuthEnvelopedData</code> object for 615 * the recipient identified by recipient identifier. 616 * <p> 617 * 618 * @param enc the BER encoded <code>AuthEnvelopedData</code> ASN.1 object 619 * @param key the key to decrypt the message 620 * @param recipientID the recipient identifier uniquely identifying the key of the 621 * recipient 622 * 623 * @return the recovered message, as byte array 624 * @throws CMSException if the message cannot be recovered 625 * @throws IOException if an I/O error occurs 626 */ 627 public byte[] getAuthEnvelopedData(byte[] enc, Key key, KeyIdentifier recipientID) 628 throws CMSException, IOException { 629 ByteArrayInputStream bais = new ByteArrayInputStream(enc); 630 AuthEnvelopedData authEnvelopedData = new AuthEnvelopedData(bais); 631 632 System.out.println("Information about the encrypted data:"); 633 EncryptedContentInfo eci = (EncryptedContentInfo)authEnvelopedData.getEncryptedContentInfo(); 634 System.out.println("Content type: "+eci.getContentType().getName()); 635 System.out.println("Content encryption algorithm: "+eci.getContentEncryptionAlgorithm().getName()); 636 637 // get the right RecipientInfo 638 System.out.println("\nSearch for RecipientInfo:"); 639 RecipientInfo recipient = authEnvelopedData.getRecipientInfo(recipientID); 640 if (recipient != null) { 641 System.out.println("RecipientInfo: " + recipient); 642 } else { 643 throw new CMSException("No recipient with ID: " + recipientID); 644 } 645 // decrypt the content encryption key and the content 646 try { 647 System.out.println("Decrypt encrypted content encryption key..."); 648 SecretKey cek = recipient.decryptKey(key, recipientID); 649 System.out.println("Decrypt content with decrypted content encryption key..."); 650 // decrypt content and verify mac 651 authEnvelopedData.setupCipher(cek); 652 byte[] content = authEnvelopedData.getContent(); 653 654 // get authenticated attributes 655 Attribute contentTypeAttribute = authEnvelopedData.getAuthenticatedAttribute(ObjectID.contentType); 656 if (contentTypeAttribute != null) { 657 CMSContentType contentType = (CMSContentType)contentTypeAttribute.getAttributeValue(); 658 System.out.println("Authenticated content type attribute included: " + contentType.get().getName()); 659 } 660 661 return content; 662 } catch (InvalidKeyException ex) { 663 throw new CMSException("Private key error: "+ex.toString()); 664 } catch (NoSuchAlgorithmException ex) { 665 throw new CMSException("Content encryption algorithm not implemented: "+ex.toString()); 666 } catch (CodingException ex) { 667 throw new CMSException("Error reading authenticated attributes: "+ex.toString()); 668 } 669 } 670 671 /** 672 * Decrypts the encrypted content of the given <code>AuthEnvelopedData</code> object for 673 * the recipient identified by its recipient certificate or keyID. 674 * 675 * @param enc the BER encoded <code>AuthEnvelopedData</code> ASN.1 object 676 * @param key the key to decrypt the message 677 * @param recipientCert the certificate of the recipient 678 * 679 * @return the recovered message, as byte array 680 * @throws CMSException if the message cannot be recovered 681 */ 682 public byte[] getAuthEnvelopedData(byte[] enc, Key key, X509Certificate recipientCert) 683 throws CMSException, IOException { 684 ByteArrayInputStream bais = new ByteArrayInputStream(enc); 685 AuthEnvelopedData authEnvelopedData = new AuthEnvelopedData(bais); 686 687 System.out.println("Information about the encrypted data:"); 688 EncryptedContentInfo eci = (EncryptedContentInfo)authEnvelopedData.getEncryptedContentInfo(); 689 System.out.println("Content type: "+eci.getContentType().getName()); 690 System.out.println("Content encryption algorithm: "+eci.getContentEncryptionAlgorithm().getName()); 691 692 // decrypt the content encryption key and the content 693 try { 694 System.out.println("Decrypt the content and verify mac..."); 695 // decrypt content and verify mac 696 authEnvelopedData.setupCipher(key, recipientCert); 697 698 byte[] content = authEnvelopedData.getContent(); 699 700 // get authenticated attributes 701 Attribute contentTypeAttribute = authEnvelopedData.getAuthenticatedAttribute(ObjectID.contentType); 702 if (contentTypeAttribute != null) { 703 CMSContentType contentType = (CMSContentType)contentTypeAttribute.getAttributeValue(); 704 System.out.println("Authenticated content type attribute included: " + contentType.get().getName()); 705 } 706 707 return content; 708 } catch (InvalidKeyException ex) { 709 throw new CMSException("Private key error: "+ex.toString()); 710 } catch (NoSuchAlgorithmException ex) { 711 throw new CMSException("Content encryption algorithm not implemented: "+ex.toString()); 712 } catch (CodingException ex) { 713 throw new CMSException("Error reading authenticated attributes: "+ex.toString()); 714 } 715 716 } 717 718 /** 719 * Creates the RecipientInfos. 720 * 721 * @param keyEA the key encryption (key agreement) algorithm used for creating 722 * a shared key encryption key for encrypting the secret content 723 * encryption key with it 724 * @param keyWrapAlg the key wrap algorithm to be used for wrapping (encrypting) 725 * the content encryption key 726 * @param kekLength the length of the key encryption key to be created for 727 * encrypting the content encryption key with it 728 * 729 * @return the RecipientInfos created, two KeyAgreeRecipientInfos 730 * 731 * @throws CMSException if an error occurs when creating the recipient infos 732 */ 733 public RecipientInfo[] createRecipients(AlgorithmID keyEA, AlgorithmID keyWrapAlg, int kekLength) throws CMSException { 734 // just for demonstration we use two recipients, one having a x25519 key, the other a x448 key 735 RecipientInfo[] recipients = new RecipientInfo[2]; 736 try { 737 recipients[0] = new KeyAgreeRecipientInfo((AlgorithmID)keyEA.clone(), (AlgorithmID)keyWrapAlg.clone(), kekLength); 738 ((KeyAgreeRecipientInfo)recipients[0]).addRecipient(x25519User, CertificateIdentifier.ISSUER_AND_SERIALNUMBER); 739 740 recipients[1] = new KeyAgreeRecipientInfo((AlgorithmID)keyEA.clone(), (AlgorithmID)keyWrapAlg.clone(), kekLength); 741 ((KeyAgreeRecipientInfo)recipients[1]).addRecipient(x448User, CertificateIdentifier.RECIPIENT_KEY_IDENTIFIER); 742 743 } catch (Exception ex) { 744 throw new CMSException("Error adding recipients: " + ex.toString()); 745 } 746 return recipients; 747 } 748 749 /** 750 * Parses an AuthEnvelopedData and decrypts the content for all test recipients 751 * using the index into the recipientInfos field for identifying the recipient. 752 * 753 * @param stream whether to use AuthEnvelopedDataStream or AuthEnvelopedData 754 * @param encodedAuthEnvelopedData the encoded AuthEnvelopedData object 755 * 756 * @throws Exception if some error occurs during decoding/decryption 757 */ 758 public void parseAuthEnvelopedDataWithRecipientInfoIndex(boolean stream, byte[] encodedAuthEnvelopedData) throws Exception { 759 byte[] receivedMessage; 760 if (stream) { 761 // x25519User 762 System.out.println("\nDecrypt for x25519User:"); 763 receivedMessage = getAuthEnvelopedDataStream(encodedAuthEnvelopedData, x25519User_pk, 0); 764 System.out.print("\nDecrypted content: "); 765 System.out.println(new String(receivedMessage)); 766 // x448User 767 System.out.println("\nDecrypt for x448User:"); 768 receivedMessage = getAuthEnvelopedDataStream(encodedAuthEnvelopedData, x448User_pk, 1); 769 System.out.print("\nDecrypted content: "); 770 System.out.println(new String(receivedMessage)); 771 772 } else { 773 // x25519User 774 System.out.println("\nDecrypt for x25519User:"); 775 receivedMessage = getAuthEnvelopedData(encodedAuthEnvelopedData, x25519User_pk, 0); 776 System.out.print("\nDecrypted content: "); 777 System.out.println(new String(receivedMessage)); 778 // x448User 779 System.out.println("\nDecrypt for x448User:"); 780 receivedMessage = getAuthEnvelopedData(encodedAuthEnvelopedData, x448User_pk, 1); 781 System.out.print("\nDecrypted content: "); 782 System.out.println(new String(receivedMessage)); 783 784 } 785 } 786 787 /** 788 * Parses an AuthEnvelopedData and decrypts the content for all test recipients 789 * using their recipient identifiers for identifying the recipient. 790 * 791 * @param stream whether to use AuthEnvelopedDataStream or AuthEnvelopedData 792 * @param encodedAuthEnvelopedData the encoded AuthEnvelopedData object 793 * 794 * @throws Exception if some error occurs during decoding/decryption 795 */ 796 public void parseAuthEnvelopedDataWithRecipientIdentifier(boolean stream, byte[] encodedAuthEnvelopedData) throws Exception { 797 byte[] receivedMessage; 798 if (stream) { 799 // x25519User 800 System.out.println("\nDecrypt for x25519User:"); 801 receivedMessage = getAuthEnvelopedDataStream(encodedAuthEnvelopedData, x25519User_pk, new IssuerAndSerialNumber(x25519User)); 802 System.out.print("\nDecrypted content: "); 803 System.out.println(new String(receivedMessage)); 804 // x448User 805 System.out.println("\nDecrypt for x448User:"); 806 receivedMessage = getAuthEnvelopedDataStream(encodedAuthEnvelopedData, x448User_pk, new RecipientKeyIdentifier(x448User)); 807 System.out.print("\nDecrypted content: "); 808 System.out.println(new String(receivedMessage)); 809 } else { 810 // x25519User 811 System.out.println("\nDecrypt for x25519User:"); 812 receivedMessage = getAuthEnvelopedData(encodedAuthEnvelopedData, x25519User_pk, new IssuerAndSerialNumber(x25519User)); 813 System.out.print("\nDecrypted content: "); 814 System.out.println(new String(receivedMessage)); 815 // x448User 816 System.out.println("\nDecrypt for x448User:"); 817 receivedMessage = getAuthEnvelopedData(encodedAuthEnvelopedData, x448User_pk, new RecipientKeyIdentifier(x448User)); 818 System.out.print("\nDecrypted content: "); 819 System.out.println(new String(receivedMessage)); 820 } 821 } 822 823 /** 824 * Parses an AuthEnvelopedData and decrypts the content for all test recipients 825 * using their recipient certificate for identifying the recipient. 826 * 827 * @param stream whether to use AuthEnvelopedDataStream or AuthEnvelopedData 828 * @param encodedAuthEnvelopedData the encoded AuthEnvelopedData object 829 * 830 * @throws Exception if some error occurs during decoding/decryption 831 */ 832 public void parseAuthEnvelopedDataWithRecipientCert(boolean stream, byte[] encodedAuthEnvelopedData) throws Exception { 833 byte[] receivedMessage; 834 if (stream) { 835 // x25519User 836 System.out.println("\nDecrypt for x25519User:"); 837 receivedMessage = getAuthEnvelopedDataStream(encodedAuthEnvelopedData, x25519User_pk, x25519User); 838 System.out.print("\nDecrypted content: "); 839 System.out.println(new String(receivedMessage)); 840 // x448User 841 System.out.println("\nDecrypt for x448User:"); 842 receivedMessage = getAuthEnvelopedDataStream(encodedAuthEnvelopedData, x448User_pk, x448User); 843 System.out.print("\nDecrypted content: "); 844 System.out.println(new String(receivedMessage)); 845 } else { 846 // x25519User 847 System.out.println("\nDecrypt for x25519User:"); 848 receivedMessage = getAuthEnvelopedData(encodedAuthEnvelopedData, x25519User_pk, x25519User); 849 System.out.print("\nDecrypted content: "); 850 System.out.println(new String(receivedMessage)); 851 // x448User 852 System.out.println("\nDecrypt for x448User:"); 853 receivedMessage = getAuthEnvelopedData(encodedAuthEnvelopedData, x448User_pk, x448User); 854 System.out.print("\nDecrypted content: "); 855 System.out.println(new String(receivedMessage)); 856 } 857 } 858 859 /** 860 * Starts the test. 861 */ 862 public void start() { 863 864 AlgorithmID[] keyEAs = { 865 AlgorithmID.dhSinglePass_stdDH_sha256kdf_scheme, 866 AlgorithmID.dhSinglePass_stdDH_sha384kdf_scheme, 867 AlgorithmID.dhSinglePass_stdDH_hkdf_sha256_scheme, 868 AlgorithmID.dhSinglePass_stdDH_hkdf_sha384_scheme, 869 AlgorithmID.dhSinglePass_stdDH_hkdf_sha512_scheme, 870 }; 871 872 AlgorithmID[] keyWrapAlgs = { 873 CMSAlgorithmID.cms_aes128_wrap, 874 CMSAlgorithmID.cms_aes192_wrap, 875 CMSAlgorithmID.cms_aes256_wrap, 876 }; 877 878 for (int i = 0; i < keyEAs.length; i++) { 879 AlgorithmID[] contentEAs = { AlgorithmID.aes256_GCM, AlgorithmID.aes256_CCM, AlgorithmID.chacha20Poly1305 }; 880 AlgorithmID keyEA = keyEAs[i]; 881 for (int j = 0; j < keyWrapAlgs.length; j++) { 882 AlgorithmID keyWrapAlg = keyWrapAlgs[j]; 883 int kekLength = 256; 884 if (keyWrapAlg.equals(CMSAlgorithmID.cms_aes192_wrap)) { 885 kekLength = 192; 886 contentEAs = new AlgorithmID[] { AlgorithmID.aes192_GCM, AlgorithmID.aes192_CCM }; 887 } else if (keyWrapAlg.equals(CMSAlgorithmID.cms_aes128_wrap)) { 888 kekLength = 128; 889 contentEAs = new AlgorithmID[] { AlgorithmID.aes128_GCM, AlgorithmID.aes128_CCM }; 890 } 891 892 for (int k = 0; k < contentEAs.length; k++) { 893 AlgorithmID contentAuthEncAlg = (AlgorithmID)contentEAs[k].clone(); 894 System.out.println("EdDH AuthEnveloped demo for " + keyEA.getName() + " with " + keyWrapAlg.getName() +" and " + contentAuthEncAlg.getName()); 895 start(keyEA, keyWrapAlg, kekLength, contentAuthEncAlg); 896 } 897 } 898 } 899 } 900 901 /** 902 * Starts the test for the given content-authenticated encryption algorithm. 903 * 904 * @param keyEA the key encryption (key agreement) algorithm used for creating 905 * a shared key encryption key for encrypting the secret content 906 * encryption key with it 907 * @param keyWrapAlg the key wrap algorithm to be used for wrapping (encrypting) 908 * the content encryption key 909 * @param kekLength the length of the key encryption key to be created for 910 * encrypting the content encryption key with it 911 * @param contentAuthEncAlg the id of the content-authenticated encryption algorithm 912 */ 913 public void start(AlgorithmID keyEA, AlgorithmID keyWrapAlg, int kekLength, AlgorithmID contentAuthEncAlg) { 914 // the test message 915 String m = "This is the test message."; 916 System.out.println("Test message: \""+m+"\""); 917 System.out.println(); 918 byte[] message = m.getBytes(); 919 920 try { 921 byte[] encodedAuthEnvelopedData; 922 System.out.println("Stream implementation demos"); 923 System.out.println("==========================="); 924 925 // the stream implementation 926 // 927 // test CMS AuthEnvelopedDataStream 928 // 929 System.out.println("\nCMS AuthEnvelopedDataStream demo [create]:\n"); 930 encodedAuthEnvelopedData = createAuthEnvelopedDataStream(message, keyEA, keyWrapAlg, kekLength, (AlgorithmID)contentAuthEncAlg.clone()); 931 // transmit data 932 System.out.println("\nCMS AuthEnvelopedDataStream demo [parse]:\n"); 933 System.out.println("Decrypt for the several recipients using their index into the recipientInfos field."); 934 parseAuthEnvelopedDataWithRecipientInfoIndex(true, encodedAuthEnvelopedData); 935 System.out.println("Decrypt for the several recipients using their RecipientIdentifier."); 936 parseAuthEnvelopedDataWithRecipientIdentifier(true, encodedAuthEnvelopedData); 937 System.out.println("Decrypt for the several recipients using their certificate."); 938 parseAuthEnvelopedDataWithRecipientCert(true, encodedAuthEnvelopedData); 939 940 941 // the output stream implementation 942 // 943 // test CMS AuthEnvelopedDataOutputStream 944 // 945 System.out.println("\nCMS AuthEnvelopedDataOutputStream demo [create]:\n"); 946 encodedAuthEnvelopedData = createAuthEnvelopedDataOutputStream(message, keyEA, keyWrapAlg, kekLength, (AlgorithmID)contentAuthEncAlg.clone()); 947 // transmit data 948 System.out.println("\nCMS AuthEnvelopedDataStream demo [parse]:\n"); 949 System.out.println("Decrypt for the several recipients using their index into the recipientInfos field."); 950 parseAuthEnvelopedDataWithRecipientInfoIndex(true, encodedAuthEnvelopedData); 951 System.out.println("Decrypt for the several recipients using their RecipientIdentifier."); 952 parseAuthEnvelopedDataWithRecipientIdentifier(true, encodedAuthEnvelopedData); 953 System.out.println("Decrypt for the several recipients using their certificate."); 954 parseAuthEnvelopedDataWithRecipientCert(true, encodedAuthEnvelopedData); 955 956 // the non-stream implementation 957 System.out.println("\nNon-stream implementation demos"); 958 System.out.println("==============================="); 959 960 961 // 962 // test CMS AuthEnvelopedData 963 // 964 System.out.println("\nCMS AuthEnvelopedData demo [create]:\n"); 965 encodedAuthEnvelopedData = createAuthEnvelopedData(message, keyEA, keyWrapAlg, kekLength, (AlgorithmID)contentAuthEncAlg.clone()); 966 // transmit data 967 System.out.println("\nCMS AuthEnvelopedData demo [parse]:\n"); 968 System.out.println("Decrypt for the several recipients using their index into the recipientInfos field."); 969 parseAuthEnvelopedDataWithRecipientInfoIndex(false, encodedAuthEnvelopedData); 970 System.out.println("Decrypt for the several recipients using their RecipientIdentifier."); 971 parseAuthEnvelopedDataWithRecipientIdentifier(false, encodedAuthEnvelopedData); 972 System.out.println("Decrypt for the several recipients using their certificate."); 973 parseAuthEnvelopedDataWithRecipientCert(false, encodedAuthEnvelopedData); 974 975 976 } catch (Exception ex) { 977 ex.printStackTrace(); 978 throw new RuntimeException(ex.toString()); 979 } 980 } 981 982 983 /** 984 * Main method. 985 * 986 * @throws IOException 987 * if an I/O error occurs when reading required keys 988 * and certificates from files 989 */ 990 public static void main(String argv[]) throws Exception { 991 992 DemoUtil.initDemos(); 993 ECCDemoUtil.installIaikEccProvider(); 994 995 (new EdDHAuthEnvelopedDataDemo()).start(); 996 System.out.println("\nReady!"); 997 DemoUtil.waitKey(); 998 } 999 1000}