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