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/ecc/ECDHAuthenticatedDataDemo.java 5 12.02.25 17:58 Dbratko $
059 // $Revision: 5 $
060 //
061
062
063 package demo.cms.ecc;
064
065 import iaik.asn1.ObjectID;
066 import iaik.asn1.structures.AlgorithmID;
067 import iaik.asn1.structures.Attribute;
068 import iaik.cms.AuthenticatedData;
069 import iaik.cms.AuthenticatedDataOutputStream;
070 import iaik.cms.AuthenticatedDataStream;
071 import iaik.cms.CMSException;
072 import iaik.cms.ContentInfo;
073 import iaik.cms.ContentInfoOutputStream;
074 import iaik.cms.ContentInfoStream;
075 import iaik.cms.IssuerAndSerialNumber;
076 import iaik.cms.KeyIdentifier;
077 import iaik.cms.OriginatorInfo;
078 import iaik.cms.RecipientInfo;
079 import iaik.cms.RecipientKeyIdentifier;
080 import iaik.cms.attributes.CMSContentType;
081 import iaik.utils.Util;
082 import iaik.x509.X509Certificate;
083
084 import java.io.ByteArrayInputStream;
085 import java.io.ByteArrayOutputStream;
086 import java.io.IOException;
087 import java.io.InputStream;
088 import java.security.InvalidKeyException;
089 import java.security.Key;
090 import java.security.NoSuchAlgorithmException;
091
092 import javax.crypto.SecretKey;
093
094 import demo.DemoUtil;
095
096 /**
097 * Demonstrates the usage of class {@link iaik.cms.AuthenticatedDataStream},
098 * {@link iaik.cms.AuthenticatedData} and {@link iaik.cms.AuthenticatedDataOutputStream}
099 * for authenticated encrypting data using the CMS type
100 * AuthenticatedData by using Static-Static ECDH according to <a href =
101 * "http://www.ietf.org/rfc/rfc6278.txt" target="_blank">6278</a> as
102 * key agreement method.
103 * <p>
104 * Any keys/certificates required for this demo are read from a keystore
105 * file "cmsecc.keystore" located in your current working directory. If
106 * the keystore file does not exist you can create it by running the
107 * {@link demo.cms.ecc.keystore.SetupCMSEccKeyStore SetupCMSEccKeyStore}
108 * program.
109 * <p>
110 * Additionally to <code>iaik_cms.jar</code> you also must have
111 * <code>iaik_jce_(full).jar</code> (IAIK-JCE, <a href =
112 * "https://sic.tech/products/core-crypto-toolkits/jca-jce/" target="_blank">
113 * https://sic.tech/products/core-crypto-toolkits/jca-jce/</a>),
114 * and <code>iaik_eccelarate.jar</code> (IAIK-ECCelerate<sup><small>TM</small></sup>, <a href =
115 * "https://sic.tech/products/core-crypto-toolkits/eccelerate/" target="_blank">
116 * https://sic.tech/products/core-crypto-toolkits/eccelerate/</a>)
117 * in your classpath.
118 *
119 * @see iaik.cms.AuthenticatedDataStream
120 * @see iaik.cms.AuthenticatedData
121 * @see iaik.cms.AuthenticatedDataOutputStream
122 * @see iaik.cms.RecipientInfo
123 * @see iaik.cms.KeyAgreeRecipientInfo
124 * @see demo.cms.ecc.keystore.SetupCMSEccKeyStore
125 */
126 public class ECDHAuthenticatedDataDemo extends StaticStaticECDHDemo {
127
128 /**
129 * Setup the demo certificate chains.
130 *
131 * Keys and certificates are retrieved from the demo keyStore file
132 * "cmsecc.keystore" located in your current working directory. If
133 * the keystore file does not exist you can create it by running the
134 * {@link demo.cms.ecc.keystore.SetupCMSEccKeyStore SetupCMSEccKeyStore}
135 * program.
136 *
137 * @throws IOException if keys/certificates cannot be read from the keystore
138 */
139 public ECDHAuthenticatedDataDemo() throws IOException {
140 super();
141 System.out.println();
142 System.out.println("**********************************************************************************");
143 System.out.println("* ECDH AuthenticatedData demo *");
144 System.out.println("* (shows the usage of the CMS AuthenticatedData type implementation for ECDH) *");
145 System.out.println("**********************************************************************************");
146 System.out.println();
147
148
149 }
150
151 /**
152 * Creates a CMS <code>AuthenticatedDataStream</code> for the given message message.
153 *
154 * @param message the message to be authenticated, as byte representation
155 * @param macAlgorithm the mac algorithm to be used
156 * @param macKeyLength the length of the temporary MAC key to be generated
157 * @param digestAlgorithm the digest algorithm to be used to calculate a digest
158 * from the content if authenticated attributes should
159 * be included
160 * @param mode whether to include the content into the AuthenticatedData ({@link
161 * AuthenticatedDataStream#IMPLICIT implicit}) or to not include it
162 * ({@link AuthenticatedDataStream#EXPLICIT explicit})
163 *
164 * @return the BER encoding of the <code>AuthenticatedData</code> object just created
165 *
166 * @throws CMSException if the <code>AuthenticatedData</code> object cannot
167 * be created
168 * @throws IOException if an I/O error occurs
169 */
170 public byte[] createAuthenticatedDataStream(byte[] message,
171 AlgorithmID macAlgorithm,
172 int macKeyLength,
173 AlgorithmID digestAlgorithm,
174 int mode)
175 throws CMSException, IOException {
176
177 AlgorithmID macAlg = (AlgorithmID)macAlgorithm.clone();
178 AlgorithmID digestAlg = null;
179 if (digestAlgorithm != null) {
180 digestAlg = (AlgorithmID)digestAlgorithm.clone();
181 }
182 ObjectID contentType = ObjectID.cms_data;
183
184 AuthenticatedDataStream authenticatedData;
185
186 // we are testing the stream interface
187 ByteArrayInputStream is = new ByteArrayInputStream(message);
188 // create a new AuthenticatedData object
189 try {
190 authenticatedData = new AuthenticatedDataStream(contentType,
191 is,
192 macAlg,
193 macKeyLength,
194 null,
195 digestAlg,
196 mode);
197 } catch (NoSuchAlgorithmException ex) {
198 throw new CMSException(ex.toString());
199 }
200
201 // static-static mode: set OriginatorInfo
202 OriginatorInfo originator = new OriginatorInfo();
203 originator.setCertificates(ecdhOriginatorCerts_);
204 authenticatedData.setOriginatorInfo(originator);
205 // create the recipient infos
206 RecipientInfo[] recipients = createRecipients();
207 // specify the recipients of the authenticated message
208 authenticatedData.setRecipientInfos(recipients);
209
210 if (digestAlgorithm != null) {
211 // create some authenticated attributes
212 // (the message digest attribute is automatically added)
213 try {
214 Attribute[] attributes = { new Attribute(new CMSContentType(contentType)) };
215 authenticatedData.setAuthenticatedAttributes(attributes);
216 } catch (Exception ex) {
217 throw new CMSException("Error creating attribute: " + ex.toString());
218 }
219 }
220
221 // in explicit mode get the content and write it to any out-of-band place
222 if (mode == AuthenticatedDataStream.EXPLICIT) {
223 InputStream data_is = authenticatedData.getInputStream();
224 byte[] buf = new byte[2048];
225 int r;
226 while ((r = data_is.read(buf)) > 0) {
227 ; // skip data
228 }
229 }
230
231 // return the AuthenticatedData as BER encoded byte array with block size 16
232 // (just for testing; in real application we will use a proper blocksize,
233 // e.g. 2048, 4096,..)
234 authenticatedData.setBlockSize(16);
235 // return the AuthenticatedDate as BER encoded byte array with block size 2048
236 ByteArrayOutputStream os = new ByteArrayOutputStream();
237 // wrap into ContentInfo
238 ContentInfoStream contentInfo = new ContentInfoStream(authenticatedData);
239 contentInfo.writeTo(os);
240 return os.toByteArray();
241 }
242
243 /**
244 * Creates a CMS <code>AuthenticatedDataOutputStream</code> for the given message message.
245 *
246 * @param message the message to be authenticated, as byte representation
247 * @param macAlgorithm the mac algorithm to be used
248 * @param macKeyLength the length of the temporary MAC key to be generated
249 * @param digestAlgorithm the digest algorithm to be used to calculate a digest
250 * from the content if authenticated attributes should
251 * be included
252 * @param mode whether to include the content into the AuthenticatedData ({@link
253 * AuthenticatedDataStream#IMPLICIT implicit}) or to not include it
254 * ({@link AuthenticatedDataStream#EXPLICIT explicit})
255 *
256 * @return the BER encoding of the <code>AuthenticatedData</code> object just created,
257 * wrapped into a ContentInfo
258 *
259 * @throws CMSException if the <code>AuthenticatedData</code> object cannot
260 * be created
261 * @throws IOException if an I/O error occurs
262 */
263 public byte[] createAuthenticatedDataOutputStream(byte[] message,
264 AlgorithmID macAlgorithm,
265 int macKeyLength,
266 AlgorithmID digestAlgorithm,
267 int mode)
268 throws CMSException, IOException {
269
270 AlgorithmID macAlg = (AlgorithmID)macAlgorithm.clone();
271 AlgorithmID digestAlg = null;
272 if (digestAlgorithm != null) {
273 digestAlg = (AlgorithmID)digestAlgorithm.clone();
274 }
275 ObjectID contentType = ObjectID.cms_data;
276
277 AuthenticatedDataOutputStream authenticatedData;
278
279 // a stream from which to read the data to be authenticated
280 ByteArrayInputStream is = new ByteArrayInputStream(message);
281
282 // the stream to which to write the AuthenticatedData
283 ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
284
285 // wrap AuthenticatedData into a ContentInfo
286 ContentInfoOutputStream contentInfoStream =
287 new ContentInfoOutputStream(ObjectID.cms_authData, resultStream);
288
289 // create AuthenticatedDataOutputStream
290 try {
291 authenticatedData = new AuthenticatedDataOutputStream(contentType,
292 contentInfoStream,
293 macAlg,
294 macKeyLength,
295 null,
296 digestAlg,
297 mode);
298 } catch (NoSuchAlgorithmException ex) {
299 throw new CMSException(ex.toString());
300 }
301
302 // static-static mode: set OriginatorInfo
303 OriginatorInfo originator = new OriginatorInfo();
304 originator.setCertificates(ecdhOriginatorCerts_);
305 authenticatedData.setOriginatorInfo(originator);
306 // create the recipient infos
307 RecipientInfo[] recipients = createRecipients();
308 // specify the recipients of the authenticated message
309 authenticatedData.setRecipientInfos(recipients);
310
311 if (digestAlgorithm != null) {
312 // create some authenticated attributes
313 // (the message digest attribute is automatically added)
314 try {
315 Attribute[] attributes = { new Attribute(new CMSContentType(contentType)) };
316 authenticatedData.setAuthenticatedAttributes(attributes);
317 } catch (Exception ex) {
318 throw new CMSException("Error creating attribute: " + ex.toString());
319 }
320 }
321
322 int blockSize = 20; // in real world we would use a block size like 2048
323 // write in the data to be signed
324 byte[] buffer = new byte[blockSize];
325 int bytesRead;
326 while ((bytesRead = is.read(buffer)) != -1) {
327 authenticatedData.write(buffer, 0, bytesRead);
328 }
329
330 // closing the stream adds auth/unauth attributes, calculates and adds the mac value, .
331 authenticatedData.close();
332 return resultStream.toByteArray();
333 }
334
335
336 /**
337 * Decrypts the encrypted MAC key for the recipient identified by its index
338 * into the recipientInfos field and uses the MAC key to verify
339 * the authenticated data.
340 * <p>
341 * This way of decrypting the MAC key and verifying the content may be used for
342 * any type of RecipientInfo (KeyTransRecipientInfo, KeyAgreeRecipientInfo,
343 * KEKRecipientInfo, PasswordRecipeintInfo, OtherRecipientInfo), but requires to
344 * know at what index of the recipientInfos field the RecipientInfo for the
345 * particular recipient in mind can be found.
346 * If the recipient in mind uses a RecipientInfo of type KeyAgreeRecipientInfo
347 * some processing overhead may take place because a KeyAgreeRecipientInfo may
348 * contain encrypted mac keys for more than only one recipient; since the
349 * recipientInfoIndex only specifies the RecipientInfo but not the encrypted
350 * mac key -- if there are more than only one -- repeated decryption runs may be
351 * required as long as the decryption process completes successfully.
352 *
353 * @param encoding the <code>AuthenticatedData</code> object as BER encoded byte array
354 * @param message the content message, if transmitted by other means (explicit mode)
355 * @param key the key to decrypt the mac key
356 * @param recipientInfoIndex the index of the right <code>RecipientInfo</code> to
357 * which the given key belongs
358 *
359 * @return the verified message, as byte array
360 *
361 * @throws CMSException if the authenticated data cannot be verified
362 * @throws IOException if a stream read/write error occurs
363 */
364 public byte[] getAuthenticatedDataStream(byte[] encoding,
365 byte[] message,
366 Key key,
367 int recipientInfoIndex)
368 throws CMSException, IOException {
369
370 // create the AuthenticatedData object from a DER encoded byte array
371 // we are testing the stream interface
372 ByteArrayInputStream is = new ByteArrayInputStream(encoding);
373 AuthenticatedDataStream authenticatedData = new AuthenticatedDataStream(is);
374
375 if (authenticatedData.getMode() == AuthenticatedDataStream.EXPLICIT) {
376 // in explicit mode explicitly supply the content for hash/mac computation
377 authenticatedData.setInputStream(new ByteArrayInputStream(message));
378 }
379
380 System.out.println("\nThis message can be verified by the following recipients:");
381 RecipientInfo[] recipients = authenticatedData.getRecipientInfos();
382
383 // for demonstration purposes we only look one time for all recipients included:
384 if (recipientInfoIndex == 0) {
385 int k = 0;
386 for (int i=0; i<recipients.length; i++) {
387 KeyIdentifier[] recipientIDs = recipients[i].getRecipientIdentifiers();
388 for (int j = 0; j < recipientIDs.length; j++) {
389 System.out.println("Recipient "+(++k)+":");
390 System.out.println(recipientIDs[j]);
391 }
392 }
393 }
394 // decrypt the mac key and verify the mac for the indented recipient
395 try {
396 authenticatedData.setupMac(key, recipientInfoIndex);
397 InputStream contentStream = authenticatedData.getInputStream();
398 ByteArrayOutputStream os = new ByteArrayOutputStream();
399 Util.copyStream(contentStream, os, null);
400
401 if (authenticatedData.verifyMac() == false) {
402 throw new CMSException("Mac verification error!");
403 }
404 System.out.println("Mac successfully verified!");
405
406 return os.toByteArray();
407
408 } catch (InvalidKeyException ex) {
409 throw new CMSException("Key error: "+ex.getMessage());
410 } catch (NoSuchAlgorithmException ex) {
411 throw new CMSException(ex.toString());
412 }
413 }
414
415 /**
416 * Decrypts the encrypted MAC key for the recipient identified by recipient identifier
417 * and uses the MAC key to verify the authenticated data.
418 * <p>
419 * This way of decrypting the mac key may be used for any type of RecipientInfo
420 * (KeyTransRecipientInfo, KeyAgreeRecipientInfo, KEKRecipientInfo). The
421 * recipient in mind is identified by its recipient identifier.
422 *
423 * @param encoding the <code>AuthenticatedData</code> object as BER encoded byte array
424 * @param message the content message, if transmitted by other means (explicit mode)
425 * @param key the key to decrypt the encrypted mac key
426 * @param recipientID the recipient identifier uniquely identifying the key of the
427 * recipient
428 *
429 * @return the verified message, as byte array
430 *
431 * @throws CMSException if the authenticated data cannot be verified
432 * @throws IOException if a stream read/write error occurs
433 */
434 public byte[] getAuthenticatedDataStream(byte[] encoding,
435 byte[] message,
436 Key key,
437 KeyIdentifier recipientID)
438 throws CMSException, IOException {
439
440 // create the AuthenticatedData object from a BER encoded byte array
441 // we are testing the stream interface
442 ByteArrayInputStream is = new ByteArrayInputStream(encoding);
443 AuthenticatedDataStream authenticatedData = new AuthenticatedDataStream(is);
444
445 if (authenticatedData.getMode() == AuthenticatedDataStream.EXPLICIT) {
446 // in explicit mode explicitly supply the content for hash/mac computation
447 authenticatedData.setInputStream(new ByteArrayInputStream(message));
448 }
449
450 // get the right RecipientInfo
451 System.out.println("\nSearch for RecipientInfo:");
452 RecipientInfo recipient = authenticatedData.getRecipientInfo(recipientID);
453 if (recipient != null) {
454 System.out.println("RecipientInfo: " + recipient);
455 } else {
456 throw new CMSException("No recipient with ID: " + recipientID);
457 }
458 // decrypt the mac key and verify the content mac
459 try {
460 System.out.println("Decrypt encrypted mac key...");
461 SecretKey cek = recipient.decryptKey(key, recipientID);
462 System.out.println("Verify content mac with decrypted mac key...");
463 authenticatedData.setupMac(cek);
464 InputStream contentStream = authenticatedData.getInputStream();
465 ByteArrayOutputStream os = new ByteArrayOutputStream();
466 Util.copyStream(contentStream, os, null);
467
468 if (authenticatedData.verifyMac() == false) {
469 throw new CMSException("Mac verification error!");
470 }
471 System.out.println("Mac successfully verified!");
472
473 return os.toByteArray();
474
475 } catch (InvalidKeyException ex) {
476 throw new CMSException("Key error: "+ex.getMessage());
477 } catch (NoSuchAlgorithmException ex) {
478 throw new CMSException(ex.toString());
479 }
480 }
481
482 /**
483 * Decrypts the encrypted content of the given <code>AuthenticatedData</code> object for
484 * the recipient identified by its recipient certificate.
485 * <p>
486 *
487 * @param encoding the <code>AuthenticatedData</code> object as DER encoded byte array
488 * @param message the content message, if transmitted by other means (explicit mode)
489 * @param key the key to decrypt the message
490 * @param recipientCert the certificate of the recipient having a RecipientInfo of
491 * type KeyTransRecipientInfo or KeyAgreeRecipientInfo
492 *
493 * @return the recovered message, as byte array
494 * @throws CMSException if the message cannot be recovered
495 * @throws IOException if a stream read/write error occurs
496 */
497 public byte[] getAuthenticatedDataStream(byte[] encoding,
498 byte[] message,
499 Key key,
500 X509Certificate recipientCert)
501 throws CMSException, IOException {
502
503 // create the AuthenticatedData object from a DER encoded byte array
504 // we are testing the stream interface
505 ByteArrayInputStream is = new ByteArrayInputStream(encoding);
506 AuthenticatedDataStream authenticatedData = new AuthenticatedDataStream(is);
507
508 if (authenticatedData.getMode() == AuthenticatedDataStream.EXPLICIT) {
509 // in explicit mode explicitly supply the content for hash/mac computation
510 authenticatedData.setInputStream(new ByteArrayInputStream(message));
511 }
512
513
514 // decrypt the mac key and verify the content mac
515 try {
516 System.out.println("Verify mac...");
517 authenticatedData.setupMac(key, recipientCert);
518 InputStream contentStream = authenticatedData.getInputStream();
519 ByteArrayOutputStream os = new ByteArrayOutputStream();
520 Util.copyStream(contentStream, os, null);
521
522 if (authenticatedData.verifyMac() == false) {
523 throw new CMSException("Mac verification error!");
524 }
525 System.out.println("Mac successfully verified!");
526
527 return os.toByteArray();
528
529 } catch (InvalidKeyException ex) {
530 throw new CMSException("Key error: "+ex.getMessage());
531 } catch (NoSuchAlgorithmException ex) {
532 throw new CMSException(ex.toString());
533 }
534 }
535
536
537 // non stream
538
539 /**
540 * Creates a CMS <code>AuthenticatedData</code> for the given message message.
541 *
542 * @param message the message to be authenticated, as byte representation
543 * @param macAlgorithm the mac algorithm to be used
544 * @param macKeyLength the length of the temporary MAC key to be generated
545 * @param digestAlgorithm the digest algorithm to be used to calculate a digest
546 * from the content if authenticated attributes should
547 * be included
548 * @param mode whether to include the content into the AuthenticatedData ({@link
549 * AuthenticatedDataStream#IMPLICIT implicit}) or to not include it
550 * ({@link AuthenticatedDataStream#EXPLICIT explicit})
551 *
552 * @return the BER encoding of the <code>AuthenticatedData</code> object just created
553 *
554 * @throws CMSException if the <code>AuthenticatedData</code> object cannot
555 * be created
556 */
557 public byte[] createAuthenticatedData(byte[] message,
558 AlgorithmID macAlgorithm,
559 int macKeyLength,
560 AlgorithmID digestAlgorithm,
561 int mode)
562 throws CMSException {
563
564 AlgorithmID macAlg = (AlgorithmID)macAlgorithm.clone();
565 AlgorithmID digestAlg = null;
566 if (digestAlgorithm != null) {
567 digestAlg = (AlgorithmID)digestAlgorithm.clone();
568 }
569 ObjectID contentType = ObjectID.cms_data;
570
571 AuthenticatedData authenticatedData;
572
573 // create a new AuthenticatedData object
574 try {
575 authenticatedData = new AuthenticatedData(contentType,
576 message,
577 macAlg,
578 macKeyLength,
579 null,
580 digestAlg,
581 mode);
582 } catch (NoSuchAlgorithmException ex) {
583 throw new CMSException(ex.toString());
584 }
585
586 // static-static mode: set OriginatorInfo
587 OriginatorInfo originator = new OriginatorInfo();
588 originator.setCertificates(ecdhOriginatorCerts_);
589 authenticatedData.setOriginatorInfo(originator);
590 // create the recipient infos
591 RecipientInfo[] recipients = createRecipients();
592 // specify the recipients of the authenticated message
593 authenticatedData.setRecipientInfos(recipients);
594
595 if (digestAlgorithm != null) {
596 // create some authenticated attributes
597 // (the message digest attribute is automatically added)
598 try {
599 Attribute[] attributes = { new Attribute(new CMSContentType(contentType)) };
600 authenticatedData.setAuthenticatedAttributes(attributes);
601 } catch (Exception ex) {
602 throw new CMSException("Error creating attribute: " + ex.toString());
603 }
604 }
605
606 // wrap into ContentInfo
607 ContentInfo contentInfo = new ContentInfo(authenticatedData);
608 // return encoded EnvelopedData
609 return contentInfo.getEncoded();
610
611 }
612
613 /**
614 * Decrypts the encrypted MAC key for the recipient identified by its index
615 * into the recipientInfos field and uses the MAC key to verify
616 * the authenticated data.
617 * <p>
618 * This way of decrypting the MAC key and verifying the content may be used for
619 * any type of RecipientInfo (KeyTransRecipientInfo, KeyAgreeRecipientInfo,
620 * KEKRecipientInfo), but requires to know at what index of the recipientInfos
621 * field the RecipientInfo for the particular recipient in mind can be found.
622 * If the recipient in mind uses a RecipientInfo of type KeyAgreeRecipientInfo
623 * some processing overhead may take place because a KeyAgreeRecipientInfo may
624 * contain encrypted mac keys for more than only one recipient; since the
625 * recipientInfoIndex only specifies the RecipientInfo but not the encrypted
626 * mac key -- if there are more than only one -- repeated decryption runs may be
627 * required as long as the decryption process completes successfully.
628 *
629 * @param encoding the <code>AuthenticatedData</code> object as BER encoded byte array
630 * @param message the content message, if transmitted by other means (explicit mode)
631 * @param key the key to decrypt the mac key
632 * @param recipientInfoIndex the index of the right <code>RecipientInfo</code> to
633 * which the given key belongs
634 *
635 * @return the verified message, as byte array
636 * @throws CMSException if the authenticated data cannot be verified
637 * @throws IOException if a IO read/write error occurs
638 */
639 public byte[] getAuthenticatedData(byte[] encoding,
640 byte[] message,
641 Key key,
642 int recipientInfoIndex)
643 throws CMSException, IOException {
644
645 // create the AuthenticatedData object from a DER encoded byte array
646 ByteArrayInputStream is = new ByteArrayInputStream(encoding);
647 AuthenticatedData authenticatedData = new AuthenticatedData(is);
648
649 if (authenticatedData.getMode() == AuthenticatedData.EXPLICIT) {
650 // in explicit mode explicitly supply the content for hash/mac computation
651 authenticatedData.setContent(message);
652 }
653
654 System.out.println("\nThis message can be verified by the owners of the following recipients:");
655 RecipientInfo[] recipients = authenticatedData.getRecipientInfos();
656
657 // for demonstration purposes we only look one time for all recipients included:
658 if (recipientInfoIndex == 0) {
659 int k = 0;
660 for (int i=0; i<recipients.length; i++) {
661 KeyIdentifier[] recipientIDs = recipients[i].getRecipientIdentifiers();
662 for (int j = 0; j < recipientIDs.length; j++) {
663 System.out.println("Recipient "+(++k)+":");
664 System.out.println(recipientIDs[j]);
665 }
666 }
667 }
668 // decrypt the mac key and verify the mac for the first recipient
669 try {
670 authenticatedData.setupMac(key, recipientInfoIndex);
671 if (authenticatedData.verifyMac() == false) {
672 throw new CMSException("Mac verification error!");
673 }
674 System.out.println("Mac successfully verified!");
675
676 return authenticatedData.getContent();
677
678 } catch (InvalidKeyException ex) {
679 throw new CMSException("Key error: "+ex.getMessage());
680 } catch (NoSuchAlgorithmException ex) {
681 throw new CMSException(ex.toString());
682 }
683 }
684
685 /**
686 * Decrypts the encrypted content of the given <code>AuthenticatedData</code> object for
687 * the recipient identified by recipient identifier.
688 * <p>
689 * This way of decrypting the content may be used for any type of RecipientInfo
690 * (KeyTransRecipientInfo, KeyAgreeRecipientInfo, KEKRecipientInfo). The
691 * recipient in mind is identified by its recipient identifier.
692 *
693 * @param encoding the DER encoeded <code>AuthenticatedData</code> object#
694 * @param message the content message, if transmitted by other means (explicit mode)
695 * @param key the key to decrypt the message
696 * @param recipientID the recipient identifier uniquely identifying the key of the
697 * recipient
698 *
699 * @return the recovered message, as byte array
700 * @throws CMSException if the message cannot be recovered
701 * @throws IOException if an I/O error occurs
702 */
703 public byte[] getAuthenticatedData(byte[] encoding,
704 byte[] message,
705 Key key,
706 KeyIdentifier recipientID)
707 throws CMSException, IOException {
708
709 // create the AuthenticatedData object from a DER encoded byte array
710 ByteArrayInputStream is = new ByteArrayInputStream(encoding);
711 AuthenticatedData authenticatedData = new AuthenticatedData(is);
712
713 if (authenticatedData.getMode() == AuthenticatedData.EXPLICIT) {
714 // in explicit mode explicitly supply the content for hash/mac computation
715 authenticatedData.setContent(message);
716 }
717
718 // get the right RecipientInfo
719 System.out.println("\nSearch for RecipientInfo:");
720 RecipientInfo recipient = authenticatedData.getRecipientInfo(recipientID);
721 if (recipient != null) {
722 System.out.println("RecipientInfo: " + recipient);
723 } else {
724 throw new CMSException("No recipient with ID " + recipientID);
725 }
726 // decrypt the mac key and verify the content mac
727 try {
728 System.out.println("Decrypt encrypted mac key...");
729 SecretKey cek = recipient.decryptKey(key, recipientID);
730 System.out.println("Verify content mac with decrypted mac key...");
731 authenticatedData.setupMac(cek);
732
733 if (authenticatedData.verifyMac() == false) {
734 throw new CMSException("Mac verification error!");
735 }
736 System.out.println("Mac successfully verified!");
737
738 return authenticatedData.getContent();
739
740 } catch (InvalidKeyException ex) {
741 throw new CMSException("Key error: "+ex.getMessage());
742 }
743 }
744
745 /**
746 * Decrypts the encrypted content of the given <code>AuthenticatedData</code> object for
747 * the recipient identified by its recipient certificate.
748 * <p>
749 *
750 * @param encoding the DER encoded <code>AuthenticatedData</code> ASN.1 object
751 * @param message the content message, if transmitted by other means (explicit mode)
752 * @param key the key to decrypt the message
753 * @param recipientCert the certificate of the recipient having a RecipientInfo of
754 * type KeyTransRecipientInfo or KeyAgreeRecipientInfo
755 *
756 * @return the recovered message, as byte array
757 * @throws CMSException if the message cannot be recovered
758 */
759 public byte[] getAuthenticatedData(byte[] encoding,
760 byte[] message,
761 Key key,
762 X509Certificate recipientCert)
763 throws CMSException, IOException {
764
765 // create the AuthenticatedData object from a DER encoded byte array
766 ByteArrayInputStream is = new ByteArrayInputStream(encoding);
767 AuthenticatedData authenticatedData = new AuthenticatedData(is);
768
769 if (authenticatedData.getMode() == AuthenticatedData.EXPLICIT) {
770 // in explicit mode explicitly supply the content for hash/mac computation
771 authenticatedData.setContent(message);
772 }
773
774 // decrypt the mac key and verify the content mac
775 try {
776 System.out.println("Verify mac...");
777 authenticatedData.setupMac(key, recipientCert);
778 if (authenticatedData.verifyMac() == false) {
779 throw new CMSException("Mac verification error!");
780 }
781 System.out.println("Mac successfully verified!");
782
783 return authenticatedData.getContent();
784
785 } catch (InvalidKeyException ex) {
786 throw new CMSException("Key error: "+ex.getMessage());
787 } catch (NoSuchAlgorithmException ex) {
788 throw new CMSException(ex.toString());
789 }
790 }
791
792 /**
793 * Parses an AuthenticatedData and verifies the mac for all recipients
794 * using the index into the recipientInfos field for identifying the recipient.
795 *
796 * @param stream whether to use AuthenticatedDataStream or AuthenticatedData
797 * @param encodedAuthenticatedData the encoded AuthenticatedData object
798 * @param message the content message, if transmitted by other means (explicit mode)
799 *
800 * @throws Exception if some error occurs during decoding/decryption
801 */
802 public void parseAuthenticatedDataWithRecipientInfoIndex(boolean stream,
803 byte[] encodedAuthenticatedData, byte[] message) throws Exception {
804 byte[] receivedMessage;
805 if (stream) {
806 // ecdhUser1
807 System.out.println("\nDecrypt for ecdhUser1:");
808 receivedMessage = getAuthenticatedDataStream(encodedAuthenticatedData, message, ecdhUser1Pk_, 0);
809 System.out.print("\nDecrypted content: ");
810 System.out.println(new String(receivedMessage));
811 // ecdhUser2
812 System.out.println("\nDecrypt for ecdhUser2:");
813 receivedMessage = getAuthenticatedDataStream(encodedAuthenticatedData, message, ecdhUser2Pk_, 0);
814 System.out.print("\nDecrypted content: ");
815 System.out.println(new String(receivedMessage));
816 } else {
817 // ecdhUser1
818 System.out.println("\nDecrypt for ecdhUser1:");
819 receivedMessage = getAuthenticatedData(encodedAuthenticatedData, message, ecdhUser1Pk_, 0);
820 System.out.print("\nDecrypted content: ");
821 System.out.println(new String(receivedMessage));
822 // ecdhUser2
823 System.out.println("\nDecrypt for ecdhUser2:");
824 receivedMessage = getAuthenticatedData(encodedAuthenticatedData, message, ecdhUser2Pk_, 0);
825 System.out.print("\nDecrypted content: ");
826 System.out.println(new String(receivedMessage));
827 }
828 }
829
830 /**
831 * Parses an AuthenticatedData and verifies the mac for all recipients
832 * using their recipient identifiers for identifying the recipient.
833 *
834 * @param stream whether to use AuthenticatedDataStream or AuthenticatedData
835 * @param encodedAuthenticatedData the encoded AuthenticatedData object
836 * @param message the content message, if transmitted by other means (explicit mode)
837 *
838 * @throws Exception if some error occurs during decoding/decryption
839 */
840 public void parseAuthenticatedDataWithRecipientIdentifier(boolean stream, byte[] encodedAuthenticatedData,
841 byte[] message) throws Exception {
842 byte[] receivedMessage;
843 if (stream) {
844 // ecdhUser1
845 System.out.println("\nDecrypt for ecdhUser1:");
846 receivedMessage = getAuthenticatedDataStream(encodedAuthenticatedData, message, ecdhUser1Pk_, new IssuerAndSerialNumber(ecdhUser1_));
847 System.out.print("\nDecrypted content: ");
848 System.out.println(new String(receivedMessage));
849 // ecdhUser2
850 System.out.println("\nDecrypt for ecdhUser2:");
851 receivedMessage = getAuthenticatedDataStream(encodedAuthenticatedData, message, ecdhUser2Pk_, new RecipientKeyIdentifier(ecdhUser2_));
852 System.out.print("\nDecrypted content: ");
853 System.out.println(new String(receivedMessage));
854 } else {
855 // ecdhUser1
856 System.out.println("\nDecrypt for ecdhUser1:");
857 receivedMessage = getAuthenticatedData(encodedAuthenticatedData, message, ecdhUser1Pk_, new IssuerAndSerialNumber(ecdhUser1_));
858 System.out.print("\nDecrypted content: ");
859 System.out.println(new String(receivedMessage));
860 // ecdhUser2
861 System.out.println("\nDecrypt for ecdhUser2:");
862 receivedMessage = getAuthenticatedData(encodedAuthenticatedData, message, ecdhUser2Pk_, new RecipientKeyIdentifier(ecdhUser2_));
863 System.out.print("\nDecrypted content: ");
864 System.out.println(new String(receivedMessage));
865 }
866 }
867
868 /**
869 * Parses an AuthenticatedData and verifies the mac for all recipients
870 * using the recipient certificate for identifying the recipient.
871 *
872 * @param stream whether to use AuthenticatedDataStream or AuthenticatedData
873 * @param encodedAuthenticatedData the encoded AuthenticatedData object
874 * @param message the content message, if transmitted by other means (explicit mode)
875 *
876 * @throws Exception if some error occurs during decoding/decryption
877 */
878 public void parseAuthenticatedDataWithRecipientCertificate(boolean stream, byte[] encodedAuthenticatedData,
879 byte[] message) throws Exception {
880 byte[] receivedMessage;
881 if (stream) {
882 // ecdhUser1
883 System.out.println("\nDecrypt for ecdhUser1:");
884 receivedMessage = getAuthenticatedDataStream(encodedAuthenticatedData, message, ecdhUser1Pk_, ecdhUser1_);
885 System.out.print("\nDecrypted content: ");
886 System.out.println(new String(receivedMessage));
887 // ecdhUser2
888 System.out.println("\nDecrypt for ecdhUser2:");
889 receivedMessage = getAuthenticatedDataStream(encodedAuthenticatedData, message, ecdhUser2Pk_, ecdhUser2_);
890 System.out.print("\nDecrypted content: ");
891 System.out.println(new String(receivedMessage));
892 } else {
893 // ecdhUser1
894 System.out.println("\nDecrypt for ecdhUser1:");
895 receivedMessage = getAuthenticatedData(encodedAuthenticatedData, message, ecdhUser1Pk_, ecdhUser1_);
896 System.out.print("\nDecrypted content: ");
897 System.out.println(new String(receivedMessage));
898 // ecdhUser2
899 System.out.println("\nDecrypt for ecdhUser2:");
900 receivedMessage = getAuthenticatedData(encodedAuthenticatedData, message, ecdhUser2Pk_, ecdhUser2_);
901 System.out.print("\nDecrypted content: ");
902 System.out.println(new String(receivedMessage));
903 }
904 }
905
906
907
908 /**
909 * Starts the demo.
910 */
911 public void start() {
912 // the test message
913 String m = "This is the test message.";
914 System.out.println("Test message: \""+m+"\"");
915 System.out.println();
916 byte[] message = m.getBytes();
917
918 AlgorithmID macAlgorithm = (AlgorithmID)AlgorithmID.hMAC_SHA256.clone();
919 int macKeyLength = 32;
920 AlgorithmID digestAlgorithm = (AlgorithmID)AlgorithmID.sha256.clone();
921
922 try {
923 byte[] encoding;
924 System.out.println("Stream implementation demos");
925 System.out.println("===========================");
926
927
928 // the stream implementation
929 //
930 // test CMS AuthenticatedDataStream
931 //
932
933 int[] modes = { AuthenticatedDataStream.IMPLICIT, AuthenticatedDataStream.EXPLICIT };
934
935 for (int i = 0; i < modes.length; i++) {
936 int mode = modes[i];
937 if (mode == AuthenticatedDataStream.IMPLICIT) {
938 System.out.print("Implicit ");
939 } else {
940 System.out.print("Explicit ");
941 }
942 // with authenticated attributes
943 System.out.println("AuthenticatedDataStream demo with authenticated attributes [create]:\n");
944 encoding = createAuthenticatedDataStream(message,
945 macAlgorithm,
946 macKeyLength,
947 digestAlgorithm,
948 mode);
949 // transmit data
950 System.out.println("\nAuthenticatedDataStream demo with authenticated attributes [parse]:\n");
951 System.out.println("Parse for the several recipients using their index into the recipientInfos field.");
952 parseAuthenticatedDataWithRecipientInfoIndex(true, encoding, message);
953 System.out.println("Parse for the several recipients using their RecipientIdentifier.");
954 parseAuthenticatedDataWithRecipientIdentifier(true, encoding, message);
955 System.out.println("Parse for the several recipients using their certificate.");
956 parseAuthenticatedDataWithRecipientCertificate(true, encoding, message);
957
958 // without authenticated attributes
959 System.out.println("AuthenticatedDataStream demo without authenticated attributes [create]:\n");
960 encoding = createAuthenticatedDataStream(message,
961 macAlgorithm,
962 macKeyLength,
963 null,
964 mode);
965 // transmit data
966 System.out.println("\nAuthenticatedDataStream demo without authenticated attributes [parse]:\n");
967 System.out.println("Parse for the several recipients using their index into the recipientInfos field.");
968 parseAuthenticatedDataWithRecipientInfoIndex(true, encoding, message);
969 System.out.println("Parse for the several recipients using their RecipientIdentifier.");
970 parseAuthenticatedDataWithRecipientIdentifier(true, encoding, message);
971 System.out.println("Parse for the several recipients using their certificate.");
972 parseAuthenticatedDataWithRecipientCertificate(true, encoding, message);
973
974 }
975
976 System.out.println("\nOutputStream implementation demos");
977 System.out.println("=================================");
978
979
980 // the output stream implementation
981 //
982 // test CMS AuthenticatedDataOutputStream
983 //
984 for (int i = 0; i < modes.length; i++) {
985 int mode = modes[i];
986 if (mode == AuthenticatedDataStream.IMPLICIT) {
987 System.out.print("Implicit ");
988 } else {
989 System.out.print("Explicit ");
990 }
991 // with authenticated attributes
992 System.out.println("AuthenticatedDataOutputStream demo with authenticated attributes [create]:\n");
993 encoding = createAuthenticatedDataOutputStream(message,
994 macAlgorithm,
995 macKeyLength,
996 digestAlgorithm,
997 mode);
998 // transmit data
999 System.out.println("\nAuthenticatedDataStream demo with authenticated attributes [parse]:\n");
1000 System.out.println("Parse for the several recipients using their index into the recipientInfos field.");
1001 parseAuthenticatedDataWithRecipientInfoIndex(true, encoding, message);
1002 System.out.println("Parse for the several recipients using their RecipientIdentifier.");
1003 parseAuthenticatedDataWithRecipientIdentifier(true, encoding, message);
1004 System.out.println("Parse for the several recipients using their certificate.");
1005 parseAuthenticatedDataWithRecipientCertificate(true, encoding, message);
1006
1007 // without authenticated attributes
1008 System.out.println("AuthenticatedDataOutputStream demo without authenticated attributes [create]:\n");
1009 encoding = createAuthenticatedDataOutputStream(message,
1010 macAlgorithm,
1011 macKeyLength,
1012 null,
1013 mode);
1014 // transmit data
1015 System.out.println("\nAuthenticatedDataOutputStream demo without authenticated attributes [parse]:\n");
1016 System.out.println("Parse for the several recipients using their index into the recipientInfos field.");
1017 parseAuthenticatedDataWithRecipientInfoIndex(true, encoding, message);
1018 System.out.println("Parse for the several recipients using their RecipientIdentifier.");
1019 parseAuthenticatedDataWithRecipientIdentifier(true, encoding, message);
1020 System.out.println("Parse for the several recipients using their certificate.");
1021 parseAuthenticatedDataWithRecipientCertificate(true, encoding, message);
1022
1023 }
1024 System.out.println("===============================");
1025
1026
1027 //
1028 // test CMS AuthenticatedData
1029 //
1030 for (int i = 0; i < modes.length; i++) {
1031 int mode = modes[i];
1032 if (mode == AuthenticatedDataStream.IMPLICIT) {
1033 System.out.print("Implicit ");
1034 } else {
1035 System.out.print("Explicit ");
1036 }
1037 // with authenticated attributes
1038 System.out.println("AuthenticatedData demo with authenticated attributes [create]:\n");
1039 encoding = createAuthenticatedData(message,
1040 macAlgorithm,
1041 macKeyLength,
1042 digestAlgorithm,
1043 mode);
1044 // transmit data
1045 System.out.println("\nAuthenticatedDataStream demo with authenticated attributes [parse]:\n");
1046 System.out.println("Parse for the several recipients using their index into the recipientInfos field.");
1047 parseAuthenticatedDataWithRecipientInfoIndex(false, encoding, message);
1048 System.out.println("Parse for the several recipients using their RecipientIdentifier.");
1049 parseAuthenticatedDataWithRecipientIdentifier(false, encoding, message);
1050 System.out.println("Parse for the several recipients using their certificate.");
1051 parseAuthenticatedDataWithRecipientCertificate(false, encoding, message);
1052
1053 // without authenticated attributes
1054 System.out.println("AuthenticatedData demo without authenticated attributes [create]:\n");
1055 encoding = createAuthenticatedData(message,
1056 macAlgorithm,
1057 macKeyLength,
1058 null,
1059 mode);
1060 // transmit data
1061 System.out.println("\nAuthenticatedData demo without authenticated attributes [parse]:\n");
1062 System.out.println("Parse for the several recipients using their index into the recipientInfos field.");
1063 parseAuthenticatedDataWithRecipientInfoIndex(false, encoding, message);
1064 System.out.println("Parse for the several recipients using their RecipientIdentifier.");
1065 parseAuthenticatedDataWithRecipientIdentifier(false, encoding, message);
1066 System.out.println("Parse for the several recipients using their certificate.");
1067 parseAuthenticatedDataWithRecipientCertificate(false, encoding, message);
1068
1069 }
1070
1071
1072
1073 } catch (Exception ex) {
1074 ex.printStackTrace();
1075 throw new RuntimeException(ex.toString());
1076 }
1077 }
1078
1079 /**
1080 * Main method.
1081 *
1082 * @throws IOException
1083 * if an I/O error occurs when reading required keys
1084 * and certificates from the keystore file
1085 */
1086 public static void main(String argv[]) throws Exception {
1087
1088 DemoUtil.initDemos();
1089 ECCDemoUtil.installIaikEccProvider();
1090 (new ECDHAuthenticatedDataDemo()).start();
1091 System.out.println("\nReady!");
1092 System.in.read();
1093 }
1094 }