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