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/big/BigSMimeMailDemo.java 23    12.02.25 17:59 Dbratko $
029// $Revision: 23 $
030//
031
032package demo.smime.big;
033
034import java.io.BufferedInputStream;
035import java.io.BufferedOutputStream;
036import java.io.File;
037import java.io.FileInputStream;
038import java.io.FileOutputStream;
039import java.io.IOException;
040import java.io.InputStream;
041import java.io.OutputStream;
042import java.security.NoSuchAlgorithmException;
043import java.security.PrivateKey;
044import java.security.SignatureException;
045import java.security.interfaces.RSAPrivateKey;
046import java.util.Date;
047import java.util.Random;
048
049import jakarta.activation.DataHandler;
050import jakarta.activation.FileDataSource;
051import jakarta.mail.Message;
052import jakarta.mail.MessagingException;
053import jakarta.mail.Part;
054import jakarta.mail.Session;
055import jakarta.mail.internet.InternetAddress;
056import jakarta.mail.internet.MimeMessage;
057
058import demo.DemoSMimeUtil;
059import demo.DemoUtil;
060import demo.keystore.CMSKeyStore;
061import demo.smime.DumpMessage;
062import iaik.asn1.structures.AlgorithmID;
063import iaik.cms.CMSAlgorithmID;
064import iaik.smime.AuthEncryptedContent;
065import iaik.smime.CompressedContent;
066import iaik.smime.EncryptedContent;
067import iaik.smime.SMimeParameters;
068import iaik.smime.SharedFileInputStream;
069import iaik.smime.SignedContent;
070import iaik.utils.CryptoUtils;
071import iaik.utils.Util;
072import iaik.x509.X509Certificate;
073
074/**
075 * This class demonstrates the usage of the IAIK S/MIME implementation for
076 * handling S/MIME message with big content data.
077 * <p>
078 * The only difference to the common usage of this S/MIME library is that
079 * this demo uses a temporary file directory to which the content of the
080 * big messages is written during processing. The temporary directory is
081 * created by calling method {@link iaik.smime.SMimeParameters#setTempDirectory(String, int)
082 * SMimeParameters#setTempDirectory}:
083 * <pre>
084 * int bufSize = 16348;
085 * String tmpDir = ...;
086 * SMimeParameters.setTempDirectory(tmpDir, bufSize);
087 * </pre>
088 * See Javadoc of {@link iaik.smime.SMimeParameters#setTempDirectory(String, int)
089 * SMimeParameters#setTempDirectory} for usage information.
090 * <p>
091 * To run this demo the following packages are required:
092 * <ul>
093 *    <li>
094 *       <code>iaik_cms.jar</code> (IAIK-CMS/SMIME)
095 *    </li>
096 *    <li>
097 *       <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>).
098 *    </li>
099 *    <li>
100 *       <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
101 *    </li>   
102 *    <li>
103 *       <a href="https://jakarta.ee/specifications/activation/" target="_blank">Jakarta Activation Framework</a>
104 *    </li> 
105 * </ul>
106 * The data for this demo is randomly created and stored into a file which is
107 * deleted again at the end of this demo. Note that running this demo may take 
108 * some certain time because it processes some MB of data.
109 */
110public class BigSMimeMailDemo {
111
112    // The directory where to write mails.
113  final static String TEST_DIR = "test";
114  
115  // The name of the data file.
116  final static String DATA_FILE_NAME = TEST_DIR + "/test.dat";
117  
118  // The data size (in bytes).
119  final static int DATA_SIZE = 15000 * 1024; 
120    
121
122  String firstName_ = "John";                     // name of sender
123  String lastName_ = "SMime";
124  String from_ = "smimetest@iaik.tugraz.at";      // email sender
125  String to_ = "smimetest@iaik.tugraz.at";        // email recipient
126  String host_ = "mailhost";                      // name of the mailhost
127
128  X509Certificate[] signerCertificates_;          // list of certificates to include in the S/MIME message
129  X509Certificate recipientCertificate_;          // certificate of the recipient
130  PrivateKey recipientKey_;                       // the private key of the recipient
131  X509Certificate signerCertificate_;             // certificate of the signer/sender
132  X509Certificate encryptionCertOfSigner_;        // signer uses different certificate for encryption
133  PrivateKey signerPrivateKey_;                   // private key of the signer/sender
134
135  
136  /**
137   * Default constructor. Reads certificates and keys from the demo keystore.
138   */
139  public BigSMimeMailDemo() {
140    
141    System.out.println();
142    System.out.println("******************************************************************************************");
143    System.out.println("*                                 BigSMimeMailDemo demo                                  *");
144    System.out.println("*                  (shows how to create and parse big S/MIME messages)                   *");
145    System.out.println("******************************************************************************************");
146    System.out.println();
147    
148    // get the certificates from the KeyStore
149    signerCertificates_ = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
150    signerPrivateKey_ = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
151    signerCertificate_ = signerCertificates_[0];
152
153    // recipient = signer for this test
154    recipientCertificate_ = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2)[0];
155    recipientKey_ = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2);
156    encryptionCertOfSigner_ = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1)[0];
157  }
158  
159  /**
160   * Starts the demo.
161   *
162   * @throws IOException if an I/O related error occurs
163   */
164  public void start() throws IOException {
165    
166    // first we create the big test data file (may take some time
167    createDataFile(DATA_FILE_NAME, DATA_SIZE);
168
169        // get the default Session
170        Session session = DemoSMimeUtil.getSession();
171    
172    // the name of the file holding the test message
173    String fileName;
174    // the file holding the test message
175    File file;
176    // the stream to which to write the mail to a file
177    FileOutputStream fos = null;
178    // the stream from which to read the mail from a file
179    SharedFileInputStream fis = null;
180    
181    // we specify a temp directory to which temporary message contents
182    // shall be written
183    int bufSize = 64 * 1024;
184    SMimeParameters.setTempDirectory(TEST_DIR, bufSize);
185        
186    try {
187      
188      // DataHandler for reading the data
189      DataHandler dataHandler = new DataHandler(new FileDataSource(DATA_FILE_NAME));
190
191      Message msg;    // the message to send
192      
193      // 1. Explicitly signed message
194      System.out.println("Creating explicitly signed message...");
195      // create
196      msg = createSignedMessage(session, dataHandler, false);
197          msg.saveChanges();
198      fileName = TEST_DIR + "/explicitSigned.eml";
199      fos = new FileOutputStream(fileName);
200      System.out.println("Writing explicitly signed message to " + fileName);
201          msg.writeTo(new BufferedOutputStream(fos));
202      fos.close();
203      System.out.println("Explicitly signed message created.");
204      // read
205      file = new File(fileName);
206          fis = new SharedFileInputStream(file);
207      System.out.println("Parsing explicitly signed message from " + fileName);
208          msg = new MimeMessage(session, fis);
209          parse(msg);
210          fis.close();
211      file.delete();
212          System.out.println("\n\n*****************************************\n\n");
213
214
215      // 2. Implicitly signed message
216      msg = createSignedMessage(session, dataHandler, true);
217      System.out.println("creating implicitly signed message...");
218      fileName = TEST_DIR + "/implicitSigned.eml";
219      fos = new FileOutputStream(fileName);
220      System.out.println("Writing implicitly signed message to " + fileName);
221      msg.writeTo(new BufferedOutputStream(fos));
222      fos.close();
223      System.out.println("Implicitly signed message created.");
224      // read
225      file = new File(fileName);
226      fis = new SharedFileInputStream(file);
227      System.out.println("Parsing implicitly signed message from " + fileName);
228      msg = new MimeMessage(session, fis);
229          parse(msg);
230          fis.close();
231      file.delete();
232          System.out.println("\n\n*****************************************\n\n");
233
234      // 3. Encrypted messages (AES)
235      System.out.println("Creating encrypted message [AES/256]...");
236      msg = createEncryptedMessage(session, dataHandler, (AlgorithmID)AlgorithmID.aes256_CBC.clone(), 256, false);
237      fileName = TEST_DIR + "/encrypted.eml";
238      fos = new FileOutputStream(fileName);
239      System.out.println("Writing encrypted message to " + fileName);
240      msg.writeTo(new BufferedOutputStream(fos));
241      fos.close();
242      System.out.println("Encrypted message created.");
243      // read
244      file = new File(fileName);
245      fis = new SharedFileInputStream(file);
246      System.out.println("Parsing encrypted message from " + fileName);
247      msg = new MimeMessage(session, fis);
248      parse(msg);
249      fis.close();
250      file.delete();
251          
252          System.out.println("\n\n*****************************************\n\n");
253
254      // 4. Implicitly signed and encrypted message
255      System.out.println("Creating implicitly signed and encrypted message");
256      msg = createSignedAndEncryptedMessage(session, dataHandler, (AlgorithmID)AlgorithmID.aes256_CBC.clone(), 256, true, false);
257      fileName = TEST_DIR + "/impsigenc.eml";
258      fos = new FileOutputStream(fileName);
259      System.out.println("Writing implicitly signed and encrypted message to " + fileName);
260      msg.writeTo(new BufferedOutputStream(fos));
261      fos.close();
262      System.out.println("Implicitly signed and encrypted message created.");
263      // read
264      file = new File(fileName);
265      fis = new SharedFileInputStream(file);
266      System.out.println("Parsing implicitly signed and encrypted message from " + fileName);
267      msg = new MimeMessage(session, fis);
268      parse(msg);
269      fis.close();
270      file.delete();
271          
272          System.out.println("\n\n*****************************************\n\n");
273
274      // 6. Explicitly signed and encrypted message 
275      System.out.println("Creating explicitly signed and encrypted message");
276      msg = createSignedAndEncryptedMessage(session, dataHandler, (AlgorithmID)AlgorithmID.aes256_CBC.clone(), 256, false, false);
277      fileName = TEST_DIR + "/impsigenc.eml";
278      fos = new FileOutputStream(fileName);
279      System.out.println("Writing explicitly signed and encrypted message to " + fileName);
280      msg.writeTo(new BufferedOutputStream(fos));
281      fos.close();
282      System.out.println("Explicitly signed and encrypted message created.");
283      // read
284      file = new File(fileName);
285      fis = new SharedFileInputStream(file);
286      System.out.println("Parsing explicitly signed and encrypted message from " + fileName);
287      msg = new MimeMessage(session, fis);
288      parse(msg);
289      fis.close();
290      file.delete();
291          
292          System.out.println("\n\n*****************************************\n\n");
293
294          // 7. compressed message
295      System.out.println("Creating compressed message");
296          msg = createCompressedMessage(session, dataHandler, (AlgorithmID)CMSAlgorithmID.zlib_compress.clone());
297      fileName = TEST_DIR + "/compressed.eml";
298      System.out.println("Writing compressed message to " + fileName);
299      fos = new FileOutputStream(fileName);
300      msg.writeTo(new BufferedOutputStream(fos));
301      fos.close();
302      System.out.println("Compressed message created.");
303      // read
304      file = new File(fileName);
305      fis = new SharedFileInputStream(file);
306      System.out.println("Parsing compressed message from " + fileName);
307      msg = new MimeMessage(session, fis);
308      parse(msg);
309      fis.close();
310      file.delete();
311      
312      // 8. Authenticated Encrypted messages (AES)
313      System.out.println("Creating authenticated encrypted message [AES-GCM/256]...");
314      msg = createEncryptedMessage(session, dataHandler, (AlgorithmID)AlgorithmID.aes256_GCM.clone(), 256, true);
315      fileName = TEST_DIR + "/encrypted.eml";
316      fos = new FileOutputStream(fileName);
317      System.out.println("Writing encrypted message to " + fileName);
318      msg.writeTo(new BufferedOutputStream(fos));
319      fos.close();
320      System.out.println("Encrypted message created.");
321      // read
322      file = new File(fileName);
323      fis = new SharedFileInputStream(file);
324      System.out.println("Parsing encrypted message from " + fileName);
325      msg = new MimeMessage(session, fis);
326      parse(msg);
327      fis.close();
328      file.delete();
329      
330      System.out.println("\n\n*****************************************\n\n");
331      
332      
333      // 9. Authenticated Encrypted messages (ChaCha20Poly1305)
334      System.out.println("Creating authenticated encrypted message [ChaCha20Poly1305]...");
335      msg = createEncryptedMessage(session, dataHandler, (AlgorithmID)AlgorithmID.chacha20Poly1305.clone(), 256, true);
336      fileName = TEST_DIR + "/encrypted.eml";
337      fos = new FileOutputStream(fileName);
338      System.out.println("Writing encrypted message to " + fileName);
339      msg.writeTo(new BufferedOutputStream(fos));
340      fos.close();
341      System.out.println("Encrypted message created.");
342      // read
343      file = new File(fileName);
344      fis = new SharedFileInputStream(file);
345      System.out.println("Parsing encrypted message from " + fileName);
346      msg = new MimeMessage(session, fis);
347      parse(msg);
348      fis.close();
349      file.delete();
350      
351      System.out.println("\n\n*****************************************\n\n");
352      
353      // 10. Implicitly signed and authenticated encrypted message
354      System.out.println("Creating implicitly signed and authenticated encrypted message (AES)");
355      msg = createSignedAndEncryptedMessage(session, dataHandler, (AlgorithmID)AlgorithmID.aes256_GCM.clone(), 256, true, true);
356      fileName = TEST_DIR + "/impsigenc.eml";
357      fos = new FileOutputStream(fileName);
358      System.out.println("Writing implicitly signed and encrypted message to " + fileName);
359      msg.writeTo(new BufferedOutputStream(fos));
360      fos.close();
361      System.out.println("Implicitly signed and encrypted message created.");
362      // read
363      file = new File(fileName);
364      fis = new SharedFileInputStream(file);
365      System.out.println("Parsing implicitly signed and encrypted message from " + fileName);
366      msg = new MimeMessage(session, fis);
367      parse(msg);
368      fis.close();
369      file.delete();
370      
371      System.out.println("\n\n*****************************************\n\n");
372
373      // 11. Explicitly signed and authenticated encrypted message 
374      System.out.println("Creating explicitly authenticated signed and encrypted message (AES)");
375      msg = createSignedAndEncryptedMessage(session, dataHandler, (AlgorithmID)AlgorithmID.aes256_GCM.clone(), 256, false, true);
376      fileName = TEST_DIR + "/impsigenc.eml";
377      fos = new FileOutputStream(fileName);
378      System.out.println("Writing explicitly signed and encrypted message to " + fileName);
379      msg.writeTo(new BufferedOutputStream(fos));
380      fos.close();
381      System.out.println("Explicitly signed and encrypted message created.");
382      // read
383      file = new File(fileName);
384      fis = new SharedFileInputStream(file);
385      System.out.println("Parsing explicitly signed and encrypted message from " + fileName);
386      msg = new MimeMessage(session, fis);
387      parse(msg);
388      fis.close();
389      file.delete();
390      
391      System.out.println("\n\n*****************************************\n\n");
392      
393      // 12. Implicitly signed and authenticated encrypted message
394      System.out.println("Creating implicitly signed and authenticated encrypted message (ChaCha20Poly1305)");
395      msg = createSignedAndEncryptedMessage(session, dataHandler, (AlgorithmID)AlgorithmID.chacha20Poly1305.clone(), 256, true, true);
396      fileName = TEST_DIR + "/impsigenc.eml";
397      fos = new FileOutputStream(fileName);
398      System.out.println("Writing implicitly signed and encrypted message to " + fileName);
399      msg.writeTo(new BufferedOutputStream(fos));
400      fos.close();
401      System.out.println("Implicitly signed and encrypted message created.");
402      // read
403      file = new File(fileName);
404      fis = new SharedFileInputStream(file);
405      System.out.println("Parsing implicitly signed and encrypted message from " + fileName);
406      msg = new MimeMessage(session, fis);
407      parse(msg);
408      fis.close();
409      file.delete();
410      
411      System.out.println("\n\n*****************************************\n\n");
412
413      // 13. Explicitly signed and authenticated encrypted message 
414      System.out.println("Creating explicitly authenticated signed and encrypted message (ChaCha20Poly1305)");
415      msg = createSignedAndEncryptedMessage(session, dataHandler, (AlgorithmID)AlgorithmID.chacha20Poly1305.clone(), 256, false, true);
416      fileName = TEST_DIR + "/impsigenc.eml";
417      fos = new FileOutputStream(fileName);
418      System.out.println("Writing explicitly signed and encrypted message to " + fileName);
419      msg.writeTo(new BufferedOutputStream(fos));
420      fos.close();
421      System.out.println("Explicitly signed and encrypted message created.");
422      // read
423      file = new File(fileName);
424      fis = new SharedFileInputStream(file);
425      System.out.println("Parsing explicitly signed and encrypted message from " + fileName);
426      msg = new MimeMessage(session, fis);
427      parse(msg);
428      fis.close();
429      file.delete();
430      
431      System.out.println("\n\n*****************************************\n\n");
432
433
434        } catch (Exception ex) {
435      if (fos != null) {
436        try {
437          fos.close();
438        } catch (Exception e) {
439          // ignore
440        }
441      }
442      if (fis != null) {
443        try {
444          fis.close();
445        } catch (Exception e) {
446          // ignore
447        }
448      }
449      ex.printStackTrace();
450      throw new RuntimeException(ex.toString());
451
452    } finally {
453      // try to delete temporary directory
454      SMimeParameters.deleteTempDirectory();
455      SMimeParameters.setTempDirectory(null, -1);
456      // delete data file
457      File dataFile = new File(DATA_FILE_NAME);
458      dataFile.delete();
459    }
460
461  }
462  
463  
464  /**
465   * Creates a MIME message container with the given subject for the given session.
466   * 
467   * @param session the mail sesion
468   * @param subject the subject of the message
469   *
470   * @return the MIME message with FROM, TO, DATE and SUBJECT headers (without content)
471   *
472   * @throws MessagingException if the message cannot be created
473   */
474  public Message createMessage(Session session, String subject) throws MessagingException {
475    MimeMessage msg = new MimeMessage(session);
476    msg.setFrom(new InternetAddress(from_));
477        msg.setRecipients(Message.RecipientType.TO,     InternetAddress.parse(to_, false));
478        msg.setSentDate(new Date());
479    msg.setSubject(subject);
480    return msg;
481  }
482  
483
484  /**
485   * Creates a signed and encrypted message.
486   *
487   * @param session the mail session
488   * @param dataHandler the content of the message to be signed and encrypted
489   * @param algorithm the content encryption algorithm to be used
490   * @param keyLength the length of the secret content encryption key to be created and used
491   * @param implicit whether to use implicit (application/pkcs7-mime) or explicit
492   *                 (multipart/signed) signing
493   * @param authEncrypt whether to create an authenticated encrypted or an encrypted message
494   * 
495   * @return the signed and encrypted message
496   *
497   * @throws MessagingException if an error occurs when creating the message
498   */
499  public Message createSignedAndEncryptedMessage(Session session, 
500                                                 DataHandler dataHandler,
501                                                 AlgorithmID algorithm,
502                                                 int keyLength,
503                                                 boolean implicit,
504                                                 boolean authEncrypt)
505    throws MessagingException {
506
507    String subject = null;
508    if (implicit) {
509      subject = "IAIK-S/MIME: Implicitly Signed and Encrypted";
510    } else {
511      subject = "IAIK-S/MIME: Explicitly Signed and Encrypted";
512    }
513    Message msg = createMessage(session, subject);
514
515    SignedContent sc = new SignedContent(implicit);
516    sc.setDataHandler(dataHandler);
517    sc.setCertificates(signerCertificates_);
518    try {
519      sc.addSigner((RSAPrivateKey)signerPrivateKey_, signerCertificate_);
520    } catch (NoSuchAlgorithmException ex) {
521      throw new MessagingException("Algorithm not supported: " + ex.getMessage(), ex);
522    }
523
524    EncryptedContent ec = authEncrypt ? new AuthEncryptedContent(sc) : new EncryptedContent(sc);
525    // encrypt for the recipient
526    ec.addRecipient(recipientCertificate_, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
527    // I want to be able to decrypt the message, too
528    ec.addRecipient(encryptionCertOfSigner_, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
529    // set the encryption algorithm
530    try {
531      ec.setEncryptionAlgorithm((AlgorithmID)algorithm.clone(), keyLength);
532    } catch (NoSuchAlgorithmException ex) {
533      throw new MessagingException("Content encryption algorithm not supported: " + ex.getMessage());   
534    }   
535    msg.setContent(ec, ec.getContentType());
536    // let the EncryptedContent update some message headers
537    ec.setHeaders(msg);
538
539    return msg;
540  }
541  
542  /**
543   * Creates a signed message.
544   *
545   * @param session the mail session
546   * @param dataHandler the content of the message to be signed
547   * @param implicit whether to use implicit (application/pkcs7-mime) or explicit
548   *                 (multipart/signed) signing
549   * 
550   * @return the signed message
551   *
552   * @throws MessagingException if an error occurs when creating the message
553   */
554  public Message createSignedMessage(Session session, DataHandler dataHandler, boolean implicit)
555      throws MessagingException {
556
557    String subject = null;
558    StringBuffer buf = new StringBuffer();
559    
560    if (implicit) {
561      subject = "IAIK-S/MIME: Implicitly Signed";
562      buf.append("This message is implicitly signed!\n");
563      buf.append("You need an S/MIME aware mail client to view this message.\n");
564      buf.append("\n\n");
565    } else {
566      subject = "IAIK-S/MIME: Explicitly Signed";
567      buf.append("This message is explicitly signed!\n");
568      buf.append("Every mail client can view this message.\n");
569      buf.append("Non S/MIME mail clients will show the signature as attachment.\n");
570      buf.append("\n\n");
571    }
572    
573    Message msg = createMessage(session, subject);
574
575    SignedContent sc = new SignedContent(implicit);
576
577    if (dataHandler != null) {
578      sc.setDataHandler(dataHandler);
579    } else {
580      sc.setText(buf.toString());
581    }
582    sc.setCertificates(signerCertificates_);
583
584    try {
585      sc.addSigner((RSAPrivateKey)signerPrivateKey_, signerCertificate_);
586    } catch (NoSuchAlgorithmException ex) {
587      throw new MessagingException("Algorithm not supported: " + ex.getMessage(), ex);
588    }
589
590    msg.setContent(sc, sc.getContentType());
591    // let the SignedContent update some message headers
592    sc.setHeaders(msg);
593    return msg;
594  }
595  
596  /**
597   * Creates an encrypted message.
598   *
599   * @param session the mail session
600   * @param dataHandler the dataHandler providing the content to be encrypted
601   * @param algorithm the content encryption algorithm to be used
602   * @param keyLength the length of the secret content encryption key to be created and used
603   * @param authEncrypt whether to create an authenticated encrypted or an encrypted message
604   * 
605   * @return the encrypted message
606   *
607   * @throws MessagingException if an error occurs when creating the message
608   */
609  public Message createEncryptedMessage(Session session,
610                                        DataHandler dataHandler,
611                                        AlgorithmID algorithm,
612                                        int keyLength,
613                                        boolean authEncrypt)
614    throws MessagingException {
615
616    StringBuffer subject = new StringBuffer();
617    subject.append("IAIK-S/MIME: " + (authEncrypt ? "Authenticated " : "") + "Encrypted ["+algorithm.getName());
618    if (keyLength > 0) {
619      subject.append("/"+keyLength);
620    }  
621    subject.append("]");
622    Message msg = createMessage(session, subject.toString());
623
624    EncryptedContent ec = authEncrypt ? new AuthEncryptedContent() : new EncryptedContent();
625
626    
627    ec.setDataHandler(dataHandler);
628    // encrypt for the recipient
629    ec.addRecipient(recipientCertificate_, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
630    // I want to be able to decrypt the message, too
631    ec.addRecipient(encryptionCertOfSigner_, (AlgorithmID)AlgorithmID.rsaEncryption.clone());
632    try {
633      ec.setEncryptionAlgorithm(algorithm, keyLength);
634    } catch (NoSuchAlgorithmException ex) {
635      throw new MessagingException("Content encryption algorithm not supported: " + ex.getMessage());   
636    }   
637
638    msg.setContent(ec, ec.getContentType());
639    // let the EncryptedContent update some message headers
640    ec.setHeaders(msg);
641
642    return msg;
643  }
644  
645  /**
646   * Creates a compressed message.
647   *
648   * @param session the mail session
649   * @param dataHandler the datahandler supplying the content to be compressed
650   * @param algorithm the compression algorithm to be used
651   * 
652   * @return the compressed message
653   *
654   * @throws MessagingException if an error occurs when creating the message
655   */
656  public Message createCompressedMessage(Session session, DataHandler dataHandler, AlgorithmID algorithm)
657      throws MessagingException {
658
659    String subject = "IAIK-S/MIME: Compressed ["+algorithm.getName()+"]";
660    Message msg = createMessage(session, subject.toString());
661
662    CompressedContent compressedContent = new CompressedContent();
663    compressedContent.setDataHandler(dataHandler);   
664    
665    try {
666      compressedContent.setCompressionAlgorithm(algorithm);
667    } catch (NoSuchAlgorithmException ex) {
668      throw new MessagingException("Compression algorithm not supported: " + ex.getMessage());   
669    }   
670
671    msg.setContent(compressedContent, compressedContent.getContentType());
672    // let the CompressedContent update some message headers
673    compressedContent.setHeaders(msg);
674
675    return msg;
676  }
677  
678  /**
679   * Parses the given object (message, part, ... ).
680   * 
681   * @param o the object (message, part, ... ) to be parsed
682   * 
683   * @throws Exception if an exception occurs while parsing
684   */
685  public void parse(Object o) throws Exception {
686    DumpMessage dumpMsg = new DumpMessage();
687    if (o instanceof Message) {
688      dumpMsg.dumpEnvelope((Message)o);
689    }
690    if (o instanceof Part) {
691      System.out.println("CONTENT-TYPE: "+((Part)o).getContentType());
692      o = ((Part)o).getContent();
693    }
694
695    if (o instanceof EncryptedContent) {
696      
697      // encrypted
698      System.out.println("This message is encrypted!");
699
700      EncryptedContent ec = (EncryptedContent)o;
701      ec.decryptSymmetricKey(recipientKey_, recipientCertificate_);
702      parse(ec.getContent());
703      
704    } else if (o instanceof SignedContent) {
705      
706      // signed
707      System.out.println("This message is signed!");
708
709      SignedContent sc = (SignedContent)o;
710
711      X509Certificate signer = null;
712      try {
713        signer = sc.verify();
714        System.out.println("This message is signed from: "+signer.getSubjectDN());
715      } catch (SignatureException ex) {
716        throw new SignatureException("Signature verification error: " + ex.toString());
717      }
718      
719      parse(sc.getContent());
720      
721    } else if (o instanceof CompressedContent) {
722      
723      System.out.println("The content of this message is compressed.");
724      CompressedContent compressed = (CompressedContent)o;
725      parse(compressed.getContent());  
726    
727    } else if (o instanceof InputStream) {
728
729       // we already know that the content is an input stream (thus we must not check for other content values)
730      System.out.println("Content is just an input stream.");
731      System.out.println("---------------------------");
732      InputStream is = (InputStream)o;
733      // read content 
734      if (readContent(is) == false) {
735        throw new Exception("Content not equal to original one!");
736      }
737      
738    } else {
739      throw new Exception("Unexpected object!");
740    }
741  }
742  
743  
744  /**
745   * Reads the content from the given input stream, writes it
746   * to a temp file and then compares the tmp file with the 
747   * original data file.
748   * 
749   * @param content the content to be written
750   * 
751   * @return <code>true</code> if the content is equal to the original one,
752   *         <code>false</code> if it differs from the original one
753   * @throws IOException
754   */
755  private final static boolean readContent(InputStream content) throws IOException {
756    String fileName = TEST_DIR + "/tmp.dat";
757    
758    // file stream to which to write content
759    FileOutputStream fos = null;
760    // file stream from which to read content for comparison 
761    FileInputStream fis = null;
762    // temp file to which write / from which read content for comparison
763    File file = null;
764    // file stream from which to read original content for comparison 
765    FileInputStream origFis = null;
766    try {
767      //  write to file
768      System.out.println("Write content to " + fileName);
769      fos = new FileOutputStream(fileName);
770      InputStream in = new BufferedInputStream(content);
771      OutputStream out = new BufferedOutputStream(fos);
772      Util.copyStream(in,
773                      out,
774                      new byte[8192]);
775      out.flush();
776      fos.close();
777      fos = null;
778      
779      // compare contents
780      file = new File(fileName);
781      fis = new FileInputStream(file);
782      origFis = new FileInputStream(DATA_FILE_NAME);
783      System.out.println("Compare with original content"); 
784      
785      boolean equal = equals(new BufferedInputStream(fis), new BufferedInputStream(origFis));
786      if (equal) {
787        System.out.println("Content ok");
788      }
789      return equal;
790    } finally {
791      if (origFis != null) {
792        try {
793          origFis.close();
794        } catch (IOException ex) {
795          // ignore
796        }
797      }
798      if (fis != null) {
799        try {
800          fis.close();
801        } catch (IOException ex) {
802          // ignore
803        }
804        // delete tmp file
805        try {
806          file.delete();
807        } catch (Exception ex) {
808          // ignore
809        }
810      }
811      if (fos != null) {
812        try {
813          fos.close();
814        } catch (IOException ex) {
815          // ignore
816        }
817      }
818      
819    }
820  }
821   
822  
823  
824  
825  /**
826   * Creates a file of the given size and fills it with random
827   * data. The file is written to the directory used by this
828   * demo. If the directory does not exist it is created.
829   * 
830   * @param fileName the name of the file to be created
831   * @param size the size (in bytes) of the file
832   * 
833   * @throws IOException if an exception occurs during creating/writing the
834   *                     file
835   */
836  private final static void createDataFile(String fileName, int size) 
837    throws IOException {
838    // create output directory
839    File dir = new File(TEST_DIR);
840    if (dir.exists() == false) {
841      dir.mkdir();
842    }
843    // create big data file
844    System.out.println("Creating " + size + " b data file " + fileName);
845    OutputStream os = null;
846    try {
847      Random random = new Random();
848      os = new BufferedOutputStream(new FileOutputStream(fileName));
849      int bufSize = 8192;
850      byte[] buf = new byte[bufSize];
851      int blockSize = size / bufSize;
852      for (int i = 0; i < blockSize; i++) {
853        random.nextBytes(buf);
854        os.write(buf);
855        os.flush();
856      }
857      // write the rest
858      buf = new byte[size - blockSize * bufSize];
859      random.nextBytes(buf);
860      os.write(buf);
861      os.flush();
862      System.out.println("Data file " + fileName + " created.");
863    } finally {
864      if (os != null) {
865        try {
866          os.close();
867        } catch (IOException ex) {
868          // ignore
869        }
870      }
871    }
872  }
873  
874  /**
875   * Compares the data read from the two input streams.
876   * 
877   * @param source1 the first input stream
878   * @param source2 the second input stream
879   * 
880   * @return true if the data is equal, false if it is not equal
881   * 
882   * @throws IOException if an exception occurs
883   */
884  private final static boolean equals(InputStream source1, InputStream source2)
885      throws IOException
886  {
887
888    boolean equals;
889    int bufSize = 8192;
890    if (source1 != source2) {
891      if ((source1 != null) && (source2 != null)) {
892        byte[] buffer1 = new byte[bufSize];
893        byte[] buffer2 = new byte[bufSize];
894        if (buffer1.length > buffer2.length) {
895          // swap
896          byte[] temp = buffer1;
897          buffer1 = buffer2;
898          buffer2 = temp;
899        }
900
901        equals = true;
902        int bytesRead1;
903        while ((bytesRead1 = source1.read(buffer1)) >= 0) {
904          int bytesRead2;
905          int totalBytesRead2 = 0;
906          while (((bytesRead2 = source2.read(buffer2, totalBytesRead2, (bytesRead1 - totalBytesRead2))) >= 0) 
907                 && (totalBytesRead2 < bytesRead1)) {
908            totalBytesRead2 += bytesRead2;
909          }
910          if (totalBytesRead2 == bytesRead1) {
911            if (!CryptoUtils.equalsBlock(buffer1, 0, buffer2, 0, bytesRead1)) {
912              equals = false;
913              break;
914            }
915          } else {
916            equals = false;
917            break;
918          }
919        }
920         if (source2.read(buffer2) >= 0) {
921           // there has been data left in stream 2
922           equals = false;
923         }
924      } else {
925        equals = false;
926      }
927    } else {
928      equals = true;
929    }
930    
931    return equals ;
932  }
933
934
935
936  /**
937   * The main method.
938   */
939  public static void main(String[] argv) throws IOException {
940
941    DemoSMimeUtil.initDemos();
942        (new BigSMimeMailDemo()).start();
943    System.out.println("\nReady!");
944    DemoUtil.waitKey();
945  }
946}