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/envelopedData/EnvelopedDataOutputStreamDemo.java 20    12.02.25 17:58 Dbratko $
059    // $Revision: 20 $
060    //
061    
062    
063    package demo.cms.envelopedData;
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.EnvelopedDataOutputStream;
073    import iaik.cms.EnvelopedDataStream;
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.EnvelopedDataOutputStream} and
097     * for encrypting data using the CMS type EnvelopedData.
098     */
099    public class EnvelopedDataOutputStreamDemo {
100    
101       
102      // encryption certificate of user 1
103      X509Certificate user1_crypt;
104      // encryption private key of user 1
105      PrivateKey user1_crypt_pk;
106      // encryption certificate of user 2
107      X509Certificate user2_crypt;
108      // encryption private key of user 2
109      PrivateKey user2_crypt_pk;
110      
111      // secure random number generator
112      SecureRandom random;
113    
114      /**
115       * Setup the demo certificate chains.
116       *
117       * Keys and certificate are retrieved from the demo KeyStore.
118       *
119       * @throws IOException if an file read error occurs
120       */
121      public EnvelopedDataOutputStreamDemo() throws IOException {
122        
123        System.out.println();
124        System.out.println("**********************************************************************************");
125        System.out.println("*                    EnvelopedDataOutputStream demo                              *");
126        System.out.println("*    (shows the usage of the CMS EnvelopedDataOutputStream implementation)       *");
127        System.out.println("**********************************************************************************");
128        System.out.println();
129        
130        // encryption certs
131        user1_crypt = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1)[0];
132        user1_crypt_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1);
133        user2_crypt = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2)[0];
134        user2_crypt_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2);
135    
136        random = SecRandom.getDefault();
137    
138      }
139    
140    
141      /**
142       * Creates a CMS <code>EnvelopedData</code> and wraps it into a ContentInfo.
143       *
144       * @param message the message to be enveloped, as byte representation
145       * @return the encoded EnvelopedData object just created, wrapped into a ContentInfo
146       *
147       * @throws CMSException if the <code>EnvelopedData</code> object cannot
148       *                          be created
149       * @throws IOException if an I/O error occurs
150       */
151      public byte[] createEnvelopedDataStream(byte[] message) throws CMSException, IOException {
152        
153        
154        //  a stream from which to read the data to be encrypted
155        ByteArrayInputStream is = new ByteArrayInputStream(message);
156        
157        // the stream to which to write the EnvelopedData
158        ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
159        EnvelopedDataOutputStream envelopedData;
160    
161        //  wrap EnvelopedData into a ContentInfo 
162        ContentInfoOutputStream contentInfoStream = 
163          new ContentInfoOutputStream(ObjectID.cms_envelopedData, resultStream);
164        // create a new EnvelopedData object encrypted with AES
165        try {
166          envelopedData = new EnvelopedDataOutputStream(contentInfoStream, 
167                                                        (AlgorithmID)AlgorithmID.aes128_CBC.clone());
168        } catch (NoSuchAlgorithmException ex) {
169          throw new CMSException("No implementation for AES.");
170        }
171    
172        // create the recipient infos
173        RecipientInfo[] recipients = new RecipientInfo[2];
174        // user1 is the first receiver
175        recipients[0] = new KeyTransRecipientInfo(user1_crypt, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
176        // user2 is the second receiver
177        recipients[1] = new KeyTransRecipientInfo(user2_crypt, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
178    
179        // specify the recipients of the encrypted message
180        envelopedData.setRecipientInfos(recipients);
181        
182        Attribute[] attributes = new Attribute[1];
183        
184        try {
185          // just for testing: set some unprotected attribute
186          // content type is data
187          CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
188          attributes[0] = new Attribute(contentType);
189          envelopedData.setUnprotectedAttributes(attributes);
190        } catch (Exception ex) {
191          throw new CMSException("Error creating attribute: " + ex.toString());   
192        }  
193    
194        int blockSize = 16; // in real world we would use a block size like 2048
195        //  write in the data to be encrypted
196        byte[] buffer = new byte[blockSize];
197        int bytesRead;
198        while ((bytesRead = is.read(buffer)) != -1) {
199          envelopedData.write(buffer, 0, bytesRead);
200        }
201        
202        // closing the stream finishes encryption and closes the underlying stream
203        envelopedData.close();
204        return resultStream.toByteArray();
205      }
206    
207      /**
208       * Decrypts the encrypted content of the given EnvelopedData object for the
209       * specified recipient and returns the decrypted (= original) message.
210       *
211       * @param encoding the encoded EnvelopedData object, wrapped in a ContentInfo
212       * @param privateKey the private key to decrypt the message
213       * @param recipientInfoIndex the index into the <code>RecipientInfo</code> array
214       *                           to which the specified private key belongs
215       *
216       * @return the recovered message, as byte array
217       * @throws CMSException if the message cannot be recovered
218       * @throws IOException if an I/O error occurs
219       */
220      public byte[] getEnvelopedDataStream(byte[] encoding, PrivateKey privateKey, int recipientInfoIndex) throws CMSException, IOException {
221    
222        // create the EnvelopedData object from a DER encoded byte array
223        // we are testing the stream interface
224        ByteArrayInputStream is = new ByteArrayInputStream(encoding);
225        
226        EnvelopedDataStream enveloped_data = new EnvelopedDataStream(is);
227    
228        System.out.println("Information about the encrypted data:");
229        EncryptedContentInfoStream eci = (EncryptedContentInfoStream)enveloped_data.getEncryptedContentInfo();
230        System.out.println("Content type: "+eci.getContentType().getName());
231        System.out.println("Content encryption algorithm: "+eci.getContentEncryptionAlgorithm().getName());
232    
233        System.out.println("\nThis message can be decrypted by the owners of the following certificates:");
234        RecipientInfo[] recipients = enveloped_data.getRecipientInfos();
235        for (int i=0; i<recipients.length; i++) {
236          System.out.println("Recipient "+(i+1)+":");
237          System.out.println(recipients[i].getRecipientIdentifiers()[0]);
238        }
239    
240        // decrypt the message
241        try {
242          enveloped_data.setupCipher(privateKey, recipientInfoIndex);
243          InputStream decrypted = enveloped_data.getInputStream();
244          ByteArrayOutputStream os = new ByteArrayOutputStream();
245          Util.copyStream(decrypted, os, null);
246          
247          // get any unprotected attributes:
248          Attribute[] attributes = enveloped_data.getUnprotectedAttributes();
249          if ((attributes != null) && (attributes.length > 0)) {
250            System.out.println("Attributes included: ");
251            // we know we have used content type
252            CMSContentType contentType = (CMSContentType)attributes[0].getAttributeValue();
253            System.out.println(contentType);  
254          }  
255    
256          return os.toByteArray();
257    
258        } catch (InvalidKeyException ex) {
259          throw new CMSException("Private key error: "+ex.toString());
260        } catch (NoSuchAlgorithmException ex) {
261          throw new CMSException("Content encryption algorithm not implemented: "+ex.getMessage());
262        } catch (CodingException ex) {
263          throw new CMSException("Cannot get unprotected attributes: "+ex.toString());
264        }
265        
266      }
267    
268      /**
269       * Starts the demo.
270       */
271      public void start() {
272         // the test message
273        String m = "This is the test message.";
274        System.out.println("Test message: \""+m+"\"");
275        System.out.println();
276        byte[] message = m.getBytes();
277    
278        try {
279          byte[] encoding;
280          byte[] received_message = null;
281          System.out.println("EnvelopedDataOutputStream implementation demo");
282          System.out.println("=============================================");
283    
284    
285          //
286          // test CMS EnvelopedDataStream
287          //
288          System.out.println("\nEnvelopedData demo [create]:\n");
289          encoding = createEnvelopedDataStream(message);
290          // transmit data
291          System.out.println("\nEnvelopedData demo [parse]:\n");
292          // user1 means index 0 (hardcoded for this demo)
293          received_message = getEnvelopedDataStream(encoding, user1_crypt_pk, 0);
294          System.out.print("\nDecrypted content: ");
295          System.out.println(new String(received_message));
296          
297          if (CryptoUtils.equalsBlock(received_message, message) == false) {
298            throw new Exception("Decrypted content not equal to original one!");
299          }
300     
301          System.out.println("Ready!");
302    
303        } catch (Exception ex) {
304          ex.printStackTrace();
305          throw new RuntimeException(ex.toString());
306        }
307      }
308    
309    
310      /**
311       * Main method.
312       *
313       * @throws Exception
314       *            if an some error occurs 
315       */
316      public static void main(String argv[]) throws Exception {
317    
318        demo.DemoUtil.initDemos();
319    
320        (new EnvelopedDataOutputStreamDemo()).start();
321    
322        DemoUtil.waitKey();
323      }
324    }