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/authEnvelopedData/AuthEnvelopedDataOutputStreamDemo.java 10    12.02.25 17:58 Dbratko $
029// $Revision: 10 $
030//
031
032
033package demo.cms.authEnvelopedData;
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.AuthEnvelopedDataOutputStream;
043import iaik.cms.AuthEnvelopedDataStream;
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.AuthEnvelopedDataOutputStream} and
067 * for authenticated encrypting data using the CMS type AuthEnvelopedData
068 * according to <a href = "http://www.ietf.org/rfc/rfc5083.txt" target="_blank">RFC 5083</a>.
069 * <p>
070 * This demo uses the AES-CCM and AES-GCM authenticated encryption algorithms
071 * as specified by <a href = "http://www.ietf.org/rfc/rfc5084.txt" target="_blank">RFC 5084</a>
072 * and the ChaCha20-Poly1305 authenticated encryption algorithm
073 * as specified by <a href = "http://www.ietf.org/rfc/rfc8103.txt" target="_blank">RFC 8103</a>.
074 * The demo creates an AuthEnvelopedData object and subsequently shows several
075 * ways that may be used for decrypting the content and verifying the message
076 * authentication code for some particular recipient.
077 * <br>
078 * Since AES-CCM and AES-GCM are not implemented by IAIK-JCE versions prior 3.17, this demo
079 * at least may require IAIK-JCE 3.17 as cryptographic service provider. 
080 * ChaCha20-Poly1305 for CMS requires IAIK-JCE version 5.62 or later.
081 * <p>
082 * Keys and certificates are retrieved from the demo KeyStore ("cms.keystore") 
083 * which has to be located in your current working directory and may be
084 * created by running the {@link demo.keystore.SetupCMSKeyStore
085 * SetupCMSKeyStore} program.
086 * <p>
087 */
088public class AuthEnvelopedDataOutputStreamDemo {
089
090   
091  // encryption certificate of user 1
092  X509Certificate user1_crypt;
093  // encryption private key of user 1
094  PrivateKey user1_crypt_pk;
095  // encryption certificate of user 2
096  X509Certificate user2_crypt;
097  // encryption private key of user 2
098  PrivateKey user2_crypt_pk;
099  
100  // secure random number generator
101  SecureRandom random;
102
103  /**
104   * Setup the demo certificate chains.
105   *
106   * Keys and certificate are retrieved from the demo KeyStore.
107   *
108   * @throws IOException if an file read error occurs
109   */
110  public AuthEnvelopedDataOutputStreamDemo() throws IOException {
111    
112    System.out.println();
113    System.out.println("**********************************************************************************");
114    System.out.println("*                    AuthEnvelopedDataOutputStream demo                          *");
115    System.out.println("*    (shows the usage of the CMS AuthEnvelopedDataOutputStream implementation)   *");
116    System.out.println("**********************************************************************************");
117    System.out.println();
118    
119    // encryption certs
120    user1_crypt = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1)[0];
121    user1_crypt_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1);
122    user2_crypt = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2)[0];
123    user2_crypt_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2);
124
125    random = SecRandom.getDefault();
126
127  }
128
129
130  /**
131   * Creates a CMS <code>AuthEnvelopedData</code> and wraps it into a ContentInfo.
132   *
133   * @param message the message to be enveloped, as byte representation
134   * @param contentAuthEncAlg the id of the content-authenticated encryption algorithm
135   * 
136   * @return the encoded AuthEnvelopedData object just created, wrapped into a ContentInfo
137   *
138   * @throws CMSException if the <code>AuthEnvelopedData</code> object cannot
139   *                          be created
140   * @throws IOException if an I/O error occurs
141   */
142  public byte[] createAuthEnvelopedData(byte[] message, AlgorithmID contentAuthEncAlg)
143    throws CMSException, IOException {
144    
145    System.out.println("Create AuthEnvelopedData message for : " + contentAuthEncAlg.getName());
146    
147    //  a stream from which to read the data to be encrypted
148    ByteArrayInputStream is = new ByteArrayInputStream(message);
149    
150    // the stream to which to write the AuthEnvelopedData
151    ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
152    AuthEnvelopedDataOutputStream authEnvelopedData;
153
154    //  wrap AuthEnvelopedData into a ContentInfo 
155    ContentInfoOutputStream contentInfoStream = 
156      new ContentInfoOutputStream(ObjectID.cms_authEnvelopedData, resultStream);
157   
158    // create a new AuthEnvelopedData object 
159    authEnvelopedData = new AuthEnvelopedDataOutputStream(contentInfoStream, 
160                                                          contentAuthEncAlg);
161    
162    
163    if (contentAuthEncAlg.equals(AlgorithmID.aes128_CCM) || 
164        contentAuthEncAlg.equals(AlgorithmID.aes192_CCM) ||
165        contentAuthEncAlg.equals(AlgorithmID.aes256_CCM)) {
166      // for aes-ccm we need to know the data input length in advance
167      authEnvelopedData.setInputLength(message.length);
168    }
169  
170    // create the recipient infos
171    RecipientInfo[] recipients = new RecipientInfo[2];
172    // user1 is the first receiver
173    recipients[0] = new KeyTransRecipientInfo(user1_crypt, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
174    // user2 is the second receiver
175    recipients[1] = new KeyTransRecipientInfo(user2_crypt, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
176
177    // specify the recipients of the encrypted message
178    authEnvelopedData.setRecipientInfos(recipients);
179    
180    try {
181      // just for demonstration: set some authenticated attribute
182      // content type is data
183      CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
184      Attribute[] attributes = { new Attribute(contentType) };
185      authEnvelopedData.setAuthenticatedAttributes(attributes);
186    } catch (Exception ex) {
187      throw new CMSException("Error creating attribute: " + ex.toString());   
188    }  
189
190    int blockSize = 16; // in real world we would use a block size like 2048
191    //  write in the data to be encrypted
192    byte[] buffer = new byte[blockSize];
193    int bytesRead;
194    while ((bytesRead = is.read(buffer)) != -1) {
195      authEnvelopedData.write(buffer, 0, bytesRead);
196    }
197    
198    // closing the stream finishes encryption and closes the underlying stream
199    authEnvelopedData.close();
200    return resultStream.toByteArray();
201  }
202
203  /**
204   * Decrypts the encrypted content of the given AuthEnvelopedData object and
205   * verifies the message authentication code for the specified recipient.
206   *
207   * @param encoding the encoded AuthEnvelopedData object, wrapped in a ContentInfo
208   * @param privateKey the private key to decrypt the message
209   * @param recipientInfoIndex the index into the <code>RecipientInfo</code> array
210   *                           to which the specified private key belongs
211   *
212   * @return the recovered message, as byte array
213   * @throws CMSException if the message cannot be recovered
214   * @throws IOException if an I/O error occurs
215   */
216  public byte[] getAuthEnvelopedDataStream(byte[] encoding, PrivateKey privateKey, int recipientInfoIndex) throws CMSException, IOException {
217
218    // create the AuthEnvelopedData object from a BER encoded byte array
219    // we are testing the stream interface
220    ByteArrayInputStream is = new ByteArrayInputStream(encoding);
221    
222    AuthEnvelopedDataStream enveloped_data = new AuthEnvelopedDataStream(is);
223
224    System.out.println("Information about the encrypted data:");
225    EncryptedContentInfoStream eci = (EncryptedContentInfoStream)enveloped_data.getEncryptedContentInfo();
226    System.out.println("Content type: "+eci.getContentType().getName());
227    System.out.println("Content encryption algorithm: "+eci.getContentEncryptionAlgorithm().getName());
228
229    System.out.println("\nThis message can be decrypted by the owners of the following certificates:");
230    RecipientInfo[] recipients = enveloped_data.getRecipientInfos();
231    for (int i=0; i<recipients.length; i++) {
232      System.out.println("Recipient "+(i+1)+":");
233      System.out.println(recipients[i].getRecipientIdentifiers()[0]);
234    }
235
236    // decrypt the message
237    try {
238      enveloped_data.setupCipher(privateKey, recipientInfoIndex);
239      InputStream decrypted = enveloped_data.getInputStream();
240      ByteArrayOutputStream os = new ByteArrayOutputStream();
241      Util.copyStream(decrypted, os, null);
242      
243      // get any unprotected attributes:
244      Attribute[] attributes = enveloped_data.getAuthenticatedAttributes();
245      if ((attributes != null) && (attributes.length > 0)) {
246        System.out.println("Attributes included: ");
247        // we know we have used content type
248        CMSContentType contentType = (CMSContentType)attributes[0].getAttributeValue();
249        System.out.println(contentType);  
250      }  
251
252      return os.toByteArray();
253
254    } catch (InvalidKeyException ex) {
255      throw new CMSException("Private key error: "+ex.toString());
256    } catch (NoSuchAlgorithmException ex) {
257      throw new CMSException("Content encryption algorithm not implemented: "+ex.getMessage());
258    } catch (CodingException ex) {
259      throw new CMSException("Cannot get unprotected attributes: "+ex.toString());
260    }
261    
262  }
263
264  /**
265   * Starts the test.
266   */
267  public void start() {
268    // AES-CCM
269    AlgorithmID contentAuthEncAlg = (AlgorithmID)AlgorithmID.aes128_CCM.clone();
270    start(contentAuthEncAlg);
271    
272    // AES-GCM
273    contentAuthEncAlg = (AlgorithmID)AlgorithmID.aes128_GCM.clone();
274    start(contentAuthEncAlg);
275    
276    // ChaCha20-Poly1305
277    contentAuthEncAlg = (AlgorithmID)AlgorithmID.chacha20Poly1305.clone();
278    start(contentAuthEncAlg);
279  }
280  
281  /**
282   * Starts the test for the given content-authenticated encryption algorithm.
283   * 
284   * @param contentAuthEncAlg the id of the content-authenticated encryption algorithm
285   */
286  public void start(AlgorithmID contentAuthEncAlg) {
287     // the test message
288    String m = "This is the test message.";
289    System.out.println("Test message: \""+m+"\"");
290    System.out.println();
291    byte[] message = m.getBytes();
292
293    try {
294      byte[] encoding;
295      byte[] received_message = null;
296      System.out.println("AuthEnvelopedDataOutputStream implementation demo");
297      System.out.println("==================================================");
298
299
300      //
301      // test CMS AuthEnvelopedDataOutputStream
302      //
303      System.out.println("\nAuthEnvelopedData demo [create]:\n");
304      encoding = createAuthEnvelopedData(message, (AlgorithmID)contentAuthEncAlg.clone());
305      // transmit data
306      System.out.println("\nAuthEnvelopedData demo [parse]:\n");
307      // user1 means index 0 (hardcoded for this demo)
308      received_message = getAuthEnvelopedDataStream(encoding, user1_crypt_pk, 0);
309      System.out.print("\nDecrypted content: ");
310      System.out.println(new String(received_message));
311      
312      if (CryptoUtils.equalsBlock(received_message, message) == false) {
313        throw new Exception("Decrypted content not equal to original one!");
314      }
315 
316      System.out.println("Ready!");
317
318    } catch (Exception ex) {
319      ex.printStackTrace();
320      throw new RuntimeException(ex.toString());
321    }
322  }
323
324
325  /**
326   * Main method.
327   *
328   * @throws Exception
329   *            if an some error occurs 
330   */
331  public static void main(String argv[]) throws Exception {
332
333    demo.DemoUtil.initDemos();
334
335    (new AuthEnvelopedDataOutputStreamDemo()).start();
336
337    DemoUtil.waitKey();
338  }
339}