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 }