001// Copyright (C) 2002 IAIK
002// https://sic.tech/
003//
004// Copyright (C) 2003 - 2025 Stiftung Secure Information and 
005//                           Communication Technologies SIC
006// https://sic.tech/
007//
008// All rights reserved.
009//
010// This source is provided for inspection purposes and recompilation only,
011// unless specified differently in a contract with IAIK. This source has to
012// be kept in strict confidence and must not be disclosed to any third party
013// under any circumstances. Redistribution in source and binary forms, with
014// or without modification, are <not> permitted in any case!
015//
016// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
017// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
018// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
019// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
020// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
021// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
022// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
023// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
024// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
025// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
026// SUCH DAMAGE.
027//
028// $Header: /IAIK-CMS/current/src/demo/cms/envelopedData/EnvelopedDataOutputStreamDemo.java 20    12.02.25 17:58 Dbratko $
029// $Revision: 20 $
030//
031
032
033package demo.cms.envelopedData;
034
035import iaik.asn1.CodingException;
036import iaik.asn1.ObjectID;
037import iaik.asn1.structures.AlgorithmID;
038import iaik.asn1.structures.Attribute;
039import iaik.cms.CMSException;
040import iaik.cms.ContentInfoOutputStream;
041import iaik.cms.EncryptedContentInfoStream;
042import iaik.cms.EnvelopedDataOutputStream;
043import iaik.cms.EnvelopedDataStream;
044import iaik.cms.KeyTransRecipientInfo;
045import iaik.cms.RecipientInfo;
046import iaik.cms.attributes.CMSContentType;
047import iaik.security.random.SecRandom;
048import iaik.utils.CryptoUtils;
049import iaik.utils.Util;
050import iaik.x509.X509Certificate;
051
052import java.io.ByteArrayInputStream;
053import java.io.ByteArrayOutputStream;
054import java.io.IOException;
055import java.io.InputStream;
056import java.security.InvalidKeyException;
057import java.security.NoSuchAlgorithmException;
058import java.security.PrivateKey;
059import java.security.SecureRandom;
060
061import demo.DemoUtil;
062import demo.keystore.CMSKeyStore;
063
064
065/**
066 * Demonstrates the usage of class {@link iaik.cms.EnvelopedDataOutputStream} and
067 * for encrypting data using the CMS type EnvelopedData.
068 */
069public class EnvelopedDataOutputStreamDemo {
070
071   
072  // encryption certificate of user 1
073  X509Certificate user1_crypt;
074  // encryption private key of user 1
075  PrivateKey user1_crypt_pk;
076  // encryption certificate of user 2
077  X509Certificate user2_crypt;
078  // encryption private key of user 2
079  PrivateKey user2_crypt_pk;
080  
081  // secure random number generator
082  SecureRandom random;
083
084  /**
085   * Setup the demo certificate chains.
086   *
087   * Keys and certificate are retrieved from the demo KeyStore.
088   *
089   * @throws IOException if an file read error occurs
090   */
091  public EnvelopedDataOutputStreamDemo() throws IOException {
092    
093    System.out.println();
094    System.out.println("**********************************************************************************");
095    System.out.println("*                    EnvelopedDataOutputStream demo                              *");
096    System.out.println("*    (shows the usage of the CMS EnvelopedDataOutputStream implementation)       *");
097    System.out.println("**********************************************************************************");
098    System.out.println();
099    
100    // encryption certs
101    user1_crypt = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1)[0];
102    user1_crypt_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1);
103    user2_crypt = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2)[0];
104    user2_crypt_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2);
105
106    random = SecRandom.getDefault();
107
108  }
109
110
111  /**
112   * Creates a CMS <code>EnvelopedData</code> and wraps it into a ContentInfo.
113   *
114   * @param message the message to be enveloped, as byte representation
115   * @return the encoded EnvelopedData object just created, wrapped into a ContentInfo
116   *
117   * @throws CMSException if the <code>EnvelopedData</code> object cannot
118   *                          be created
119   * @throws IOException if an I/O error occurs
120   */
121  public byte[] createEnvelopedDataStream(byte[] message) throws CMSException, IOException {
122    
123    
124    //  a stream from which to read the data to be encrypted
125    ByteArrayInputStream is = new ByteArrayInputStream(message);
126    
127    // the stream to which to write the EnvelopedData
128    ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
129    EnvelopedDataOutputStream envelopedData;
130
131    //  wrap EnvelopedData into a ContentInfo 
132    ContentInfoOutputStream contentInfoStream = 
133      new ContentInfoOutputStream(ObjectID.cms_envelopedData, resultStream);
134    // create a new EnvelopedData object encrypted with AES
135    try {
136      envelopedData = new EnvelopedDataOutputStream(contentInfoStream, 
137                                                    (AlgorithmID)AlgorithmID.aes128_CBC.clone());
138    } catch (NoSuchAlgorithmException ex) {
139      throw new CMSException("No implementation for AES.");
140    }
141
142    // create the recipient infos
143    RecipientInfo[] recipients = new RecipientInfo[2];
144    // user1 is the first receiver
145    recipients[0] = new KeyTransRecipientInfo(user1_crypt, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
146    // user2 is the second receiver
147    recipients[1] = new KeyTransRecipientInfo(user2_crypt, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
148
149    // specify the recipients of the encrypted message
150    envelopedData.setRecipientInfos(recipients);
151    
152    Attribute[] attributes = new Attribute[1];
153    
154    try {
155      // just for testing: set some unprotected attribute
156      // content type is data
157      CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
158      attributes[0] = new Attribute(contentType);
159      envelopedData.setUnprotectedAttributes(attributes);
160    } catch (Exception ex) {
161      throw new CMSException("Error creating attribute: " + ex.toString());   
162    }  
163
164    int blockSize = 16; // in real world we would use a block size like 2048
165    //  write in the data to be encrypted
166    byte[] buffer = new byte[blockSize];
167    int bytesRead;
168    while ((bytesRead = is.read(buffer)) != -1) {
169      envelopedData.write(buffer, 0, bytesRead);
170    }
171    
172    // closing the stream finishes encryption and closes the underlying stream
173    envelopedData.close();
174    return resultStream.toByteArray();
175  }
176
177  /**
178   * Decrypts the encrypted content of the given EnvelopedData object for the
179   * specified recipient and returns the decrypted (= original) message.
180   *
181   * @param encoding the encoded EnvelopedData object, wrapped in a ContentInfo
182   * @param privateKey the private key to decrypt the message
183   * @param recipientInfoIndex the index into the <code>RecipientInfo</code> array
184   *                           to which the specified private key belongs
185   *
186   * @return the recovered message, as byte array
187   * @throws CMSException if the message cannot be recovered
188   * @throws IOException if an I/O error occurs
189   */
190  public byte[] getEnvelopedDataStream(byte[] encoding, PrivateKey privateKey, int recipientInfoIndex) throws CMSException, IOException {
191
192    // create the EnvelopedData object from a DER encoded byte array
193    // we are testing the stream interface
194    ByteArrayInputStream is = new ByteArrayInputStream(encoding);
195    
196    EnvelopedDataStream enveloped_data = new EnvelopedDataStream(is);
197
198    System.out.println("Information about the encrypted data:");
199    EncryptedContentInfoStream eci = (EncryptedContentInfoStream)enveloped_data.getEncryptedContentInfo();
200    System.out.println("Content type: "+eci.getContentType().getName());
201    System.out.println("Content encryption algorithm: "+eci.getContentEncryptionAlgorithm().getName());
202
203    System.out.println("\nThis message can be decrypted by the owners of the following certificates:");
204    RecipientInfo[] recipients = enveloped_data.getRecipientInfos();
205    for (int i=0; i<recipients.length; i++) {
206      System.out.println("Recipient "+(i+1)+":");
207      System.out.println(recipients[i].getRecipientIdentifiers()[0]);
208    }
209
210    // decrypt the message
211    try {
212      enveloped_data.setupCipher(privateKey, recipientInfoIndex);
213      InputStream decrypted = enveloped_data.getInputStream();
214      ByteArrayOutputStream os = new ByteArrayOutputStream();
215      Util.copyStream(decrypted, os, null);
216      
217      // get any unprotected attributes:
218      Attribute[] attributes = enveloped_data.getUnprotectedAttributes();
219      if ((attributes != null) && (attributes.length > 0)) {
220        System.out.println("Attributes included: ");
221        // we know we have used content type
222        CMSContentType contentType = (CMSContentType)attributes[0].getAttributeValue();
223        System.out.println(contentType);  
224      }  
225
226      return os.toByteArray();
227
228    } catch (InvalidKeyException ex) {
229      throw new CMSException("Private key error: "+ex.toString());
230    } catch (NoSuchAlgorithmException ex) {
231      throw new CMSException("Content encryption algorithm not implemented: "+ex.getMessage());
232    } catch (CodingException ex) {
233      throw new CMSException("Cannot get unprotected attributes: "+ex.toString());
234    }
235    
236  }
237
238  /**
239   * Starts the demo.
240   */
241  public void start() {
242     // the test message
243    String m = "This is the test message.";
244    System.out.println("Test message: \""+m+"\"");
245    System.out.println();
246    byte[] message = m.getBytes();
247
248    try {
249      byte[] encoding;
250      byte[] received_message = null;
251      System.out.println("EnvelopedDataOutputStream implementation demo");
252      System.out.println("=============================================");
253
254
255      //
256      // test CMS EnvelopedDataStream
257      //
258      System.out.println("\nEnvelopedData demo [create]:\n");
259      encoding = createEnvelopedDataStream(message);
260      // transmit data
261      System.out.println("\nEnvelopedData demo [parse]:\n");
262      // user1 means index 0 (hardcoded for this demo)
263      received_message = getEnvelopedDataStream(encoding, user1_crypt_pk, 0);
264      System.out.print("\nDecrypted content: ");
265      System.out.println(new String(received_message));
266      
267      if (CryptoUtils.equalsBlock(received_message, message) == false) {
268        throw new Exception("Decrypted content not equal to original one!");
269      }
270 
271      System.out.println("Ready!");
272
273    } catch (Exception ex) {
274      ex.printStackTrace();
275      throw new RuntimeException(ex.toString());
276    }
277  }
278
279
280  /**
281   * Main method.
282   *
283   * @throws Exception
284   *            if an some error occurs 
285   */
286  public static void main(String argv[]) throws Exception {
287
288    demo.DemoUtil.initDemos();
289
290    (new EnvelopedDataOutputStreamDemo()).start();
291
292    DemoUtil.waitKey();
293  }
294}