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/DumpMessage.java 28 12.02.25 17:58 Dbratko $
059 // $Revision: 28 $
060 //
061
062 package demo.smime;
063
064 import iaik.asn1.CodingException;
065 import iaik.asn1.structures.Attribute;
066 import iaik.cms.CertificateIdentifier;
067 import iaik.cms.KeyIdentifier;
068 import iaik.cms.RecipientInfo;
069 import iaik.cms.SignerInfo;
070 import iaik.pkcs.pkcs10.CertificateRequest;
071 import iaik.smime.AuthEncryptedContent;
072 import iaik.smime.CompressedContent;
073 import iaik.smime.EncryptedContent;
074 import iaik.smime.PKCS10Content;
075 import iaik.smime.SignedContent;
076 import iaik.smime.ess.Receipt;
077 import iaik.smime.ess.ReceiptContent;
078 import iaik.utils.Util;
079 import iaik.x509.X509Certificate;
080
081 import java.io.InputStream;
082 import java.security.PrivateKey;
083 import java.security.SignatureException;
084 import java.security.cert.Certificate;
085 import java.util.Date;
086
087 import javax.mail.Address;
088 import javax.mail.Flags;
089 import javax.mail.Message;
090 import javax.mail.MessagingException;
091 import javax.mail.Multipart;
092 import javax.mail.Part;
093
094 import demo.keystore.CMSKeyStore;
095
096
097 /**
098 * Simple utility for dumping a message to System.out.
099 * The parts of the message are recursively dumped.
100 * Note that this utility is not thread-safe (i.e. only
101 * one {@link #privateKey_ private key} can be statically
102 * set to decrypt an encrypted content part.
103 */
104 public class DumpMessage {
105
106 /**
107 * Dumps the given message to System.out.
108 *
109 * @param msg the message to be dumped
110 *
111 * @throws Exception if an error occurs
112 */
113 public static void dumpMsg(Message msg) throws Exception {
114 DumpMessage dumpMsg = new DumpMessage();
115 dumpMsg.dump(msg);
116 }
117
118
119 /**
120 * Dumps the given encrypted message and decrypts the content for
121 * the several recipients.
122 *
123 * @param msg the message to be dumped
124 *
125 * @throws Exception if an error occurs
126 */
127 public static void dumpEncryptedMessage(Message msg) throws Exception {
128 DumpMessage dumpMsg = new DumpMessage();
129 dumpMsg.dumpEnvelope(msg);
130 EncryptedContent ec = (EncryptedContent)msg.getContent();
131 if (ec instanceof AuthEncryptedContent) {
132 System.out.println("This message is authenticated encrypted!");
133 } else {
134 System.out.println("This message is encrypted!");
135 }
136 RecipientInfo[] recipients = ec.getRecipientInfos();
137 for (int i = 0; i < recipients.length; i++) {
138 KeyIdentifier[] recipientKeyIds = recipients[i].getRecipientIdentifiers();
139 for (int j = 0; j < recipientKeyIds.length; j++) {
140 KeyIdentifier recipientKeyId = recipientKeyIds[j];
141 // in practice the message will be decrypted by one recipient only; however,
142 // since we demonstrate decryption for each recipient we must parse the message
143 // anew (otherwise the content has been already decrypted)
144 ec = (EncryptedContent)msg.getContent();
145 dumpMsg.dumpEncrypted(ec, recipientKeyId);
146 System.out.println("############################");
147 }
148 }
149 }
150
151 /**
152 * Private key; if set used for decrypting an encrypted entity.
153 * Otherwise the {@link demo.keystore.CMSKeyStore CMSKeyStore} used by the demos
154 * is {@link demo.keystore.CMSKeyStore CMSKeyStore#getRecipientCert(KeyIdentifier) searched}
155 * for a recipient decryption key based on the recipient key identifier.
156 */
157 private PrivateKey privateKey_;
158
159 /**
160 * The certificates of the signer; got from a message, if it is signed.
161 */
162 private X509Certificate[] signerCerts_;
163
164
165 /**
166 * Default constructor.
167 */
168 public DumpMessage() {
169 }
170
171 /**
172 * Sets the private recipient key to be used to decrypt an encrypted message.
173 * <br>
174 * If not set the {@link demo.keystore.CMSKeyStore CMSKeyStore} used by the demos
175 * is {@link demo.keystore.CMSKeyStore CMSKeyStore#getRecipientCert(KeyIdentifier) searched}
176 * for a recipient decryption key based on the recipient key identifier.
177 *
178 * @param recipientKey the private key of the recipient
179 */
180 public void setRecipientKey(PrivateKey recipientKey) {
181 privateKey_ = recipientKey;
182 }
183
184 /**
185 * Gets the certificates of the signer included in a signed message.
186 *
187 * @return the signer certificates; it the message is signed and contains the certificate
188 * of the signer; <code>null</code> otherwise.
189 */
190 public X509Certificate[] getSignerCerts() {
191 return signerCerts_;
192 }
193
194
195 /**
196 * Dumps the given object (message, part,...) to System.out.
197 *
198 * @param o the object to be dumped
199 *
200 * @throws Exception if an error occurs
201 */
202 public void dump(Object o) throws Exception {
203 if (o instanceof Message) {
204 dumpEnvelope((Message)o);
205 }
206 if (o instanceof Part) {
207 System.out.println("CONTENT-TYPE: "+((Part)o).getContentType());
208 o = ((Part)o).getContent();
209 }
210
211 if (o instanceof AuthEncryptedContent) {
212 // authenticated encrypted
213 System.out.println("This message is authenticated encrypted!");
214 AuthEncryptedContent ec = (AuthEncryptedContent)o;
215 dumpAuthEncrypted(ec, null);
216 } else if (o instanceof EncryptedContent) {
217 // encrypted
218 System.out.println("This message is encrypted!");
219 EncryptedContent ec = (EncryptedContent)o;
220 dumpEncrypted(ec, null);
221 } else if (o instanceof SignedContent) {
222 // signed
223 System.out.println("This message is signed!");
224 SignedContent sc = (SignedContent)o;
225 dumpSigned(sc);
226 } else if (o instanceof PKCS10Content) {
227 System.out.println("This message contains a certificate request:");
228 PKCS10Content pkcs10 = (PKCS10Content)o;
229 dumpPKCS10(pkcs10);
230 } else if (o instanceof CompressedContent) {
231 System.out.println("The content of this message is compressed.");
232 CompressedContent compressed = (CompressedContent)o;
233 dump(compressed.getContent());
234 } else if (o instanceof ReceiptContent) {
235 System.out.println("This message contains a signed receipt:");
236 ReceiptContent rc = (ReceiptContent)o;
237 dumpReceiptContent(rc);
238 } else if (o instanceof String) {
239 System.out.println("Content is a String");
240 System.out.println("---------------------------");
241 System.out.println((String)o);
242 } else if (o instanceof Multipart) {
243 System.out.println("----------------> Content is a Multipart");
244 Multipart mp = (Multipart)o;
245 int count = mp.getCount();
246 for (int i = 0; i < count; i++) {
247 System.out.println("----------------> Multipart: "+(i+1));
248 dump(mp.getBodyPart(i));
249 }
250 System.out.println("----------------> End of Multipart");
251 } else if (o instanceof Message) {
252 System.out.println("Content is a Nested Message");
253 System.out.println("---------------------------");
254 dump(o);
255 } else if (o instanceof InputStream) {
256 System.out.println("Content is just an input stream: "+o);
257 System.out.println("---------------------------");
258 InputStream is = (InputStream)o;
259 int a;
260 int sum = 0;
261 byte[] buf = new byte[1024];
262 while ((a = is.read(buf)) > 0) {
263 sum += a;
264 }
265 System.out.println("Length of data: "+sum+" bytes");
266 }
267 }
268
269 /**
270 * Dumps the given encrypted content.
271 *
272 * @param ec the EncryptedContent to be dumped
273 * @param recipientKeyId the id of the recipient key to be used for decrypting the content
274 *
275 * @throws Exception if an error occurs
276 */
277 public void dumpEncrypted(EncryptedContent ec, KeyIdentifier recipientKeyId) throws Exception {
278
279 if (recipientKeyId == null) {
280 if (privateKey_ != null) {
281 // take the key that has been explicitly set
282 dumpEncrypted(ec, null, privateKey_);
283 } else {
284 // search key data base for a key for any of the included recipients
285 boolean foundKey = false;
286 RecipientInfo[] recipients = ec.getRecipientInfos();
287 for (int i = 0; i < recipients.length; i++) {
288 KeyIdentifier[] recipientKeyIds = recipients[i].getRecipientIdentifiers();
289 for (int j = 0; j < recipientKeyIds.length; j++) {
290 KeyIdentifier recipientId = recipientKeyIds[j];
291 PrivateKey recipientKey = CMSKeyStore.getRecipientKey(recipientId);
292 if (recipientKey != null) {
293 dumpEncrypted(ec, recipientId, recipientKey);
294 foundKey = true;
295 break;
296 }
297 }
298 if (foundKey) {
299 break;
300 }
301 }
302 if (foundKey == false) {
303 throw new Exception("No recipient key found!");
304 }
305 }
306 } else {
307 // search for the key based on the given recipient key id
308 PrivateKey recipientKey = CMSKeyStore.getRecipientKey(recipientKeyId);
309 if (recipientKey == null) {
310 throw new Exception("No key available for " + recipientKeyId);
311 }
312 dumpEncrypted(ec, recipientKeyId, recipientKey);
313 }
314 }
315
316 /**
317 * Dumps the given encrypted content for the recipient identified by the given recipient key id
318 * using the given private recipient decryption key.
319 *
320 * @param ec the EncryptedContent to be dumped
321 * @param recipientKeyId the id of the recipient key to be used for decrypting the content
322 * @param recipientKey the private key of the recipient
323 *
324 * @throws Exception if an error occurs
325 */
326 public void dumpEncrypted(EncryptedContent ec, KeyIdentifier recipientKeyId, PrivateKey recipientKey) throws Exception {
327 System.out.println("Encryption algorithm: " + ec.getEncryptionAlgorithm().getName());
328 if (recipientKeyId == null) {
329 // take the key that has been explicitly set
330 ec.decryptSymmetricKey(privateKey_, 0);
331 } else {
332 X509Certificate recipientCert = CMSKeyStore.getRecipientCert((CertificateIdentifier)recipientKeyId);
333 System.out.println("Decrypting message for " + recipientCert.getSubjectDN());
334 ec.decryptSymmetricKey(recipientKey, recipientKeyId);
335 }
336 dump(ec.getContent());
337 }
338
339 /**
340 * Dumps the given authenticated encrypted content.
341 *
342 * @param ec the AuthEncryptedContent to be dumped
343 * @param recipientKeyId the id of the recipient key to be used for decrypting the content
344
345 *
346 * @throws Exception if an error occurs
347 */
348 public void dumpAuthEncrypted(AuthEncryptedContent ec, KeyIdentifier recipientKeyId) throws Exception {
349 dumpEncrypted(ec, recipientKeyId);
350 }
351
352 /**
353 * Dumps the given signed content.
354 *
355 * @param sc the SignedContent to be dumped
356 *
357 * @throws Exception if an error occurs
358 */
359 public void dumpSigned(SignedContent sc) throws Exception {
360
361 if (sc.getSMimeType().equals("certs-only")) {
362 System.out.println("This message contains only certificates!");
363 Certificate[] certs = sc.getCertificates();
364 for (int i = 0; i < certs.length; ++i) {
365 System.out.println(certs[i].toString());
366 }
367 } else {
368 X509Certificate signer = null;
369 try {
370 signer = sc.verify();
371 System.out.println("This message is signed from: "+signer.getSubjectDN());
372 } catch (SignatureException ex) {
373 throw new SignatureException("Signature verification error: " + ex.toString());
374 }
375
376 SignerInfo[] signerInfos = sc.getSignerInfos();
377 for (int i=0; i<signerInfos.length; i++) {
378 System.out.println("Digest algorithm: " + signerInfos[i].getDigestAlgorithm().getName());
379 System.out.println("Signature algorithm: " + signerInfos[i].getSignatureAlgorithm().getName());
380 Attribute[] signedAttributes = signerInfos[i].getSignedAttributes();
381 if ((signedAttributes != null) && (signedAttributes.length > 0)) {
382 System.out.println("SignerInfo " + i + " contains the following signed attributes:");
383 for (int j = 0; j < signedAttributes.length; j++) {
384 dumpAttribute(signedAttributes[j]);
385 }
386 }
387 Attribute[] unsignedAttributes = signerInfos[i].getUnsignedAttributes();
388 if ((unsignedAttributes != null) && (unsignedAttributes.length > 0)) {
389 System.out.println("SignerInfo " + i + " contains the following unsigned attributes:");
390 for (int j = 0; j < unsignedAttributes.length; j++) {
391 dumpAttribute(unsignedAttributes[j]);
392 }
393 }
394 }
395 if (!signerInfos[0].isSignerCertificate(signer)) {
396 System.out.println("Signer certificate check failed!");
397 }
398 signerCerts_ = Util.convertCertificateChain(sc.getCertificates());
399 // arrange chain to get user cert at index 0
400 signerCerts_ = Util.createCertificateChain(signer, signerCerts_);
401 dump(sc.getContent());
402 }
403 }
404
405 /**
406 * Dumps the given PKCS#10 content.
407 *
408 * @param pkcs10 the PKCS10Content to be dumped
409 *
410 * @throws Exception if an error occurs
411 */
412 public void dumpPKCS10(PKCS10Content pkcs10) throws Exception {
413 CertificateRequest request = pkcs10.getCertRequest();
414 System.out.println(request.toString());
415 try {
416 if (request.verify()) {
417 System.out.println("Request verification ok for " + request.getSubject());
418 } else {
419 throw new SignatureException("Incorrect signature!");
420 }
421 } catch (SignatureException ex) {
422 throw new SignatureException("Request verification error for " + request.getSubject() + ex.getMessage());
423 }
424 }
425
426 /**
427 * Dumps the given signed receipt.
428 *
429 * @param rc the signed receipt to be dumped
430 *
431 * @throws Exception if an error occurs
432 */
433 public void dumpReceiptContent(ReceiptContent rc) throws Exception {
434
435 Receipt receipt = (Receipt)rc.getContent();
436 System.out.println(receipt);
437 // verify the signature (we assume only one signer)
438 X509Certificate receiptSigner = null;
439 try {
440 receiptSigner = rc.verify();
441 System.out.println("This receipt content is signed from: "+receiptSigner.getSubjectDN());
442 } catch (SignatureException ex) {
443 System.err.println("Signature verification error!");
444 throw ex;
445 }
446 }
447
448
449 /**
450 * Prints the envelope of a message.
451 *
452 * @param m the message
453 *
454 * @throws MessagingException if an error occurs
455 */
456 public void dumpEnvelope(Message m) throws MessagingException {
457 System.out.println("This is the message envelope");
458 System.out.println("---------------------------");
459 Address[] a;
460 // FROM
461 if ((a = m.getFrom()) != null) {
462 for (int j = 0; j < a.length; j++)
463 System.out.println("FROM: " + a[j].toString());
464 }
465
466 // TO
467 if ((a = m.getRecipients(Message.RecipientType.TO)) != null) {
468 for (int j = 0; j < a.length; j++)
469 System.out.println("TO: " + a[j].toString());
470 }
471
472 // SUBJECT
473 System.out.println("SUBJECT: " + m.getSubject());
474
475 // DATE
476 Date d = m.getSentDate();
477 System.out.println("SendDate: "+(d != null ? d.toString() : "UNKNOWN"));
478
479 // SIZE
480 System.out.println("Size: " + m.getSize());
481
482 // FLAGS:
483 Flags flags = m.getFlags();
484 StringBuffer sb = new StringBuffer();
485 Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags
486
487 boolean first = true;
488 for (int i = 0; i < sf.length; i++) {
489 String s;
490 Flags.Flag f = sf[i];
491 if (f == Flags.Flag.ANSWERED)
492 s = "\\Answered";
493 else if (f == Flags.Flag.DELETED)
494 s = "\\Deleted";
495 else if (f == Flags.Flag.DRAFT)
496 s = "\\Draft";
497 else if (f == Flags.Flag.FLAGGED)
498 s = "\\Flagged";
499 else if (f == Flags.Flag.RECENT)
500 s = "\\Recent";
501 else if (f == Flags.Flag.SEEN)
502 s = "\\Seen";
503 else
504 continue; // skip it
505 if (first)
506 first = false;
507 else
508 sb.append(' ');
509 sb.append(s);
510 }
511
512 String[] uf = flags.getUserFlags(); // get the user flag strings
513 for (int i = 0; i < uf.length; i++) {
514 if (first)
515 first = false;
516 else
517 sb.append(' ');
518 sb.append(uf[i]);
519 }
520 System.out.println("FLAGS = " + sb.toString());
521
522 // X-MAILER
523 String[] hdrs = m.getHeader("X-Mailer");
524 if (hdrs != null)
525 System.out.println("X-Mailer: " + hdrs[0]);
526 else
527 System.out.println("X-Mailer NOT available");
528 }
529
530 /**
531 * Dumps the given attribute to System.out.
532 *
533 * @param attribute the Attribute
534 *
535 * @throws CodingException if the attribute cannot be parsed
536 */
537 private static void dumpAttribute(Attribute attribute) throws CodingException {
538 System.out.println(attribute.toString() + "\n");
539 }
540 }
541