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
029package demo.cms.envelopedData;
030
031import java.io.ByteArrayInputStream;
032import java.io.ByteArrayOutputStream;
033import java.io.IOException;
034import java.io.InputStream;
035import java.security.AlgorithmParameters;
036import java.security.InvalidAlgorithmParameterException;
037import java.security.InvalidKeyException;
038import java.security.MessageDigest;
039import java.security.NoSuchAlgorithmException;
040import java.security.PrivateKey;
041import java.security.spec.InvalidParameterSpecException;
042
043import demo.DemoUtil;
044import demo.keystore.CMSKeyStore;
045import iaik.asn1.OCTET_STRING;
046import iaik.asn1.structures.AlgorithmID;
047import iaik.cms.CMSException;
048import iaik.cms.ContentInfo;
049import iaik.cms.ContentInfoStream;
050import iaik.cms.EncryptedContentInfo;
051import iaik.cms.EncryptedContentInfoStream;
052import iaik.cms.EnvelopedData;
053import iaik.cms.EnvelopedDataStream;
054import iaik.cms.KeyTransRecipientInfo;
055import iaik.cms.RecipientInfo;
056import iaik.cms.SecurityProvider;
057import iaik.cms.Utils;
058import iaik.pkcs.pkcs1.MGF1ParameterSpec;
059import iaik.pkcs.pkcs1.MaskGenerationAlgorithm;
060import iaik.pkcs.pkcs1.RSAOaepParameterSpec;
061import iaik.utils.Util;
062import iaik.x509.X509Certificate;
063
064
065/**
066 * This class demonstrates the CMS EnvelopedData implementation for
067 * the RSA-OAEP (PKCS#1v2.1) algorithm.
068 * <p>
069 * All keys and certificates are read from a keystore created by the
070 * SetupKeyStore program.
071 * @version File Revision <!-- $$Revision: --> 21 <!-- $ -->
072 */
073public class OaepEnvelopedDataDemo {
074
075  // certificate of user 1
076  X509Certificate user1;
077  // private key of user 1
078  PrivateKey user1_pk;
079  // certificate of user 2
080  X509Certificate user2;
081  // private key of user 2
082  PrivateKey user2_pk;
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 OaepEnvelopedDataDemo() throws IOException {
092    
093    System.out.println();
094    System.out.println("**********************************************************************************");
095    System.out.println("*                           OaepEnvelopedDataDemo                                *");
096    System.out.println("*    (shows the usage of the CMS EnvelopedData type with the RSA OAEP method)    *");
097    System.out.println("**********************************************************************************");
098    System.out.println();
099    
100    // add all certificates to the list
101    X509Certificate[] certs = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1);
102    user1 = certs[0];
103    user1_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1);
104    user2 = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2)[0];
105    user2_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2);
106  }
107
108
109  /**
110   * Creates a CMS <code>EnvelopedDataStream</code> message.
111   *
112   * @param message the message to be enveloped, as byte representation
113   * @return the BER encoded ContentInfo containing the EnvelopedData object just created
114   *
115   * @throws CMSException if the <code>EnvelopedData</code> object cannot
116   *                          be created
117   * @throws IOException if an I/O error occurs
118   */
119  public byte[] createEnvelopedDataStream(byte[] message) throws CMSException, IOException {
120
121    EnvelopedDataStream enveloped_data;
122
123    // we are testing the stream interface
124    ByteArrayInputStream is = new ByteArrayInputStream(message);
125    // create a new EnvelopedData object encrypted with AES
126    try {
127      enveloped_data = new EnvelopedDataStream(is, (AlgorithmID)AlgorithmID.aes256_CBC.clone());
128    } catch (NoSuchAlgorithmException ex) {
129      throw new CMSException(ex.toString());
130    }
131
132    try {
133      // create the recipient infos
134      RecipientInfo[] recipients = new RecipientInfo[2];
135      AlgorithmID hashID = AlgorithmID.sha256;
136      AlgorithmID oaepID = Utils.createOaepAlgorithmID(hashID);
137      // user1 is the first receiver 
138      recipients[0] = new KeyTransRecipientInfo(user1, oaepID);
139      // user2 is the second receiver (OAEP with user defined parameters)
140      oaepID = Utils.createOaepAlgorithmID(hashID);
141      recipients[1] = new KeyTransRecipientInfo(user2, oaepID);
142      // specify the recipients of the encrypted message
143      enveloped_data.setRecipientInfos(recipients);
144    } catch (Exception e) {
145      throw new CMSException("Error adding recipients: " + e.toString());
146    }
147    
148    // return the EnvelopedDate as DER encoded byte array with block size 2048
149    ByteArrayOutputStream os = new ByteArrayOutputStream();
150    enveloped_data.setBlockSize(2048);
151    ContentInfoStream cis = new ContentInfoStream(enveloped_data);
152    cis.writeTo(os);
153    return os.toByteArray();
154  }
155
156  /**
157   * Decrypts the encrypted content of the given EnvelopedData object for the
158   * specified recipient and returns the decrypted (= original) message.
159   *
160   * @param encoding the BER encoded ContentInfo containing an EnvelopedData object
161   * @param privateKey the private key to decrypt the message
162   * @param recipientInfoIndex the index into the <code>RecipientInfo</code> array
163   *                           to which the specified private key belongs
164   *
165   * @return the recovered message, as byte array
166   * @throws CMSException if the message cannot be recovered
167   * @throws IOException if an I/O error occurs
168   */
169  public byte[] getEnvelopedDataStream(byte[] encoding, PrivateKey privateKey, int recipientInfoIndex) 
170    throws CMSException, IOException {
171
172    // create the EnvelopedData object from a DER encoded byte array
173    // we are testing the stream interface
174    ByteArrayInputStream is = new ByteArrayInputStream(encoding);
175    EnvelopedDataStream enveloped_data = new EnvelopedDataStream(is);
176
177    System.out.println("Information about the encrypted data:");
178    EncryptedContentInfoStream eci = (EncryptedContentInfoStream)enveloped_data.getEncryptedContentInfo();
179    System.out.println("Content type: "+eci.getContentType().getName());
180    System.out.println("Content encryption algorithm: "+eci.getContentEncryptionAlgorithm().getName());
181
182    System.out.println("\nThis message can be decrypted by the owners of the following certificates:");
183    RecipientInfo[] recipients = enveloped_data.getRecipientInfos();
184    for (int i=0; i<recipients.length; i++) {
185      System.out.println("Recipient: "+(i+1));
186      System.out.print(recipients[i].getRecipientIdentifiers()[0]);
187    }
188
189    // decrypt the message
190    try {
191      enveloped_data.setupCipher(privateKey, recipientInfoIndex);
192      InputStream decrypted = enveloped_data.getInputStream();
193      ByteArrayOutputStream os = new ByteArrayOutputStream();
194      Util.copyStream(decrypted, os, null);
195
196      return os.toByteArray();
197
198    } catch (InvalidKeyException ex) {
199      System.out.println("Private key error: "+ex.getMessage());
200      return null;
201    } catch (NoSuchAlgorithmException ex) {
202      System.out.println("Content encryption algorithm not implemented: "+ex.getMessage());
203      return null;
204    }
205  }
206
207
208  /**
209   * Creates a CMS <code>EnvelopedData</code> message.
210   *
211   * @param message the message to be enveloped, as byte representation
212   * @return a BER encoded ContentInfo holding the EnvelopedData object just created
213   * @throws CMSException if the <code>EnvelopedData</code> object cannot
214   *                          be created
215   * @throws IOException if an I/O error occurs
216   */
217  public byte[] createEnvelopedData(byte[] message) throws CMSException, IOException {
218
219    EnvelopedData enveloped_data;
220
221    // create a new EnvelopedData object encrypted with AES
222    try {
223      enveloped_data = new EnvelopedData(message, (AlgorithmID)AlgorithmID.aes256_CBC.clone());
224    } catch (NoSuchAlgorithmException ex) {
225      throw new CMSException(ex.toString());
226    }
227
228    try {
229      // create the recipient infos
230      RecipientInfo[] recipients = new RecipientInfo[2];
231      AlgorithmID hashID = AlgorithmID.sha256;
232      AlgorithmID oaepID = Utils.createOaepAlgorithmID(hashID);
233      // user1 is the first receiver 
234      recipients[0] = new KeyTransRecipientInfo(user1, oaepID);
235      // user2 is the second receiver (OAEP with user defined parameters)
236      oaepID = Utils.createOaepAlgorithmID(hashID);
237      recipients[1] = new KeyTransRecipientInfo(user2, oaepID);
238      // specify the recipients of the encrypted message
239      enveloped_data.setRecipientInfos(recipients);
240    } catch (Exception e) {
241      throw new CMSException("Error adding recipients: " + e.toString());
242    }
243    
244    ContentInfo ci = new ContentInfo(enveloped_data);
245    // return the EnvelopedData as DER encoded byte array
246    return ci.toByteArray();
247  }
248
249  /**
250   * Decrypts the encrypted content of the given <code>EnvelopedData</code> object for the
251   * specified recipient and returns the decrypted (= original) message.
252   *
253   * @param encoding the ContentInfo encoding holding an EnvelopedData
254   * @param privateKey the private key to decrypt the message
255   * @param recipientInfoIndex the index into the <code>RecipientInfo</code> array
256   *                           to which the specified private key belongs
257   *
258   * @return the recovered message, as byte array
259   * @throws CMSException if the message cannot be recovered
260   * @throws IOException if an I/O error occurs
261   */
262  public byte[] getEnvelopedData(byte[] encoding, PrivateKey privateKey, int recipientInfoIndex) 
263    throws CMSException, IOException {
264    
265    EnvelopedData enveloped_data = new EnvelopedData(new ByteArrayInputStream(encoding));
266
267    System.out.println("Information about the encrypted data:");
268    EncryptedContentInfo eci = (EncryptedContentInfo)enveloped_data.getEncryptedContentInfo();
269    System.out.println("Content type: "+eci.getContentType().getName());
270    System.out.println("Content encryption algorithm: "+eci.getContentEncryptionAlgorithm().getName());
271
272    System.out.println("\nThis message can be decrypted by the owners of the following certificates:");
273    RecipientInfo[] recipients = enveloped_data.getRecipientInfos();
274    for (int i=0; i<recipients.length; i++) {
275      System.out.println("Recipient: "+(i+1));
276      System.out.print(recipients[i].getRecipientIdentifiers()[0]);
277    }
278
279    // decrypt the message
280    try {
281      enveloped_data.setupCipher(privateKey, recipientInfoIndex);
282      return enveloped_data.getContent();
283
284    } catch (InvalidKeyException ex) {
285      System.out.println("Private key error: "+ex.getMessage());
286      return null;
287    } catch (NoSuchAlgorithmException ex) {
288      System.out.println("Content encryption algorithm not implemented: "+ex.getMessage());
289      return null;
290    }
291  }
292
293 
294  /**
295   * Shows thw CMS EnvelopedData implementation for
296   * the RSA-OAEP (PKCS#1v2.1) algorithm.
297   */
298  public void start() {
299     // the test message
300    String m = "This is the test message.";
301    System.out.println("Test message: \""+m+"\"");
302    System.out.println();
303    byte[] message = m.getBytes();
304
305    try {
306      byte[] encodedEnvelopedData;
307      byte[] received_message = null;
308      System.out.println("Stream implementation demos (OAEP)");
309      System.out.println("==================================");
310
311     
312
313      // the stream implementation
314      //
315      // test CMS EnvelopedDataStream
316      //
317      System.out.println("\nEnvelopedDataStream demo [create]:\n");
318      encodedEnvelopedData = createEnvelopedDataStream(message);
319      // transmit data
320      System.out.println("\nEnvelopedDataStream demo [parse]:\n");
321      // user1 means index 0 (hardcoded for this demo)
322      System.out.println("\nDecrypt for recipient 1:\n");
323      received_message = getEnvelopedDataStream(encodedEnvelopedData, user1_pk, 0);
324      System.out.print("\nDecrypted content: ");
325      System.out.println(new String(received_message));
326      
327      // user2 means index 1 (hardcoded for this demo)
328      System.out.println("\nDecrypt for recipient 1:\n");
329      received_message = getEnvelopedDataStream(encodedEnvelopedData, user2_pk, 1);
330      System.out.print("\nDecrypted content: ");
331      System.out.println(new String(received_message));
332
333      
334      // the non-stream implementation
335      System.out.println("\nNon-stream implementation demos (OAEP)");
336      System.out.println("========================================");
337     
338      //
339      // test CMS EnvelopedData
340      //
341      System.out.println("\nEnvelopedData demo [create]:\n");
342      encodedEnvelopedData = createEnvelopedData(message);
343      // transmit data
344      System.out.println("\nEnvelopedData demo [parse]:\n");
345      System.out.println("\nDecrypt for recipient 1:\n");
346      // user1 means index 0 (hardcoded for this demo)
347      received_message = getEnvelopedData(encodedEnvelopedData, user1_pk, 0);
348      System.out.print("\nDecrypted content: ");
349      System.out.println(new String(received_message));
350      
351      System.out.println("\nDecrypt for recipient 2:\n");
352      // user2 means index 1 (hardcoded for this demo)
353      received_message = getEnvelopedData(encodedEnvelopedData, user2_pk, 1);
354      System.out.print("\nDecrypted content: ");
355      System.out.println(new String(received_message));
356
357      System.out.println("Ready!");
358
359        } catch (Exception ex) {
360          ex.printStackTrace();
361          throw new RuntimeException(ex.toString());
362        }
363  }
364
365
366  /**
367   * Main method.
368   *
369   * @throws IOException
370   *            if an I/O error occurs when reading required keys
371   *            and certificates from the keystore file
372   */
373  public static void main(String argv[]) throws Exception {
374
375    DemoUtil.initDemos();
376    (new OaepEnvelopedDataDemo()).start();
377
378    DemoUtil.waitKey();
379  }
380}