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