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