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}