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