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/basic/SMimeOaepPssDemo.java 8 12.02.25 17:58 Dbratko $ 029// $Revision: 8 $ 030// 031 032package demo.smime.basic; 033 034import iaik.asn1.structures.AlgorithmID; 035import iaik.cms.CMSException; 036import iaik.cms.Utils; 037import iaik.smime.EncryptedContent; 038import iaik.smime.SMimeBodyPart; 039import iaik.smime.SMimeMultipart; 040import iaik.smime.SignedContent; 041import iaik.x509.X509Certificate; 042 043import java.io.ByteArrayInputStream; 044import java.io.ByteArrayOutputStream; 045import java.io.IOException; 046import java.security.InvalidAlgorithmParameterException; 047import java.security.NoSuchAlgorithmException; 048import java.security.PrivateKey; 049import java.util.Date; 050 051import jakarta.activation.DataHandler; 052import jakarta.activation.FileDataSource; 053import jakarta.mail.Message; 054import jakarta.mail.MessagingException; 055import jakarta.mail.Multipart; 056import jakarta.mail.Session; 057import jakarta.mail.internet.InternetAddress; 058import jakarta.mail.internet.MimeBodyPart; 059import jakarta.mail.internet.MimeMessage; 060 061import demo.DemoSMimeUtil; 062import demo.DemoUtil; 063import demo.cms.envelopedData.OaepEnvelopedDataDemo; 064import demo.cms.signedData.PssSignedDataDemo; 065import demo.keystore.CMSKeyStore; 066import demo.smime.DumpMessage; 067 068/** 069 * This class demonstrates the usage of the IAIK S/MIME implementation using RSA-PSS for 070 * signing, RSA-OAEP for key transport and AES-256 CBC for content encryption. It shows how to create 071 * signed and/or encrypted S/MIME messages and how to parse them and verify the signature 072 * and decrypt the content, respectively. 073 * <p> 074 * To run this demo the following packages are required: 075 * <ul> 076 * <li> 077 * <code>iaik_cms.jar</code> (IAIK-CMS/SMIME) 078 * </li> 079 * <li> 080 * <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>). 081 * </li> 082 * <li> 083 * <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 084 * </li> 085 * <li> 086 * <a href="https://jakarta.ee/specifications/activation/" target="_blank">Jakarta Activation Framework</a> 087 * </li> 088 * </ul> 089 */ 090public class SMimeOaepPssDemo { 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"; // name of sender 096 String lastName_ = "SMime"; 097 String from_ = "smimetest@iaik.tugraz.at"; // email sender 098 String to_ = "smimetest@iaik.tugraz.at"; // email recipient 099 String host_ = "mailhost"; // name of the mailhost 100 101 X509Certificate[] signerCertificates_; // list of certificates to include in the S/MIME message 102 X509Certificate recipientCertificate_; // certificate of the recipient 103 X509Certificate signerCertificate_; // certificate of the signer/sender 104 PrivateKey signerPrivateKey_; // private key of the signer/sender 105 106 AlgorithmID hashID_; // the hash algorithm to be used 107 int saltLength_; // the salt length to be used 108 109 /** 110 * Default constructor. Reads certificates and keys from the demo keystore. 111 */ 112 public SMimeOaepPssDemo() { 113 114 System.out.println(); 115 System.out.println("******************************************************************************************"); 116 System.out.println("* SMimeDemo demo *"); 117 System.out.println("* (shows how to create and parse (verify, decrypt) signed and encrypted S/MIME messages) *"); 118 System.out.println("******************************************************************************************"); 119 System.out.println(); 120 121 // get the certificates from the KeyStore 122 signerCertificates_ = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1); 123 signerPrivateKey_ = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1); 124 signerCertificate_ = signerCertificates_[0]; 125 126 // recipient = signer for this test 127 recipientCertificate_ = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1)[0]; 128 129 hashID_ = AlgorithmID.sha256; 130 saltLength_ = 32; 131 } 132 133 /** 134 * Starts the demo. 135 * 136 * @throws IOException if an I/O related error occurs 137 */ 138 public void start() throws IOException { 139 140 // get the default Session 141 Session session = DemoSMimeUtil.getSession(); 142 143 try { 144 // Create a demo Multipart 145 MimeBodyPart mbp1 = new SMimeBodyPart(); 146 mbp1.setText("This is a Test of the IAIK S/MIME implementation!\n\n"); 147 // attachment 148 MimeBodyPart attachment = new SMimeBodyPart(); 149 attachment.setDataHandler(new DataHandler(new FileDataSource("test.html"))); 150 attachment.setFileName("test.html"); 151 152 Multipart mp = new SMimeMultipart(); 153 mp.addBodyPart(mbp1); 154 mp.addBodyPart(attachment); 155 DataHandler multipart = new DataHandler(mp, mp.getContentType()); 156 157 Message msg; // the message to send 158 ByteArrayOutputStream baos = new ByteArrayOutputStream(); // we write to a stream 159 ByteArrayInputStream bais; // we read from a stream 160 161 // 1. This is a plain message 162 msg = createPlainMessage(session, multipart); 163 System.out.println("creating plain message..."); 164 msg.saveChanges(); 165 msg.writeTo(baos); 166 bais = new ByteArrayInputStream(baos.toByteArray()); 167 msg = new MimeMessage(session, bais); 168 if (PRINT_MESSAGES) { 169 printMessage(msg); 170 } 171 DumpMessage.dumpMsg(msg); 172 173 System.out.println("\n\n*****************************************\n\n"); 174 175 // 2. This is an explicitly signed message 176 msg = createSignedMessage(session, multipart, false); 177 System.out.println("creating explicitly signed message..."); 178 baos.reset(); 179 msg.saveChanges(); 180 msg.writeTo(baos); 181 bais = new ByteArrayInputStream(baos.toByteArray()); 182 msg = new MimeMessage(session, bais); 183 if (PRINT_MESSAGES) { 184 printMessage(msg); 185 } 186 DumpMessage.dumpMsg(msg); 187 188 System.out.println("\n\n*****************************************\n\n"); 189 190 191 // 3. This is an implicitly signed message 192 msg = createSignedMessage(session, multipart, true); 193 System.out.println("creating implicitly signed message..."); 194 baos.reset(); 195 msg.saveChanges(); 196 msg.writeTo(baos); 197 bais = new ByteArrayInputStream(baos.toByteArray()); 198 msg = new MimeMessage(session, bais); 199 if (PRINT_MESSAGES) { 200 printMessage(msg); 201 } 202 DumpMessage.dumpMsg(msg); 203 204 System.out.println("\n\n*****************************************\n\n"); 205 206 // 4. Now create encrypted message 207 208 msg = createEncryptedMessage(session, (AlgorithmID)AlgorithmID.aes256_CBC.clone(), 256); 209 System.out.println("creating encrypted message [AES/256]..."); 210 baos.reset(); 211 msg.saveChanges(); 212 msg.writeTo(baos); 213 bais = new ByteArrayInputStream(baos.toByteArray()); 214 msg = new MimeMessage(session, bais); 215 if (PRINT_MESSAGES) { 216 printMessage(msg); 217 } 218 DumpMessage.dumpMsg(msg); 219 220 System.out.println("\n\n*****************************************\n\n"); 221 222 223 224 // 5. Now create a implicitly signed and encrypted message with attachment 225 System.out.println("creating implicitly signed and encrypted message [AES/256]..."); 226 msg = createSignedAndEncryptedMessage(session, multipart, true); 227 baos.reset(); 228 msg.saveChanges(); 229 msg.writeTo(baos); 230 bais = new ByteArrayInputStream(baos.toByteArray()); 231 msg = new MimeMessage(session, bais); 232 if (PRINT_MESSAGES) { 233 printMessage(msg); 234 } 235 DumpMessage.dumpMsg(msg); 236 237 System.out.println("\n\n*****************************************\n\n"); 238 239 // 6. Now create a explicitly signed and encrypted message with attachment 240 System.out.println("creating explicitly signed and encrypted message [AES/256]..."); 241 msg = createSignedAndEncryptedMessage(session, multipart, false); 242 baos.reset(); 243 msg.saveChanges(); 244 msg.writeTo(baos); 245 bais = new ByteArrayInputStream(baos.toByteArray()); 246 msg = new MimeMessage(session, bais); 247 if (PRINT_MESSAGES) { 248 printMessage(msg); 249 } 250 DumpMessage.dumpMsg(msg); 251 252 System.out.println("\n\n*****************************************\n\n"); 253 254 255 256 } catch (Exception ex) { 257 ex.printStackTrace(); 258 throw new RuntimeException(ex.toString()); 259 } 260 } 261 262 /** 263 * Creates a MIME message container with the given subject for the given session. 264 * 265 * @param session the mail sesion 266 * @param subject the subject of the message 267 * 268 * @return the MIME message with FROM, TO, DATE and SUBJECT headers (without content) 269 * 270 * @throws MessagingException if the message cannot be created 271 */ 272 public Message createMessage(Session session, String subject) throws MessagingException { 273 MimeMessage msg = new MimeMessage(session); 274 msg.setFrom(new InternetAddress(from_)); 275 msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to_, false)); 276 msg.setSentDate(new Date()); 277 msg.setSubject(subject); 278 return msg; 279 } 280 281 /** 282 * Creates a simple plain (neither signed nor encrypted) message. 283 * 284 * @param session the mail session 285 * @param dataHandler the content of the message 286 * 287 * @return the plain message 288 * 289 * @throws MessagingException if an error occurs when creating the message 290 */ 291 public Message createPlainMessage(Session session, DataHandler dataHandler) throws MessagingException { 292 293 Message msg = createMessage(session, "IAIK-S/MIME: Plain message"); 294 if (dataHandler != null) { 295 msg.setDataHandler(dataHandler); 296 } else { 297 msg.setText("This is a plain message!\nIt is wether signed nor encrypted!\n"); 298 } 299 return msg; 300 } 301 302 /** 303 * Creates a signed and encrypted message. 304 * 305 * @param session the mail session 306 * @param dataHandler the content of the message to be signed and encrypted 307 * @param implicit whether to use implicit (application/pkcs7-mime) or explicit 308 * (multipart/signed) signing 309 * 310 * @return the signed and encrypted message 311 * 312 * @throws MessagingException if an error occurs when creating the message 313 */ 314 public Message createSignedAndEncryptedMessage(Session session, DataHandler dataHandler, boolean implicit) 315 throws MessagingException { 316 317 String subject = null; 318 String text = null; 319 if (implicit) { 320 subject = "IAIK-S/MIME: Implicitly Signed and Encrypted"; 321 text = "This message is implicitly signed and encrypted!\n\n\n"; 322 } else { 323 subject = "IAIK-S/MIME: Explicitly Signed and Encrypted"; 324 text = "This message is explicitly signed and encrypted!\n\n\n"; 325 } 326 Message msg = createMessage(session, subject); 327 328 SignedContent sc = new SignedContent(implicit); 329 if (dataHandler != null) { 330 sc.setDataHandler(dataHandler); 331 } else { 332 sc.setText(text); 333 } 334 sc.setCertificates(signerCertificates_); 335 try { 336 AlgorithmID mgfID = (AlgorithmID)AlgorithmID.mgf1.clone(); 337 AlgorithmID rsaPssID = PssSignedDataDemo.createPssAlgorithmID(hashID_, mgfID, saltLength_); 338 sc.addSigner(signerPrivateKey_, signerCertificate_, hashID_, rsaPssID); 339 } catch (NoSuchAlgorithmException ex) { 340 throw new MessagingException("Algorithm not supported: " + ex.getMessage(), ex); 341 } catch (InvalidAlgorithmParameterException ex) { 342 throw new MessagingException("Cannot create PSS parameters: " + ex.getMessage(), ex); 343 } 344 345 346 EncryptedContent ec = new EncryptedContent(sc); 347 // encrypt for the recipient 348 try { 349 // empty label 350 byte[] label = {}; 351 AlgorithmID rsaOaepID = Utils.createOaepAlgorithmID(hashID_); 352 ec.addRecipient(recipientCertificate_, rsaOaepID); 353 ec.setEncryptionAlgorithm((AlgorithmID)AlgorithmID.aes256_CBC.clone(), 256); 354 } catch (NoSuchAlgorithmException ex) { 355 throw new MessagingException("Content encryption algorithm not supported: " + ex.getMessage()); 356 } catch (Exception ex) { 357 throw new MessagingException("Cannot create OAEP parameters: " + ex.getMessage()); 358 } 359 msg.setContent(ec, ec.getContentType()); 360 // let the EncryptedContent update some message headers 361 ec.setHeaders(msg); 362 363 return msg; 364 } 365 366 /** 367 * Creates a signed message. 368 * 369 * @param session the mail session 370 * @param dataHandler the content of the message to be signed 371 * @param implicit whether to use implicit (application/pkcs7-mime) or explicit 372 * (multipart/signed) signing 373 * 374 * @return the signed message 375 * 376 * @throws MessagingException if an error occurs when creating the message 377 */ 378 public Message createSignedMessage(Session session, 379 DataHandler dataHandler, boolean implicit) 380 throws MessagingException { 381 382 String subject = null; 383 StringBuffer buf = new StringBuffer(); 384 385 if (implicit) { 386 subject = "IAIK-S/MIME: Implicitly Signed"; 387 buf.append("This message is implicitly signed!\n"); 388 buf.append("You need an S/MIME aware mail client to view this message.\n"); 389 buf.append("\n\n"); 390 } else { 391 subject = "IAIK-S/MIME: Explicitly Signed"; 392 buf.append("This message is explicitly signed!\n"); 393 buf.append("Every mail client can view this message.\n"); 394 buf.append("Non S/MIME mail clients will show the signature as attachment.\n"); 395 buf.append("\n\n"); 396 } 397 398 Message msg = createMessage(session, subject); 399 400 SignedContent sc = new SignedContent(implicit); 401 402 if (dataHandler != null) { 403 sc.setDataHandler(dataHandler); 404 } else { 405 sc.setText(buf.toString()); 406 } 407 sc.setCertificates(signerCertificates_); 408 409 try { 410 AlgorithmID mgfID = (AlgorithmID)AlgorithmID.mgf1.clone(); 411 AlgorithmID rsaPssID = PssSignedDataDemo.createPssAlgorithmID(hashID_, mgfID, saltLength_); 412 sc.addSigner(signerPrivateKey_, signerCertificate_, hashID_, rsaPssID); 413 } catch (NoSuchAlgorithmException ex) { 414 throw new MessagingException("Algorithm not supported: " + ex.getMessage(), ex); 415 } catch (InvalidAlgorithmParameterException ex) { 416 throw new MessagingException("Cannot create PSS parameters: " + ex.getMessage(), ex); 417 } 418 419 msg.setContent(sc, sc.getContentType()); 420 // let the SignedContent update some message headers 421 sc.setHeaders(msg); 422 return msg; 423 } 424 425 /** 426 * Creates an encrypted message. 427 * 428 * @param session the mail session 429 * @param algorithm the content encryption algorithm to be used 430 * @param keyLength the length of the secret content encryption key to be created and used 431 * 432 * @return the encrypted message 433 * 434 * @throws MessagingException if an error occurs when creating the message 435 */ 436 public Message createEncryptedMessage(Session session, AlgorithmID algorithm, int keyLength) 437 throws MessagingException { 438 439 StringBuffer subject = new StringBuffer(); 440 subject.append("IAIK-S/MIME: Encrypted ["+algorithm.getName()); 441 if (keyLength > 0) { 442 subject.append("/"+keyLength); 443 } 444 subject.append("]"); 445 Message msg = createMessage(session, subject.toString()); 446 447 EncryptedContent ec = new EncryptedContent(); 448 449 StringBuffer buf = new StringBuffer(); 450 buf.append("This is the encrypted content!\n"); 451 buf.append("Content encryption algorithm: "+algorithm.getName()); 452 buf.append("\n\n"); 453 454 ec.setText(buf.toString()); 455 456 // encrypt for the recipient 457 try { 458 AlgorithmID mgfID = (AlgorithmID)AlgorithmID.mgf1.clone(); 459 AlgorithmID pSourceID = (AlgorithmID)AlgorithmID.pSpecified.clone(); 460 // empty label 461 byte[] label = {}; 462 AlgorithmID rsaOaepID = Utils.createOaepAlgorithmID(hashID_); 463 ec.addRecipient(recipientCertificate_, rsaOaepID); 464 ec.setEncryptionAlgorithm(algorithm, keyLength); 465 } catch (NoSuchAlgorithmException ex) { 466 throw new MessagingException("Content encryption algorithm not supported: " + ex.getMessage()); 467 } catch (Exception ex) { 468 throw new MessagingException("Cannot create OAEP parameters: " + ex.getMessage()); 469 } 470 471 msg.setContent(ec, ec.getContentType()); 472 // let the EncryptedContent update some message headers 473 ec.setHeaders(msg); 474 475 return msg; 476 } 477 478 479 480 481 482 /** 483 * Prints a dump of the given message to System.out. 484 * 485 * @param msg the message to be dumped to System.out 486 */ 487 static void printMessage(Message msg) throws IOException { 488 System.out.println("------------------------------------------------------------------"); 489 System.out.println("Message dump: \n"); 490 try { 491 msg.writeTo(System.out); 492 } catch (MessagingException ex) { 493 throw new IOException(ex.getMessage()); 494 } 495 System.out.println("\n------------------------------------------------------------------"); 496 } 497 498 499 /** 500 * The main method. 501 */ 502 public static void main(String[] argv) throws IOException { 503 504 DemoSMimeUtil.initDemos(); 505 (new SMimeOaepPssDemo()).start(); 506 System.out.println("\nReady!"); 507 DemoUtil.waitKey(); 508 } 509}