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/pkcs11/SignedMailDemo.java 12    12.02.25 17:59 Dbratko $
059    // $Revision: 12 $
060    //
061    
062    package demo.smime.pkcs11;
063    
064    import java.io.ByteArrayInputStream;
065    import java.io.ByteArrayOutputStream;
066    import java.io.IOException;
067    import java.security.GeneralSecurityException;
068    import java.security.Key;
069    import java.security.NoSuchAlgorithmException;
070    import java.security.PrivateKey;
071    import java.security.cert.Certificate;
072    import java.util.Date;
073    import java.util.Enumeration;
074    
075    import javax.activation.DataHandler;
076    import javax.activation.FileDataSource;
077    import javax.mail.Message;
078    import javax.mail.MessagingException;
079    import javax.mail.Multipart;
080    import javax.mail.Session;
081    import javax.mail.internet.InternetAddress;
082    import javax.mail.internet.MimeBodyPart;
083    import javax.mail.internet.MimeMessage;
084    
085    import demo.DemoSMimeUtil;
086    import demo.cms.pkcs11.PKCS11Demo;
087    import demo.smime.DumpMessage;
088    // class and interface imports
089    import iaik.smime.SMimeBodyPart;
090    import iaik.smime.SMimeMultipart;
091    import iaik.smime.SMimeUtil;
092    import iaik.smime.SignedContent;
093    import iaik.utils.Util;
094    import iaik.x509.X509Certificate;
095    
096    
097    /**
098     * Base class of signed mail demos using PKCS#11 for accessing
099     * the signer key on a smart card. 
100     */
101    public abstract class SignedMailDemo extends PKCS11Demo {
102      
103      // whether to print dump all generates test messages to System.out
104      final static boolean PRINT_MESSAGES = true;   
105      
106      /**
107       * Default email address. Used in this demo if signer certificate
108       * does not contain an email address.
109       */
110      private final static String DEFAULT_EMAIL = "smimetest@iaik.tugraz.at";
111      
112       /**
113       * The private key of the signer. In this case only a proxy object, but the
114       * application cannot see this.
115       */
116      protected PrivateKey signerKey_;
117    
118      /**
119       * The certificate chain of the signer. In contrast to the
120       * private signer key, the certificate holds the actual public keying material.
121       */
122      protected X509Certificate[] signerCertificates_;
123      
124      /**
125       * The email address of the sender.
126       */
127      protected String sender_;
128      
129      /**
130       * The email address of the recipient.
131       */
132      protected String recipient_;
133    
134      /**
135       * Creates a SignedMailDemo object for the given module name.
136       * 
137       * @param moduleName the name of the module
138       * @param userPin the user-pin (password) for the TokenKeyStore
139       *                (may be <code>null</code> to pou-up a dialog asking for the pin)
140       */
141      protected SignedMailDemo(String moduleName, char[] userPin) {
142        // install provider in super class    
143        super(moduleName, userPin);
144      }
145    
146      /**
147       * This method gets the key stores of all inserted (compatible) smart
148       * cards and simply takes the first key-entry. From this key entry it
149       * takes the private key and the certificate to retrieve the public key
150       * from. The keys are stored in the member variables <code>signerKey_
151       * </code> and <code>signerCertificate_</code>.
152       *
153       * @throws GeneralSecurityException If anything with the provider fails.
154       * @throws IOException If loading the key store fails.
155       */
156      protected void getSignatureKey() throws GeneralSecurityException, IOException
157      {
158        // we simply take the first keystore, if there are serveral
159        Enumeration aliases = tokenKeyStore_.aliases();
160    
161        // and we take the first signature (private) key for simplicity
162        PrivateKey privateKey = null;
163        X509Certificate[] certificates = null;
164        while (aliases.hasMoreElements()) {
165          String keyAlias = aliases.nextElement().toString();
166          Key key = null;
167          try {
168            key = tokenKeyStore_.getKey(keyAlias, null);
169          } catch (NoSuchAlgorithmException ex) {
170            throw new GeneralSecurityException(ex.toString());
171          }
172    
173          if (key instanceof PrivateKey) {
174            Certificate[] certificateChain = tokenKeyStore_.getCertificateChain(keyAlias);
175            if ((certificateChain != null) && (certificateChain.length > 0)) {
176              X509Certificate[] signerCertificates = Util.convertCertificateChain(certificateChain);
177              boolean[] keyUsage = signerCertificates[0].getKeyUsage();
178              if ((keyUsage == null) || keyUsage[0] || keyUsage[1]) { // check for digital signature or non-repudiation, but also accept if none set
179                
180                privateKey = (PrivateKey) key;
181                certificates = signerCertificates;
182                // email address included in certificate?
183                String[] emailAddresses = SMimeUtil.getEmailAddresses(certificates[0]);
184                if (emailAddresses.length > 0) {
185                  // in this demo we use same email for sender and recipient
186                  sender_ = emailAddresses[0];
187                  recipient_ = emailAddresses[0];
188                  signerKey_ = privateKey;
189                  signerCertificates_ = certificates;
190                  break;
191                }
192              }
193            }  
194          }
195        }
196        
197        if (signerKey_ == null) {
198          if (privateKey == null) {
199            System.out.println("Found no signature key. Ensure that a valid card is inserted and contains a key that is suitable for signing.");
200            System.exit(0);
201          }   
202          signerKey_ = privateKey;
203          signerCertificates_ = certificates;
204        }
205        System.out.println("##########");
206        System.out.println("The signer key is: " + signerKey_ );
207        System.out.println("##########");
208        // get the corresponding certificate for this signer key
209        System.out.println("##########");
210        System.out.println("The signer certificate is:");
211        System.out.println(signerCertificates_[0].toString());
212        System.out.println("##########");
213        if (sender_ == null) {
214          sender_ = DEFAULT_EMAIL;
215        }
216        if (recipient_ == null) {
217          recipient_ = DEFAULT_EMAIL;
218        }
219      }
220    
221      /**
222       * Creates a signed message.
223       *
224       * @param session the mail session
225       * @param dataHandler the content of the message to be signed
226       * @param implicit whether to use implicit (application/pkcs7-mime) or explicit
227       *                 (multipart/signed) signing
228       * 
229       * @return the signed message
230       *
231       * @throws MessagingException if an error occurs when creating the message
232       */
233      protected MimeMessage createSignedMessage(Session session, DataHandler dataHandler, boolean implicit)
234        throws MessagingException {
235    
236        String subject = null;
237        StringBuffer buf = new StringBuffer();
238        
239        if (implicit) {
240          subject = "IAIK-S/MIME Demo: PKCS11 Implicitly Signed";
241          buf.append("This message is implicitly signed!\n");
242          buf.append("You need an S/MIME aware mail client to view this message.\n");
243          buf.append("\n\n");
244        } else {
245          subject = "IAIK-S/MIME Demo: PKCS11 Explicitly Signed";
246          buf.append("This message is explicitly signed!\n");
247          buf.append("Every mail client can view this message.\n");
248          buf.append("Non S/MIME mail clients will show the signature as attachment.\n");
249          buf.append("\n\n");
250        }
251        
252       
253        // create SignedContent object
254        SignedContent sc = new SignedContent(implicit);
255    
256        if (dataHandler != null) {
257          sc.setDataHandler(dataHandler);
258        } else {
259          sc.setText(buf.toString());
260        }
261        sc.setCertificates(signerCertificates_);
262    
263        try {
264          sc.addSigner(signerKey_, signerCertificates_[0]);
265        } catch (NoSuchAlgorithmException ex) {
266          throw new MessagingException("Algorithm not supported: " + ex.getMessage(), ex);
267        }
268        
269        // create MimeMessage
270        MimeMessage msg = new MimeMessage(session);
271        msg.setFrom(new InternetAddress(sender_));
272        msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipient_, false));
273        msg.setSentDate(new Date());
274        msg.setSubject(subject);
275        // set signed content
276        msg.setContent(sc, sc.getContentType());
277        // let the SignedContent update some message headers
278        sc.setHeaders(msg);
279        return msg;
280      }
281      
282      /**
283       * Starts the demo.
284       * 
285       * @param implicit whether to create an implicit (content included;
286       *                 application/pkcs7-mime) or an explicit (content
287       *                 not included; multipart/signed) signed message
288       *                  
289       * @throws Exception if an error occurs
290       */
291      protected void start(boolean implicit) throws Exception {
292        Session session = DemoSMimeUtil.getSession();
293        //  Create a demo Multipart
294        MimeBodyPart mbp1 = new SMimeBodyPart();
295        mbp1.setText("This is a Test of the IAIK S/MIME implementation!\n\n");
296          // attachment
297        MimeBodyPart attachment = new SMimeBodyPart();
298        attachment.setDataHandler(new DataHandler(new FileDataSource("test.html")));
299        attachment.setFileName("test.html");
300          
301        Multipart mp = new SMimeMultipart();
302        mp.addBodyPart(mbp1);
303        mp.addBodyPart(attachment);
304        DataHandler multipart = new DataHandler(mp, mp.getContentType());
305        
306        // create signed message
307        MimeMessage msg = createSignedMessage(DemoSMimeUtil.getSession(), multipart, implicit);
308        
309        //  we write to a stream
310        ByteArrayOutputStream baos = new ByteArrayOutputStream();
311        msg.saveChanges();
312        msg.writeTo(baos); // here you could call Transport.send if you want to send the message
313        
314        // we read from a stream
315        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
316        // parse message
317        msg = new MimeMessage(session, bais);
318        if (PRINT_MESSAGES) {
319          printMessage(msg);
320        }
321        DumpMessage.dumpMsg(msg);
322        
323      }
324      
325      /** 
326       * Prints a dump of the given message to System.out.
327       *
328       * @param msg the message to be dumped to System.out
329       */
330      private static void printMessage(Message msg) throws IOException {
331        System.out.println("------------------------------------------------------------------");
332        System.out.println("Message dump: \n");
333        try {
334          msg.writeTo(System.out);
335        } catch (MessagingException ex) {
336          throw new IOException(ex.getMessage());   
337        }    
338        System.out.println("\n------------------------------------------------------------------");
339      }  
340     
341    }