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 }