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