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/smime/ess/TripleWrappingDemo.java 38 12.02.25 17:59 Dbratko $ 029// $Revision: 38 $ 030// 031 032package demo.smime.ess; 033 034import iaik.asn1.structures.AlgorithmID; 035import iaik.smime.EncryptedContent; 036import iaik.smime.SMimeBodyPart; 037import iaik.smime.SMimeMultipart; 038import iaik.smime.SignedContent; 039import iaik.x509.X509Certificate; 040 041import java.io.ByteArrayInputStream; 042import java.io.ByteArrayOutputStream; 043import java.io.IOException; 044import java.security.NoSuchAlgorithmException; 045import java.security.PrivateKey; 046import java.security.interfaces.RSAPrivateKey; 047import java.util.Date; 048 049import jakarta.activation.DataHandler; 050import jakarta.activation.FileDataSource; 051import jakarta.mail.Message; 052import jakarta.mail.MessagingException; 053import jakarta.mail.Multipart; 054import jakarta.mail.Session; 055import jakarta.mail.internet.InternetAddress; 056import jakarta.mail.internet.MimeBodyPart; 057import jakarta.mail.internet.MimeMessage; 058 059import demo.DemoSMimeUtil; 060import demo.DemoUtil; 061import demo.keystore.CMSKeyStore; 062import demo.smime.DumpMessage; 063 064/** 065 * An ESS triple wrapping demo. Creates a <a href="https://www.rfc-editor.org/rfc/rfc2634.html" target = "_blank">RFC2634</a> 066 * ESS triple wrapped (signed - encrypted - signed) message and subsequently parses it to 067 * decrypt the layer and verify the signatures. 068 * <p> 069 * To run this demo the following packages are required: 070 * <ul> 071 * <li> 072 * <code>iaik_cms.jar</code> 073 * </li> 074 * <li> 075 * <code>iaik_jce(_full).jar</code> (<a href="https://sic.tech/products/core-crypto-toolkits/jca-jce/" target="_blank">IAIK-JCE Core Crypto Library</a>). 076 * </li> 077 * <li> 078 * <a href="https://jakarta.ee/specifications/mail/" target="_blank">Jakarta</a>/<a href="https://eclipse-ee4j.github.io/angus-mail/" target="_blank">Angus</a> Mail 079 * </li> 080 * <li> 081 * <a href="https://jakarta.ee/specifications/activation/" target="_blank">Jakarta Activation Framework</a> 082 * </li> 083 * </ul> 084 * 085 * @see iaik.smime.EncryptedContent 086 * @see iaik.smime.SignedContent 087 */ 088public class TripleWrappingDemo { 089 090 final static boolean DEBUG = false; 091 092 // whether to print dump all generates test messages to System.out 093 final static boolean PRINT_MESSAGES = false; 094 095 String firstName = "John"; 096 String lastName = "SMime"; 097 String to = "smimetest@iaik.at"; // email recipient 098 String from = "smimetest@iaik.at"; // email sender 099 String host = "mailhost"; // name of the mailhost 100 101 X509Certificate[] signerCertificates1; // list of certificates to include in the S/MIME message 102 X509Certificate recipientCertificate; // certificate of the recipient 103 X509Certificate signerCertificate1; // certificate of the signer/sender 104 X509Certificate encryptionCertOfSigner1; // signer uses different certificate for encryption 105 PrivateKey signerPrivateKey1; // private key of the signer/sender 106 107 X509Certificate[] signerCertificates2; // if outer signer is different than inner signer 108 X509Certificate signerCertificate2; // certificate of the signer/sender 109 PrivateKey signerPrivateKey2; // private key of the signer/sender 110 111 /** 112 * Empty default constructor. Reads all required keys and certificates 113 * from the demo keystore (created by running @link demo.keystore.SetupCMSKeySrore) 114 * stored at "cms.keystore" in your current working directoy. 115 */ 116 public TripleWrappingDemo() { 117 118 System.out.println(); 119 System.out.println("******************************************************************************************"); 120 System.out.println("* TripleWrapping demo *"); 121 System.out.println("* (shows the usage of the IAIK-CMS library for creating/parsing a triple wrapped message *"); 122 System.out.println("******************************************************************************************"); 123 System.out.println(); 124 125 // get the certificates from the KeyStore 126 signerCertificates1 = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1); 127 signerPrivateKey1 = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1); 128 signerCertificate1 = signerCertificates1[0]; 129 130 // recipient = signer for this test 131 recipientCertificate = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2)[0]; 132 encryptionCertOfSigner1 = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1)[0]; 133 134 signerCertificates2 = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_2); 135 signerPrivateKey2 = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_2); 136 signerCertificate2 = signerCertificates2[0]; 137 } 138 139 /** 140 * Starts the demo. 141 * 142 * @throws IOException if an I/O related error occurs 143 */ 144 public void start() throws IOException { 145 146 // get the default Session 147 Session session = DemoSMimeUtil.getSession(); 148 149 try { 150 // Create a demo Multipart 151 MimeBodyPart mbp1 = new SMimeBodyPart(); 152 mbp1.setText("This is a Test of the IAIK S/MIME implementation!\n\n"); 153 MimeBodyPart attachment = new SMimeBodyPart(); 154 attachment.setDataHandler(new DataHandler(new FileDataSource("test.html"))); 155 attachment.setFileName("test.html"); 156 157 Multipart mp = new SMimeMultipart(); 158 mp.addBodyPart(mbp1); 159 mp.addBodyPart(attachment); 160 DataHandler multipart = new DataHandler(mp, mp.getContentType()); 161 162 Message msg; // the message to send 163 ByteArrayOutputStream baos = new ByteArrayOutputStream(); // we write to a stream 164 ByteArrayInputStream bais; // we read from a stream 165 166 // 1. implicitly signed - encrypted - implicitly signed; inner = outer signed 167 System.out.println("1. implicitly signed - encrypted - implicitly signed; inner = outer signed"); 168 msg = tripleWrap(session, multipart, true, true, false); 169 msg.writeTo(baos); 170 bais = new ByteArrayInputStream(baos.toByteArray()); 171 msg = new MimeMessage(session, bais); 172 if (PRINT_MESSAGES) { 173 printMessage(msg); 174 } 175 DumpMessage.dumpMsg(msg); 176 baos.reset(); 177 System.out.println("\n\n*****************************************\n\n"); 178 179 // 2. implicitly signed - encrypted - implicitly signed; inner != outer signed 180 System.out.println("2. implicitly signed - encrypted - implicitly signed; inner != outer signed"); 181 msg = tripleWrap(session, multipart, true, true, true); 182 msg.writeTo(baos); 183 bais = new ByteArrayInputStream(baos.toByteArray()); 184 msg = new MimeMessage(session, bais); 185 if (PRINT_MESSAGES) { 186 printMessage(msg); 187 } 188 DumpMessage.dumpMsg(msg); 189 baos.reset(); 190 System.out.println("\n\n*****************************************\n\n"); 191 192 // 3. implicitly signed - encrypted - explicitly signed; inner == outer signed 193 System.out.println("3. implicitly signed - encrypted - explicitly signed; inner = outer signed"); 194 msg = tripleWrap(session, multipart, true, false, false); 195 msg.writeTo(baos); 196 bais = new ByteArrayInputStream(baos.toByteArray()); 197 msg = new MimeMessage(session, bais); 198 if (PRINT_MESSAGES) { 199 printMessage(msg); 200 } 201 DumpMessage.dumpMsg(msg); 202 baos.reset(); 203 System.out.println("\n\n*****************************************\n\n"); 204 205 // 4. implicitly signed - encrypted - explicitly signed; inner != outer signed 206 System.out.println("4. implicitly signed - encrypted - explicitly signed; inner != outer signed"); 207 msg = tripleWrap(session, multipart, true, false, true); 208 msg.writeTo(baos); 209 bais = new ByteArrayInputStream(baos.toByteArray()); 210 msg = new MimeMessage(session, bais); 211 if (PRINT_MESSAGES) { 212 printMessage(msg); 213 } 214 DumpMessage.dumpMsg(msg); 215 baos.reset(); 216 System.out.println("\n\n*****************************************\n\n"); 217 218 // 5. explicitly signed - encrypted - implicitly signed; inner == outer signed 219 System.out.println("5. explicitly signed - encrypted - implicitly signed; inner = outer signed"); 220 msg = tripleWrap(session, multipart, false, true, false); 221 msg.writeTo(baos); 222 bais = new ByteArrayInputStream(baos.toByteArray()); 223 msg = new MimeMessage(session, bais); 224 if (PRINT_MESSAGES) { 225 printMessage(msg); 226 } 227 DumpMessage.dumpMsg(msg); 228 baos.reset(); 229 System.out.println("\n\n*****************************************\n\n"); 230 231 // 6. explicitly signed - encrypted - implicitly signed; inner != outer signed 232 System.out.println("6. explicitly signed - encrypted - implicitly signed; inner != outer signed"); 233 msg = tripleWrap(session, multipart, false, true, true); 234 msg.writeTo(baos); 235 bais = new ByteArrayInputStream(baos.toByteArray()); 236 msg = new MimeMessage(session, bais); 237 if (PRINT_MESSAGES) { 238 printMessage(msg); 239 } 240 DumpMessage.dumpMsg(msg); 241 baos.reset(); 242 System.out.println("\n\n*****************************************\n\n"); 243 244 // 7. explicitly signed - encrypted - explicitly signed; inner == outer signed 245 System.out.println("7. explicitly signed - encrypted - explicitly signed; inner = outer signed"); 246 msg = tripleWrap(session, multipart, false, false, false); 247 msg.writeTo(baos); 248 bais = new ByteArrayInputStream(baos.toByteArray()); 249 msg = new MimeMessage(session, bais); 250 if (PRINT_MESSAGES) { 251 printMessage(msg); 252 } 253 DumpMessage.dumpMsg(msg); 254 baos.reset(); 255 System.out.println("\n\n*****************************************\n\n"); 256 257 // 8. explicitly signed - encrypted - explicitly signed; inner != outer signed 258 System.out.println("8. explicitly signed - encrypted - explicitly signed; inner != outer signed"); 259 msg = tripleWrap(session, multipart, false, false, true); 260 msg.writeTo(baos); 261 bais = new ByteArrayInputStream(baos.toByteArray()); 262 msg = new MimeMessage(session, bais); 263 if (PRINT_MESSAGES) { 264 printMessage(msg); 265 } 266 DumpMessage.dumpMsg(msg); 267 baos.reset(); 268 System.out.println("\n\n*****************************************\n\n"); 269 270 System.out.println("Ready!"); 271 272 } catch (Exception ex) { 273 ex.printStackTrace(); 274 throw new RuntimeException(ex.toString()); 275 } 276 } 277 278 /** 279 * Creates a MIME message container with the given subject for the given session. 280 * 281 * @param session the mail sesion 282 * @param subject the subject of the message 283 * 284 * @return the MIME message with FROM, TO, DATE and SUBJECT headers (without content) 285 * 286 * @throws MessagingException if the message cannot be created 287 */ 288 public Message createMessage(Session session, String subject) throws MessagingException { 289 MimeMessage msg = new MimeMessage(session); 290 msg.setFrom(new InternetAddress(from)); 291 msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to, false)); 292 msg.setSentDate(new Date()); 293 msg.setSubject(subject); 294 return msg; 295 } 296 297 /** 298 * Creates a triple wrapped (signed - encrypted - signed) message. 299 * 300 * @param session the Session 301 * @param dataHandler the data handler providing the raw content 302 * @param innerImplicit whether to sign the inner content implicitly or explicitly 303 * @param outerImplicit whether to sign the outer content implicitly or explicitly 304 * @param differentOuterSigner whether to simulate a different outer signer receiving 305 * the signed and encrypted message and adding an outer 306 * layer 307 * 308 * @return the triple wrapped message 309 */ 310 public Message tripleWrap(Session session, DataHandler dataHandler, 311 boolean innerImplicit, boolean outerImplicit, 312 boolean differentOuterSigner) 313 throws Exception { 314 315 316 StringBuffer buf = new StringBuffer(); 317 String subject = "IAIK-S/MIME: TripleWrapped: "; 318 buf.append("This is a triple wrapped message where "); 319 if (innerImplicit) { 320 subject += "implicit signed - encrypted - "; 321 buf.append("the inner content is implicit signed\n"); 322 } else { 323 subject += "explicit signed - encrypted - "; 324 buf.append("the inner content is explicit signed\n"); 325 } 326 if (outerImplicit) { 327 subject += "implicit signed"; 328 buf.append("and the outer content is implicit signed\n"); 329 } else { 330 subject += "explicit signed"; 331 buf.append("and the outer content is explicit signed\n"); 332 } 333 334 Message msg = createMessage(session, subject); 335 336 337 // create the inner signed content 338 SignedContent sc1 = new SignedContent(innerImplicit); 339 if (dataHandler != null) { 340 sc1.setDataHandler(dataHandler); 341 } else { 342 sc1.setText(buf.toString()); 343 } 344 sc1.setCertificates(signerCertificates1); 345 try { 346 sc1.addSigner((RSAPrivateKey)signerPrivateKey1, signerCertificate1); 347 } catch (NoSuchAlgorithmException ex) { 348 throw new MessagingException("Algorithm not supported: " + ex.getMessage(), ex); 349 } 350 351 // create the enrypted ("middle") layer 352 EncryptedContent ec = new EncryptedContent(sc1); 353 ec.setSMimeType(); 354 // encrypt for the recipient 355 ec.addRecipient(recipientCertificate, (AlgorithmID)AlgorithmID.rsaEncryption.clone()); 356 // I want to be able to decrypt the message, too 357 ec.addRecipient(encryptionCertOfSigner1, (AlgorithmID)AlgorithmID.rsaEncryption.clone()); 358 // set the encryption algorithm 359 ec.setEncryptionAlgorithm((AlgorithmID)AlgorithmID.aes256_CBC.clone(), 256); 360 361 if (differentOuterSigner) { 362 // just do a receiving sending step inbetween 363 Message msg1 = createMessage(session, "IAIK-S/MIME: Signed and encrypted"); 364 msg1.setContent(ec, ec.getContentType()); 365 ec.setHeaders(msg1); 366 msg1.saveChanges(); 367 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 368 msg1.writeTo(baos); 369 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 370 msg1 = new MimeMessage(session, bais); 371 if (PRINT_MESSAGES) { 372 printMessage(msg1); 373 } 374 // optionally parse message here 375 DumpMessage.dumpMsg(msg1); 376 ec = (EncryptedContent)msg1.getContent(); 377 } 378 379 // create the signed outer layer 380 SignedContent sc2 = new SignedContent(ec, outerImplicit); 381 382 if (differentOuterSigner) { 383 sc2.setCertificates(signerCertificates2); 384 try { 385 sc2.addSigner((RSAPrivateKey)signerPrivateKey2, signerCertificate2); 386 } catch (NoSuchAlgorithmException ex) { 387 throw new MessagingException("Algorithm not supported: " + ex.getMessage(), ex); 388 } 389 } else { 390 sc2.setCertificates(signerCertificates1); 391 try { 392 sc2.addSigner((RSAPrivateKey)signerPrivateKey1, signerCertificate1); 393 } catch (NoSuchAlgorithmException ex) { 394 throw new MessagingException("Algorithm not supported: " + ex.getMessage(), ex); 395 } 396 } 397 msg.setContent(sc2, sc2.getContentType()); 398 // let the EncryptedContent update some message headers 399 sc2.setHeaders(msg); 400 msg.saveChanges(); 401 return msg; 402 } 403 404 /** 405 * Prints a dump of the given message to System.out. 406 * 407 * @param msg the message to be dumped to System.out 408 */ 409 private static void printMessage(Message msg) throws IOException { 410 System.out.println("------------------------------------------------------------------"); 411 System.out.println("Message dump: \n"); 412 try { 413 msg.writeTo(System.out); 414 } catch (MessagingException ex) { 415 throw new IOException(ex.getMessage()); 416 } 417 System.out.println("\n------------------------------------------------------------------"); 418 } 419 420 /** 421 * Main method. 422 */ 423 public static void main(String[] argv) throws IOException { 424 425 DemoSMimeUtil.initDemos(); 426 (new TripleWrappingDemo()).start(); 427 System.out.println("\nReady!"); 428 DemoUtil.waitKey(); 429 } 430}