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 }