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}