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/EncryptedContentInfoDemo.java 26    12.02.25 17:58 Dbratko $
029// $Revision: 26 $
030//
031
032package demo.cms.envelopedData;
033
034import iaik.asn1.ASN;
035import iaik.asn1.ASN1Object;
036import iaik.asn1.INTEGER;
037import iaik.asn1.OCTET_STRING;
038import iaik.asn1.ObjectID;
039import iaik.asn1.SEQUENCE;
040import iaik.asn1.structures.AlgorithmID;
041import iaik.cms.EncryptedContentInfoStream;
042import iaik.cms.EnvelopedDataStream;
043import iaik.cms.KeyTransRecipientInfo;
044import iaik.cms.RecipientInfo;
045import iaik.cms.SecurityProvider;
046import iaik.security.random.SecRandom;
047import iaik.utils.Util;
048import iaik.x509.X509Certificate;
049
050import java.io.ByteArrayInputStream;
051import java.io.ByteArrayOutputStream;
052import java.io.IOException;
053import java.io.InputStream;
054import java.math.BigInteger;
055import java.security.PrivateKey;
056import java.security.SecureRandom;
057import java.security.spec.AlgorithmParameterSpec;
058
059import javax.crypto.KeyGenerator;
060import javax.crypto.SecretKey;
061import javax.crypto.spec.IvParameterSpec;
062import javax.crypto.spec.RC2ParameterSpec;
063
064import demo.keystore.CMSKeyStore;
065
066/**
067 * This class demonstrates the EnvelopedDataStream/EncryptedContentInfoStream usages
068 * for algorithms that require a specific parameter handling.
069 * <p>
070 * All keys and certificates are read from a keystore created by the
071 * SetupCMSKeyStore program.
072 * <p>
073 * The following algorithms are demonstrated:
074 * <ul>
075 * <li>ARCFOUR: Variable-key-size stream cipher; no parameters to be sent
076 * <li>RC2_CBC: Variable-key-size block cipher; parameters as used by S/MIME:
077 *              rc2ParamterVersion and IV; encoded as SEQUENCE:
078 *              <pre>
079 *               RC2-CBC parameter ::=  SEQUENCE {
080 *                 rc2ParameterVersion  INTEGER,
081 *                 iv                   OCTET STRING (8)}
082 *
083 *               For the effective-key-bits of 40, 64, and 128, the
084 *                rc2ParameterVersion values are 160, 120, 58 respectively.
085 *              </pre>
086 * <li>CAST5_CBC: Feistel type block cipher with key sizes of 40-128 bit in 8 bit
087 *              increments; parameters (RFC 2144):
088 *              <pre>
089 *               Parameters ::=  SEQUENCE {
090 *                 iv         OCTET STRING DEFAULT 0,
091 *                 keyLength  INTEGER }
092 *
093 *              </pre>
094 * </ul>
095 * This class shows how an EncryptedContentInfo is explicit created for encrypting
096 * the content and supplying it to an EnvelopedDataStream object. Note that IAIK-CMS
097 * also allows to use EnvelopedData(Stream) for algorithms like RC2, ARCFOUR or CAST 
098 * without having the necessity of explicit key/parameter handling, see {@link 
099 * RC2EnvelopedDataDemo RC2EnvelopedDataDemo} for an example. Note that the usage
100 * of algorithms like RC2 is deprecated but used here for this demo since it
101 * requires a specific parameter handling.
102 */
103public class EncryptedContentInfoDemo {
104
105  // certificate of user 1
106  X509Certificate user1;
107  // private key of user 1
108  PrivateKey user1_pk;
109  // certificate of user 2
110  X509Certificate user2;
111  // private key of user 2
112  PrivateKey user2_pk;
113  // secure random number generator
114  SecureRandom random;
115
116  /**
117   * Setup the demo certificate chains.
118   *
119   * Keys and certificate are retrieved from the demo KeyStore.
120   *
121   * @throws IOException if an file read error occurs
122   */
123  public EncryptedContentInfoDemo() throws IOException {
124    
125    System.out.println();
126    System.out.println("********************************************************************************************");
127    System.out.println("*                               EncryptedContentInfoDemo                                   *");
128    System.out.println("* (shows the usage of the EncryptedContentInfo implementation for encrypting some message) *");
129    System.out.println("********************************************************************************************");
130    System.out.println();
131    
132    
133    // add all certificates to the list
134    X509Certificate[] certs = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1);
135    user1 = certs[0];
136    user1_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1);
137    user2 = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2)[0];
138    user2_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2);
139
140    random = SecRandom.getDefault();
141  }
142
143
144  /**
145   * Creates a CMS <code>EnvelopedDataStream</code> message.
146   *
147   * @param message the message to be enveloped, as byte representation
148   * @param contentEA the content encryption algorithm
149   * @param keyLength the key length for the symmetric key
150   * @return the DER encoding of the <code>EnvelopedData</code> object just created
151   * @throws Exception if the <code>EnvelopedData</code> object cannot be created
152   */
153  public byte[] createEnvelopedDataStream(byte[] message, AlgorithmID contentEA, int keyLength) throws Exception {
154
155      SecurityProvider provider = SecurityProvider.getSecurityProvider();
156      ByteArrayInputStream is = new ByteArrayInputStream(message);
157
158      AlgorithmParameterSpec params = null;
159      SecretKey secretKey = null;
160
161      // create iv
162      byte[] iv = new byte[8];
163      random.nextBytes(iv);
164
165      int rc2_param = 58;
166      if (contentEA.equals(AlgorithmID.rc2_CBC)) {
167         
168         switch (keyLength) {
169           case 40:
170             rc2_param = 160;
171             break;
172           case 64:
173             rc2_param = 120;
174             break;
175           default:    // 128
176             rc2_param = 58;
177             keyLength = 128;
178         }
179         // create the paramters (SEQUENCE) to be sent
180         SEQUENCE parameter = new SEQUENCE();
181         parameter.addComponent(new INTEGER(rc2_param));
182         parameter.addComponent(new OCTET_STRING(iv));
183         contentEA.setParameter(parameter);
184         params = new RC2ParameterSpec(keyLength,iv);
185      }  else if (contentEA.equals(AlgorithmID.arcfour)){
186         // no params for ARCFOUR
187         params = null;
188      } else if (contentEA.equals(AlgorithmID.cast5_CBC)) {
189         SEQUENCE parameter = new SEQUENCE();
190         parameter.addComponent(new OCTET_STRING(iv));
191         parameter.addComponent(new INTEGER(keyLength));
192         contentEA.setParameter(parameter);
193         params = new IvParameterSpec(iv);
194
195      } else {
196         throw new Exception("Algorithm " + contentEA + " not supportted for this test!");
197      }
198
199      KeyGenerator keyGen = provider.getKeyGenerator(contentEA, keyLength);
200      // generate a new key
201      secretKey = keyGen.generateKey();
202
203      // create the EncryptedContentInfo for the content to be encrypted
204      EncryptedContentInfoStream eci = new EncryptedContentInfoStream(ObjectID.pkcs7_data, is);
205      // setup the cipher for encryption
206      eci.setupCipher(contentEA, secretKey, params);
207
208       // create the recipient infos
209      RecipientInfo[] recipients = new RecipientInfo[2];
210      // user1 is the first receiver
211      recipients[0] = new KeyTransRecipientInfo(user1, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
212      // encrypt the secret key for recipient 1
213      recipients[0].encryptKey(secretKey);
214      // user2 is the second receiver
215      recipients[1] = new KeyTransRecipientInfo(user2, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
216      // encrypt the secret key for recipient 2
217      recipients[1].encryptKey(secretKey);
218      // now create the EnvelopedDataStream
219      EnvelopedDataStream enveloped_data = new EnvelopedDataStream(recipients, eci);
220
221      // return the EnvelopedDate as DER encoded byte array with block size 2048
222      ByteArrayOutputStream os = new ByteArrayOutputStream();
223      enveloped_data.writeTo(os, 2048);
224      byte[] enc = os.toByteArray();
225      return enc;
226
227  }
228
229  /**
230   * Decrypts the encrypted content of the given CMS <code>EnvelopedData</code> object for the
231   * specified recipient and returns the decrypted (= original) message.
232   *
233   * @param encoding the <code>EnvelopedData</code> object as DER encoded byte array
234   * @param privateKey the private key to decrypt the message
235   * @param recipientInfoIndex the index into the <code>RecipientInfo</code> array
236   *                           to which the specified private key belongs
237   *
238   * @return the recovered message, as byte array
239   * @throws Exception if the message cannot be recovered
240   */
241  public byte[] getEnvelopedDataStream(byte[] encoding, PrivateKey privateKey, int recipientInfoIndex) throws Exception {
242
243    // create the EnvelopedData object from a DER encoded byte array
244    // we are testing the stream interface
245    ByteArrayInputStream is = new ByteArrayInputStream(encoding);
246    EnvelopedDataStream enveloped_data = new EnvelopedDataStream(is);
247
248    AlgorithmParameterSpec params = null;
249    // get the recipient infos
250    RecipientInfo[]  recipients = enveloped_data.getRecipientInfos();
251
252    System.out.println("\nThis message can be decrypted by the owners of the following certificates:");
253
254    for (int i=0; i<recipients.length; i++) {
255      System.out.println("Recipient "+(i+1)+":");
256      System.out.println(recipients[i].getRecipientIdentifiers()[0]);
257    }
258    // decrypt symmetric content encryption key, e.g.:
259    SecretKey secretKey = recipients[recipientInfoIndex].decryptKey(user1_pk);
260
261    //get the ECI from the enveloped data:
262    EncryptedContentInfoStream eci = (EncryptedContentInfoStream)enveloped_data.getEncryptedContentInfo();
263    //get the content encryption algorithm:
264    AlgorithmID contentEA = eci.getContentEncryptionAlgorithm();
265    System.out.println("\nContent Encryption Algorithm: " + contentEA);
266    if (contentEA.equals(AlgorithmID.rc2_CBC)) {
267        // get the parameters as SEQUENCE
268        SEQUENCE seq = (SEQUENCE)contentEA.getParameter();
269        // the iv is the second component
270        OCTET_STRING oct = (OCTET_STRING)seq.getComponentAt(1);
271        // create an IvParameterSpec:
272        //params = new IvParameterSpec((byte[])oct.getValue());
273        int rc2ParameterVersion = ((BigInteger)seq.getComponentAt(0).getValue()).intValue();
274        int effective_key_bits = 32;
275        switch (rc2ParameterVersion) {
276              case 160:
277                effective_key_bits = 40;
278                break;
279              case 120:
280                effective_key_bits = 64;
281                break;
282              case 58:
283                effective_key_bits = 128;
284                break;
285              default:
286                throw new Exception("Invalid rc2ParameterVersion " + rc2ParameterVersion + "!");
287
288             }
289             params = new RC2ParameterSpec(effective_key_bits,(byte[])seq.getComponentAt(1).getValue());
290
291      }
292      else if (contentEA.equals(AlgorithmID.rc5_CBC)) {
293         OCTET_STRING oct = (OCTET_STRING)contentEA.getParameter();
294         // create an IvParameterSpec:
295         params = new IvParameterSpec((byte[])oct.getValue());
296      } else if (contentEA.equals(AlgorithmID.arcfour)) {
297         params = null;
298      } else if (contentEA.equals(AlgorithmID.cast5_CBC)) {
299          // get the parameters
300          ASN1Object asn1Params = contentEA.getParameter();
301          if (asn1Params.isA(ASN.SEQUENCE)) {
302            // the iv is the first component
303            params = new IvParameterSpec((byte[])asn1Params.getComponentAt(0).getValue());
304          } else {
305            // to be compatible with (invalid) CAST AlgorithmIDs only using the IV as parameters
306            params = new IvParameterSpec((byte[])asn1Params.getValue());
307          }  
308      } else {
309         throw new Exception("Algorithm " + contentEA + " not supportted for this test!");
310      }
311
312
313      //now setup the cipher with previously decrypted recipient key amd params
314      eci.setupCipher(secretKey, params);
315      //get and read the data thereby actually performing the decryption
316      InputStream data_is = eci.getInputStream();
317      ByteArrayOutputStream baos = new ByteArrayOutputStream();
318      Util.copyStream(data_is, baos, null);
319      byte[] decrypted = baos.toByteArray();
320      return decrypted;
321
322  }
323  
324  
325  /**
326   * Starts the test.
327   */
328  public void start() {
329     // the test message
330    String m = "This is the test message.";
331    System.out.println("Test message: \""+m+"\"");
332    System.out.println();
333    byte[] message = m.getBytes();
334
335    try {
336      byte[] data;
337      byte[] received_message = null;
338
339
340      // the stream implementation
341      //
342      // test CMS EnvelopedDataStream
343      //
344
345      // ARCFOUR
346      System.out.println("\nEnvelopedDataStream demo for algorithm ARCFOUR [create]:\n");
347      data = createEnvelopedDataStream(message, (AlgorithmID)AlgorithmID.arcfour.clone(), 128);
348      // transmit data
349      System.out.println("\nEnvelopedDataStream demo [parse]:\n");
350      // user1 means index 0 (hardcoded for this demo)
351      received_message = getEnvelopedDataStream(data, user1_pk, 0);
352      System.out.print("\nDecrypted content: ");
353      System.out.println(new String(received_message));
354
355      // RC2
356      System.out.println("\nEnvelopedDataStream demo for algorithm RC2 [create]:\n");
357      data = createEnvelopedDataStream(message, (AlgorithmID)AlgorithmID.rc2_CBC.clone(), 128);
358      // transmit data
359      System.out.println("\nEnvelopedDataStream demo [parse]:\n");
360      // user1 means index 0 (hardcoded for this demo)
361      received_message = getEnvelopedDataStream(data, user1_pk, 0);
362      System.out.print("\nDecrypted content: ");
363      System.out.println(new String(received_message));
364
365      // CAST5_CBC
366      System.out.println("\nEnvelopedDataStream demo for algorithm CAST5_CBC [create]:\n");
367      data = createEnvelopedDataStream(message, (AlgorithmID)AlgorithmID.cast5_CBC.clone(), 128);
368      // transmit data
369      System.out.println("\nEnvelopedDataStream demo [parse]:\n");
370      // user1 means index 0 (hardcoded for this demo)
371      received_message = getEnvelopedDataStream(data, user1_pk, 0);
372      System.out.print("\nDecrypted content: ");
373      System.out.println(new String(received_message));
374      
375        } catch (Exception ex) {
376          ex.printStackTrace();
377          throw new RuntimeException(ex.toString());
378        }
379  }
380
381  /**
382   * The main method.
383   *
384   * @throws IOException
385   *            if an I/O error occurs when reading required keys
386   *            and certificates from files
387   */
388  public static void main(String argv[]) throws Exception {
389
390        demo.DemoUtil.initDemos();
391
392    (new EncryptedContentInfoDemo()).start();
393    System.out.println("\nReady!");
394    System.in.read();
395  }
396}