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