001    // Copyright (C) 2002 IAIK
002    // https://jce.iaik.tugraz.at
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    // Redistribution and use in source and binary forms, with or without
011    // modification, are permitted provided that the following conditions
012    // are met:
013    // 1. Redistributions of source code must retain the above copyright
014    //    notice, this list of conditions and the following disclaimer.
015    // 2. Redistributions in binary form must reproduce the above copyright
016    //    notice, this list of conditions and the following disclaimer in the
017    //    documentation and/or other materials provided with the distribution.
018    //
019    // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
020    // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
021    // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022    // ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
023    // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
024    // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
025    // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
026    // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
027    // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
028    // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
029    // SUCH DAMAGE.
030    
031    // Copyright (C) 2002 IAIK
032    // https://sic.tech/
033    //
034    // Copyright (C) 2003 - 2025 Stiftung Secure Information and 
035    //                           Communication Technologies SIC
036    // https://sic.tech/
037    //
038    // All rights reserved.
039    //
040    // This source is provided for inspection purposes and recompilation only,
041    // unless specified differently in a contract with IAIK. This source has to
042    // be kept in strict confidence and must not be disclosed to any third party
043    // under any circumstances. Redistribution in source and binary forms, with
044    // or without modification, are <not> permitted in any case!
045    //
046    // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
047    // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
048    // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
049    // ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
050    // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
051    // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
052    // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
053    // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
054    // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
055    // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
056    // SUCH DAMAGE.
057    //
058    // $Header: /IAIK-CMS/current/src/demo/cms/authEnvelopedData/AuthEnvelopedDataOutputStreamDemo.java 10    12.02.25 17:58 Dbratko $
059    // $Revision: 10 $
060    //
061    
062    
063    package demo.cms.authEnvelopedData;
064    
065    import iaik.asn1.CodingException;
066    import iaik.asn1.ObjectID;
067    import iaik.asn1.structures.AlgorithmID;
068    import iaik.asn1.structures.Attribute;
069    import iaik.cms.CMSException;
070    import iaik.cms.ContentInfoOutputStream;
071    import iaik.cms.EncryptedContentInfoStream;
072    import iaik.cms.AuthEnvelopedDataOutputStream;
073    import iaik.cms.AuthEnvelopedDataStream;
074    import iaik.cms.KeyTransRecipientInfo;
075    import iaik.cms.RecipientInfo;
076    import iaik.cms.attributes.CMSContentType;
077    import iaik.security.random.SecRandom;
078    import iaik.utils.CryptoUtils;
079    import iaik.utils.Util;
080    import iaik.x509.X509Certificate;
081    
082    import java.io.ByteArrayInputStream;
083    import java.io.ByteArrayOutputStream;
084    import java.io.IOException;
085    import java.io.InputStream;
086    import java.security.InvalidKeyException;
087    import java.security.NoSuchAlgorithmException;
088    import java.security.PrivateKey;
089    import java.security.SecureRandom;
090    
091    import demo.DemoUtil;
092    import demo.keystore.CMSKeyStore;
093    
094    
095    /**
096     * Demonstrates the usage of class {@link iaik.cms.AuthEnvelopedDataOutputStream} and
097     * for authenticated encrypting data using the CMS type AuthEnvelopedData
098     * according to <a href = "http://www.ietf.org/rfc/rfc5083.txt" target="_blank">RFC 5083</a>.
099     * <p>
100     * This demo uses the AES-CCM and AES-GCM authenticated encryption algorithms
101     * as specified by <a href = "http://www.ietf.org/rfc/rfc5084.txt" target="_blank">RFC 5084</a>
102     * and the ChaCha20-Poly1305 authenticated encryption algorithm
103     * as specified by <a href = "http://www.ietf.org/rfc/rfc8103.txt" target="_blank">RFC 8103</a>.
104     * The demo creates an AuthEnvelopedData object and subsequently shows several
105     * ways that may be used for decrypting the content and verifying the message
106     * authentication code for some particular recipient.
107     * <br>
108     * Since AES-CCM and AES-GCM are not implemented by IAIK-JCE versions prior 3.17, this demo
109     * at least may require IAIK-JCE 3.17 as cryptographic service provider. 
110     * ChaCha20-Poly1305 for CMS requires IAIK-JCE version 5.62 or later.
111     * <p>
112     * Keys and certificates are retrieved from the demo KeyStore ("cms.keystore") 
113     * which has to be located in your current working directory and may be
114     * created by running the {@link demo.keystore.SetupCMSKeyStore
115     * SetupCMSKeyStore} program.
116     * <p>
117     */
118    public class AuthEnvelopedDataOutputStreamDemo {
119    
120       
121      // encryption certificate of user 1
122      X509Certificate user1_crypt;
123      // encryption private key of user 1
124      PrivateKey user1_crypt_pk;
125      // encryption certificate of user 2
126      X509Certificate user2_crypt;
127      // encryption private key of user 2
128      PrivateKey user2_crypt_pk;
129      
130      // secure random number generator
131      SecureRandom random;
132    
133      /**
134       * Setup the demo certificate chains.
135       *
136       * Keys and certificate are retrieved from the demo KeyStore.
137       *
138       * @throws IOException if an file read error occurs
139       */
140      public AuthEnvelopedDataOutputStreamDemo() throws IOException {
141        
142        System.out.println();
143        System.out.println("**********************************************************************************");
144        System.out.println("*                    AuthEnvelopedDataOutputStream demo                          *");
145        System.out.println("*    (shows the usage of the CMS AuthEnvelopedDataOutputStream implementation)   *");
146        System.out.println("**********************************************************************************");
147        System.out.println();
148        
149        // encryption certs
150        user1_crypt = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1)[0];
151        user1_crypt_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1);
152        user2_crypt = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2)[0];
153        user2_crypt_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2);
154    
155        random = SecRandom.getDefault();
156    
157      }
158    
159    
160      /**
161       * Creates a CMS <code>AuthEnvelopedData</code> and wraps it into a ContentInfo.
162       *
163       * @param message the message to be enveloped, as byte representation
164       * @param contentAuthEncAlg the id of the content-authenticated encryption algorithm
165       * 
166       * @return the encoded AuthEnvelopedData object just created, wrapped into a ContentInfo
167       *
168       * @throws CMSException if the <code>AuthEnvelopedData</code> object cannot
169       *                          be created
170       * @throws IOException if an I/O error occurs
171       */
172      public byte[] createAuthEnvelopedData(byte[] message, AlgorithmID contentAuthEncAlg)
173        throws CMSException, IOException {
174        
175        System.out.println("Create AuthEnvelopedData message for : " + contentAuthEncAlg.getName());
176        
177        //  a stream from which to read the data to be encrypted
178        ByteArrayInputStream is = new ByteArrayInputStream(message);
179        
180        // the stream to which to write the AuthEnvelopedData
181        ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
182        AuthEnvelopedDataOutputStream authEnvelopedData;
183    
184        //  wrap AuthEnvelopedData into a ContentInfo 
185        ContentInfoOutputStream contentInfoStream = 
186          new ContentInfoOutputStream(ObjectID.cms_authEnvelopedData, resultStream);
187       
188        // create a new AuthEnvelopedData object 
189        authEnvelopedData = new AuthEnvelopedDataOutputStream(contentInfoStream, 
190                                                              contentAuthEncAlg);
191        
192        
193        if (contentAuthEncAlg.equals(AlgorithmID.aes128_CCM) || 
194            contentAuthEncAlg.equals(AlgorithmID.aes192_CCM) ||
195            contentAuthEncAlg.equals(AlgorithmID.aes256_CCM)) {
196          // for aes-ccm we need to know the data input length in advance
197          authEnvelopedData.setInputLength(message.length);
198        }
199      
200        // create the recipient infos
201        RecipientInfo[] recipients = new RecipientInfo[2];
202        // user1 is the first receiver
203        recipients[0] = new KeyTransRecipientInfo(user1_crypt, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
204        // user2 is the second receiver
205        recipients[1] = new KeyTransRecipientInfo(user2_crypt, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
206    
207        // specify the recipients of the encrypted message
208        authEnvelopedData.setRecipientInfos(recipients);
209        
210        try {
211          // just for demonstration: set some authenticated attribute
212          // content type is data
213          CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
214          Attribute[] attributes = { new Attribute(contentType) };
215          authEnvelopedData.setAuthenticatedAttributes(attributes);
216        } catch (Exception ex) {
217          throw new CMSException("Error creating attribute: " + ex.toString());   
218        }  
219    
220        int blockSize = 16; // in real world we would use a block size like 2048
221        //  write in the data to be encrypted
222        byte[] buffer = new byte[blockSize];
223        int bytesRead;
224        while ((bytesRead = is.read(buffer)) != -1) {
225          authEnvelopedData.write(buffer, 0, bytesRead);
226        }
227        
228        // closing the stream finishes encryption and closes the underlying stream
229        authEnvelopedData.close();
230        return resultStream.toByteArray();
231      }
232    
233      /**
234       * Decrypts the encrypted content of the given AuthEnvelopedData object and
235       * verifies the message authentication code for the specified recipient.
236       *
237       * @param encoding the encoded AuthEnvelopedData object, wrapped in a ContentInfo
238       * @param privateKey the private key to decrypt the message
239       * @param recipientInfoIndex the index into the <code>RecipientInfo</code> array
240       *                           to which the specified private key belongs
241       *
242       * @return the recovered message, as byte array
243       * @throws CMSException if the message cannot be recovered
244       * @throws IOException if an I/O error occurs
245       */
246      public byte[] getAuthEnvelopedDataStream(byte[] encoding, PrivateKey privateKey, int recipientInfoIndex) throws CMSException, IOException {
247    
248        // create the AuthEnvelopedData object from a BER encoded byte array
249        // we are testing the stream interface
250        ByteArrayInputStream is = new ByteArrayInputStream(encoding);
251        
252        AuthEnvelopedDataStream enveloped_data = new AuthEnvelopedDataStream(is);
253    
254        System.out.println("Information about the encrypted data:");
255        EncryptedContentInfoStream eci = (EncryptedContentInfoStream)enveloped_data.getEncryptedContentInfo();
256        System.out.println("Content type: "+eci.getContentType().getName());
257        System.out.println("Content encryption algorithm: "+eci.getContentEncryptionAlgorithm().getName());
258    
259        System.out.println("\nThis message can be decrypted by the owners of the following certificates:");
260        RecipientInfo[] recipients = enveloped_data.getRecipientInfos();
261        for (int i=0; i<recipients.length; i++) {
262          System.out.println("Recipient "+(i+1)+":");
263          System.out.println(recipients[i].getRecipientIdentifiers()[0]);
264        }
265    
266        // decrypt the message
267        try {
268          enveloped_data.setupCipher(privateKey, recipientInfoIndex);
269          InputStream decrypted = enveloped_data.getInputStream();
270          ByteArrayOutputStream os = new ByteArrayOutputStream();
271          Util.copyStream(decrypted, os, null);
272          
273          // get any unprotected attributes:
274          Attribute[] attributes = enveloped_data.getAuthenticatedAttributes();
275          if ((attributes != null) && (attributes.length > 0)) {
276            System.out.println("Attributes included: ");
277            // we know we have used content type
278            CMSContentType contentType = (CMSContentType)attributes[0].getAttributeValue();
279            System.out.println(contentType);  
280          }  
281    
282          return os.toByteArray();
283    
284        } catch (InvalidKeyException ex) {
285          throw new CMSException("Private key error: "+ex.toString());
286        } catch (NoSuchAlgorithmException ex) {
287          throw new CMSException("Content encryption algorithm not implemented: "+ex.getMessage());
288        } catch (CodingException ex) {
289          throw new CMSException("Cannot get unprotected attributes: "+ex.toString());
290        }
291        
292      }
293    
294      /**
295       * Starts the test.
296       */
297      public void start() {
298        // AES-CCM
299        AlgorithmID contentAuthEncAlg = (AlgorithmID)AlgorithmID.aes128_CCM.clone();
300        start(contentAuthEncAlg);
301        
302        // AES-GCM
303        contentAuthEncAlg = (AlgorithmID)AlgorithmID.aes128_GCM.clone();
304        start(contentAuthEncAlg);
305        
306        // ChaCha20-Poly1305
307        contentAuthEncAlg = (AlgorithmID)AlgorithmID.chacha20Poly1305.clone();
308        start(contentAuthEncAlg);
309      }
310      
311      /**
312       * Starts the test for the given content-authenticated encryption algorithm.
313       * 
314       * @param contentAuthEncAlg the id of the content-authenticated encryption algorithm
315       */
316      public void start(AlgorithmID contentAuthEncAlg) {
317         // the test message
318        String m = "This is the test message.";
319        System.out.println("Test message: \""+m+"\"");
320        System.out.println();
321        byte[] message = m.getBytes();
322    
323        try {
324          byte[] encoding;
325          byte[] received_message = null;
326          System.out.println("AuthEnvelopedDataOutputStream implementation demo");
327          System.out.println("==================================================");
328    
329    
330          //
331          // test CMS AuthEnvelopedDataOutputStream
332          //
333          System.out.println("\nAuthEnvelopedData demo [create]:\n");
334          encoding = createAuthEnvelopedData(message, (AlgorithmID)contentAuthEncAlg.clone());
335          // transmit data
336          System.out.println("\nAuthEnvelopedData demo [parse]:\n");
337          // user1 means index 0 (hardcoded for this demo)
338          received_message = getAuthEnvelopedDataStream(encoding, user1_crypt_pk, 0);
339          System.out.print("\nDecrypted content: ");
340          System.out.println(new String(received_message));
341          
342          if (CryptoUtils.equalsBlock(received_message, message) == false) {
343            throw new Exception("Decrypted content not equal to original one!");
344          }
345     
346          System.out.println("Ready!");
347    
348        } catch (Exception ex) {
349          ex.printStackTrace();
350          throw new RuntimeException(ex.toString());
351        }
352      }
353    
354    
355      /**
356       * Main method.
357       *
358       * @throws Exception
359       *            if an some error occurs 
360       */
361      public static void main(String argv[]) throws Exception {
362    
363        demo.DemoUtil.initDemos();
364    
365        (new AuthEnvelopedDataOutputStreamDemo()).start();
366    
367        DemoUtil.waitKey();
368      }
369    }