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}