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}