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