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