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.// Copyright (C) 2002 IAIK
027// https://sic.tech
028//
029// Copyright (C) 2003 - 2025 Stiftung Secure Information and 
030//                           Communication Technologies SIC
031// https://sic.tech
032//
033// All rights reserved.
034//
035// This source is provided for inspection purposes and recompilation only,
036// unless specified differently in a contract with IAIK. This source has to
037// be kept in strict confidence and must not be disclosed to any third party
038// under any circumstances. Redistribution in source and binary forms, with
039// or without modification, are <not> permitted in any case!
040//
041// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
042// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
043// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
044// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
045// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
046// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
047// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
048// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
049// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
050// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
051// SUCH DAMAGE.
052//
053// $Header: /IAIK-CMS/current/src/demo/smime/ess/SignedReceiptDemo.java 39    12.02.25 17:59 Dbratko $
054// $Revision: 39 $
055//
056
057package demo.smime.ess;
058
059import iaik.asn1.structures.AlgorithmID;
060import iaik.asn1.structures.Attribute;
061import iaik.cms.IssuerAndSerialNumber;
062import iaik.cms.SignerInfo;
063import iaik.smime.SMimeSignerInfo;
064import iaik.smime.SignedContent;
065import iaik.smime.ess.ContentIdentifier;
066import iaik.smime.ess.ESSException;
067import iaik.smime.ess.EntityIdentifier;
068import iaik.smime.ess.MLData;
069import iaik.smime.ess.MLExpansionHistory;
070import iaik.smime.ess.MLReceiptPolicy;
071import iaik.smime.ess.Receipt;
072import iaik.smime.ess.ReceiptContent;
073import iaik.smime.ess.ReceiptRequest;
074import iaik.smime.ess.ReceiptsFrom;
075import iaik.smime.ess.utils.SenderAndReceiptContentDigest;
076import iaik.smime.ess.utils.SignedReceipt;
077import iaik.x509.X509Certificate;
078
079import java.io.ByteArrayInputStream;
080import java.io.ByteArrayOutputStream;
081import java.io.IOException;
082import java.io.OutputStream;
083import java.security.NoSuchAlgorithmException;
084import java.security.PrivateKey;
085import java.security.PublicKey;
086import java.security.SignatureException;
087import java.util.Date;
088
089import jakarta.mail.Message;
090import jakarta.mail.MessagingException;
091import jakarta.mail.Session;
092import jakarta.mail.internet.InternetAddress;
093import jakarta.mail.internet.MimeMessage;
094
095import demo.DemoSMimeUtil;
096import demo.DemoUtil;
097import demo.keystore.CMSKeyStore;
098
099
100/**
101 * An <a href="https://www.rfc-editor.org/rfc/rfc2634.html" target = "_blank">RFC2634</a> ESS ReceiptRequest -- SignedReceipt demo.
102 * <p>
103 * This demo creates a message with a {@link iaik.smime.ess.ReceiptRequest
104 * ReceiptRequest} attribute and "sends" it to some intended recipient.
105 * The recipient then "sends" a signed receipt message back to the
106 * original sender who finally validates the signed receipt.
107 * A further test run adds a MLA layer with a {@link iaik.smime.ess.MLExpansionHistory
108 * MLExpansionHistory} attribute,
109 * that supersedes the original receipt request.
110 * <p>
111 * To run this demo the following packages are required:
112 * <ul>
113 *    <li>
114 *       <code>iaik_cms.jar</code>
115 *    </li>
116 *    <li>
117 *       <code>iaik_jce(_full).jar</code> (<a href="https://sic.tech/products/core-crypto-toolkits/jca-jce/" target="_blank">IAIK-JCE Core Crypto Library</a>).
118 *    </li>
119 *    <li>
120 *       <a href="https://jakarta.ee/specifications/mail/" target="_blank">Jakarta</a>/<a href="https://eclipse-ee4j.github.io/angus-mail/" target="_blank">Angus</a> Mail
121 *    </li>   
122 *    <li>
123 *       <a href="https://jakarta.ee/specifications/activation/" target="_blank">Jakarta Activation Framework</a>
124 *    </li> 
125 * </ul>
126 *
127 * @see iaik.smime.ess.ReceiptRequest 
128 * @see iaik.smime.ess.Receipt
129 * @see iaik.smime.ess.MLExpansionHistory
130 * @see iaik.smime.ess.MLData
131 * @see iaik.smime.ess.MLReceiptPolicy
132 * @see iaik.smime.ess.utils.SignedReceipt
133 */
134public class SignedReceiptDemo {
135    
136  // whether to dump all generates test messages to System.out
137  final static boolean DUMP_MESSAGES = false;  
138 
139  // sender (sends a receipt request message)
140  String senderName_ = "IAIK Demo Sender";
141  // recipient (receives a receipt request and sends a signed receipt)
142  String recipientName_ = "IAIK Demo Recipient";
143  // an mail list agent
144  String mlaName_ = "IAIK Demo ML Agent";
145  // we use the same email address for all parties
146  String senderAddress_ = "\""+senderName_+"\" <smimetest@iaik.at>";
147  String recipientAddress_ = "\""+recipientName_+"\" <smimetest@iaik.at>";
148  String mlaAddress_ = "\""+mlaName_+"\" <smimetest@iaik.tugraz.at>";
149  
150  String host_ = "mailhost";                  // name of the mailhost
151
152  X509Certificate[] signerCertificates_;      // signer certificate list
153  X509Certificate signerCertificate_;         // certificate of the signer/sender
154  X509Certificate encryptionCertOfSigner_;    // signer uses different certificate for encryption
155  PrivateKey signerPrivateKey_;               // private key of the signer/sender
156  
157  X509Certificate[] recipientCertificates_;   // recipient certificate list
158  X509Certificate recipientCertificate_;      // certificate of the recipient
159  X509Certificate encryptionCertOfRecipient_; // recipient uses different certificate for encryption
160  PrivateKey recipientPrivateKey_;            // private key of the recipient
161  
162  X509Certificate[] signerCertificatesOfMLA_; // signer certificates of MLA
163  PrivateKey signerPrivateKeyOfMLA_;          // signer private key of MLA
164 
165  
166  /**
167   * Empty default constructor. Reads all required keys and certificates
168   * from the demo keystore (created by running @link demo.keystore.SetupCMSKeySrore)
169   * stored at "cms.keystore" in your current working directoy.
170   */
171  public SignedReceiptDemo() {
172    
173    System.out.println();
174    System.out.println("******************************************************************************************");
175    System.out.println("*                               SignedReceiptDemo                                        *");
176    System.out.println("*       (shows the usage of the IAIK-CMS library for handling ESS signed receipts)       *");
177    System.out.println("******************************************************************************************");
178    System.out.println();
179    
180    // get the certificates from the KeyStore
181    signerCertificates_ = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
182    signerPrivateKey_ = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
183    signerCertificate_ = signerCertificates_[0];
184    encryptionCertOfSigner_ = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_1)[0];
185    recipientCertificates_ = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_2);
186    recipientPrivateKey_ = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_2);
187    recipientCertificate_ = recipientCertificates_[0];
188    encryptionCertOfRecipient_ = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_CRYPT_2)[0];
189    signerCertificatesOfMLA_ = CMSKeyStore.getCertificateChain(CMSKeyStore.DSA, CMSKeyStore.SZ_2048_SIGN_1);
190    signerPrivateKeyOfMLA_ = CMSKeyStore.getPrivateKey(CMSKeyStore.DSA, CMSKeyStore.SZ_2048_SIGN_1);
191    X509Certificate[] tmpCerts = new X509Certificate[signerCertificates_.length + 1];
192    System.arraycopy(signerCertificates_, 0, tmpCerts, 0, signerCertificates_.length);
193    tmpCerts[signerCertificates_.length] = encryptionCertOfSigner_;
194    signerCertificates_ = tmpCerts;
195    tmpCerts = new X509Certificate[recipientCertificates_.length + 1];
196    System.arraycopy(recipientCertificates_, 0, tmpCerts, 0, recipientCertificates_.length);
197    tmpCerts[recipientCertificates_.length] = encryptionCertOfRecipient_;
198    recipientCertificates_ = tmpCerts;
199  }
200  
201  /**
202   * Starts the SignedReceipt demo.
203   */
204  public void start() {
205    boolean implicit = true;
206    boolean mla = false;
207    System.out.println("Testing receipt request - signed receipt (all implicit)");
208    test(implicit, mla);
209    mla = true;
210    System.out.println("Testing receipt request - MLA - signed receipt (all implicit)");
211    test(implicit, mla);
212    implicit = false;
213    mla = false;
214    System.out.println("Testing receipt request - signed receipt (all explicit)");
215    test(implicit, mla);
216    mla = true;
217    System.out.println("Testing receipt request - MLA - signed receipt (all explicit)");
218    test(implicit, mla);
219    System.out.println("Ready!");
220  }
221  
222  /**
223   * Runs the ReceiptRequest - SignedReceipt test.
224   *
225   * @param implicit whether to create implicit (application/pkcs7-mime) or
226   *                 explicit (multipart/signed) ReceiptRequest messages
227   * 
228   * @param mla whether to add a MLA layer
229   */
230  public void test(boolean implicit, boolean mla) {
231
232    try {
233          // get the default Session
234          Session session = DemoSMimeUtil.getSession();
235      
236      Message msg;    // the message to send
237      ByteArrayOutputStream baos = new ByteArrayOutputStream(); // we write to a stream
238      ByteArrayInputStream bais;  // we read from a stream
239      
240      // we send a signed message with a receipt request
241      System.out.println("Creating implicit signed message with receipt request.");
242      // create message and "send" it (write it to baos)
243      msg = createSignedMessageWithReceiptRequest(session, implicit, baos);
244      
245      // now parse the receipt request message
246          bais = new ByteArrayInputStream(baos.toByteArray());
247          msg = new MimeMessage(session, bais);
248          if (DUMP_MESSAGES) {
249            dumpMessage(msg);
250          }
251          baos.reset();
252          
253          if (mla == true) {
254            // MLA receives the message and adds a MLExpansionHistory that supersedes the 
255            // original receipt request
256            System.out.println("MLA: parsing received original message.");
257            SignedContent sc = (SignedContent)msg.getContent();
258            System.out.println("MLA: Verifying signature.");
259            // verify the signature (we assume only one signer)
260        X509Certificate signer = sc.verify();
261            System.out.println("MLA: This message is signed from: "+signer.getSubjectDN());
262            // add MLExpansionHistory
263            System.out.println("MLA: Creating new signed message with MLExpansionHistory attribute.");
264        SignedContent mlaSc = new SignedContent(sc, implicit);
265        mlaSc.setCertificates(signerCertificatesOfMLA_);
266        try {
267          SMimeSignerInfo signerInfo = new SMimeSignerInfo(signerCertificatesOfMLA_[0],
268                                                           (AlgorithmID)AlgorithmID.sha256.clone(), 
269                                                           (AlgorithmID)AlgorithmID.dsaWithSHA256.clone(), 
270                                                           signerPrivateKeyOfMLA_);            
271          // add a MLExpansionHistory attribute superseding the original receipt request
272          MLExpansionHistory mlExpansionHistory = createMLExpansionHistory(signerCertificatesOfMLA_[0],
273                                                                           new Date(),
274                                                                           mlaAddress_);
275          signerInfo.addSignedAttribute(new Attribute(mlExpansionHistory));
276          mlaSc.addSigner(signerInfo);
277        } catch (NoSuchAlgorithmException ex) {
278          throw new MessagingException("Algorithm not supported: " + ex.getMessage(), ex);
279        }
280        msg = createMessage(session, mlaAddress_, recipientAddress_, "IAIK-S/MIME: MLA with ReceiptRequest");
281        msg.setContent(mlaSc, mlaSc.getContentType());
282        // let the SignedContent update some message headers
283        mlaSc.setHeaders(msg);
284        msg.saveChanges();
285            msg.writeTo(baos);
286            
287            // now parse the MLA message
288            bais = new ByteArrayInputStream(baos.toByteArray());
289        msg = new MimeMessage(session, bais);
290          
291            if (DUMP_MESSAGES) {
292              dumpMessage(msg);
293            }
294      }   
295          
296          // signed receipt creation
297          baos.reset();
298          Message msg1 = createMessageWithSignedReceipt(session, msg);
299          msg1.saveChanges();
300          msg1.writeTo(baos);
301          bais = new ByteArrayInputStream(baos.toByteArray());
302          msg = new MimeMessage(session, bais);
303
304          if (DUMP_MESSAGES) {
305            dumpMessage(msg);
306          }
307          // signed receipt validation
308          System.out.println("\nNow getting and verifying signed receipt:");
309          verifyReceiptContent(msg);
310      
311        } catch (Exception ex) {
312          ex.printStackTrace();
313          throw new RuntimeException(ex.toString());
314        }
315
316        
317  }
318  
319  /**
320   * Creates a MimeMessage.
321   *
322   * @param session the current mail session
323   * @param from the sender of the message
324   * @param to the recipient of the message
325   * @param subject the subject of the message
326   *
327   * @return the newly created MimeMessage
328   *
329   * @throws MessagingException if an error occurs when creating the message
330   */
331  public Message createMessage(Session session, String from, String to, String subject) throws MessagingException {
332    MimeMessage msg = new MimeMessage(session);
333    msg.setFrom(new InternetAddress(from));
334        msg.setRecipients(Message.RecipientType.TO,     InternetAddress.parse(to, false));
335        msg.setSentDate(new Date());
336    return msg;
337  }
338
339  /**
340   * Creates a signed message that contains a <code>ReceiptRequest</code> attribute.
341   *
342   * @param session the current mail session
343   * @param implicit whether to sign the content implicitly or explicitly
344   * @param os the output stream to which to write the message
345   *
346   * @return the message containing a ReceiptRequest attribute
347   */
348  public Message createSignedMessageWithReceiptRequest(Session session,
349                                                       boolean implicit,
350                                                       OutputStream os)
351    throws Exception {
352
353    Message msg = createMessage(session, senderAddress_, recipientAddress_, "IAIK-S/MIME: ReceiptRequest");
354    
355    // create the inner signed content
356    SignedContent sc = new SignedContent(implicit, implicit ? SignedContent.SIGNED_DATA : null);
357    sc.setText("This is a signed message with a ReceiptRequest.");
358    
359    sc.setCertificates(signerCertificates_);
360    SMimeSignerInfo signerInfo = new SMimeSignerInfo(signerCertificate_, 
361                                                     (AlgorithmID)AlgorithmID.sha256.clone(),
362                                                     (AlgorithmID)AlgorithmID.rsaEncryption.clone(), 
363                                                     signerPrivateKey_, 
364                                                     encryptionCertOfSigner_,
365                                                     true);
366    // add a ReceiptRequest attribute to request a receipt to be sent back to the sender
367    ReceiptRequest receiptRequest = createReceiptRequest(signerCertificate_.getPublicKey(),
368                                                         msg.getSentDate(),
369                                                         senderAddress_);
370    signerInfo.addSignedAttribute(new Attribute(receiptRequest));
371    sc.addSigner(signerInfo);
372    msg.setContent(sc, sc.getContentType());
373    // let the SignedContent update some message headers
374    sc.setHeaders(msg);
375    msg.saveChanges();
376        msg.writeTo(os);
377        // now after sending (writing) the message we can access and keep the digest values for later SignedReceipt validation
378    storeDigestValues(sc);
379    return msg;
380  }
381  
382  /**
383   * Creates a ReceiptRequest attribute to request all recipients to send
384   * a signed receipt to the entity to the given email address.
385   * 
386   * @param publicKey the public key of the sender (used for ContentIdentifier calculation)
387   * @param sentDate the sent date of the message (used for ContentIdentifier calculation)
388   * @param email the email address of the sender (to whom to return a signed receipt)
389   *
390   * @return the ReceiptRequest
391   */
392  public ReceiptRequest createReceiptRequest(PublicKey publicKey, Date sentDate, String email) {
393    // we request a receipt from all recipients
394    ReceiptsFrom receiptsFrom = new ReceiptsFrom(ReceiptsFrom.ALL_RECIPIENTS);
395    // the receipt should be send to the given email
396    String[] sendTo = { email };
397    // create the signed content identifier
398    ContentIdentifier contentIdentifier = new ContentIdentifier(publicKey, sentDate, null);
399    // create the receipt request
400    ReceiptRequest receiptRequest = new ReceiptRequest(contentIdentifier, receiptsFrom, sendTo);
401    return receiptRequest;
402  }  
403  
404  /**
405   * Creates a MLExpansionHistory containing only one MLData for
406   * the given MLA with given expansion time and a MLReceiptPolicy
407   * of type IN_ADDITION_TO for the given mlaEmailAddress.
408   *
409   * @param mlaCertificate the certificate of the MLA from which to create the
410   *        MLData EntityIdentiifier of type IssuerAndSerialNumber
411   * @param expansionTime the expansion time
412   * @param mlaEmailAddress to be set as IN_ADDITION_TO recipient list for
413   *                        the MLData MLRecipientPolicy
414   *
415   * @return the newly created MLExpansionHistory
416   */
417  public static MLExpansionHistory createMLExpansionHistory(X509Certificate mlaCertificate,
418                                                            Date expansionTime,
419                                                            String mlaEmailAddress) {
420    
421    IssuerAndSerialNumber ias = new IssuerAndSerialNumber(mlaCertificate);
422    MLData mlData = new MLData(new EntityIdentifier(ias), expansionTime); 
423    MLReceiptPolicy mlReceiptPolicy = new MLReceiptPolicy(MLReceiptPolicy.IN_ADDITION_TO);
424    mlReceiptPolicy.setRecipientList(new String[] { mlaEmailAddress });
425    mlData.setMLReceiptPolicy(mlReceiptPolicy);
426    return new MLExpansionHistory(mlData);
427  }  
428  
429  /**
430   * Keeps the signature message digest value of the sender and the receipt content digest
431   * values for later SignedReceipt validation.
432   *
433   * @param signedContent the signed message for which to keep the digest values
434   *
435   * @throws ESSException if an error occurs while gathering the required digest values
436   */
437  public void storeDigestValues(SignedContent signedContent) throws ESSException {
438    SignerInfo originatorSignerInfo = signedContent.getSignerInfos()[0];
439    SenderAndReceiptContentDigest.storeEntry(new SenderAndReceiptContentDigest(originatorSignerInfo));
440  }  
441  
442  /**
443   * Creates a signed-receipt message from the received message.
444   *
445   * @param session the current mail session
446   * @param receivedMsg the message containing a ReceiptRequest attribute
447   *
448   * @return a message containing s signed receipt to be sent in return
449   *         to the receipt request
450   *
451   * @throws Exception if some error occurs during receipt request processing
452   *                      or signed receipt creation
453   */
454  public Message createMessageWithSignedReceipt(Session session, Message receivedMsg) 
455          throws Exception {
456
457    SignedReceipt signedReceipt = new SignedReceipt(receivedMsg, recipientAddress_, System.out);
458    String subject = "IAIK-S/MIME: Signed Receipt";
459         
460    Message msg = signedReceipt.createReceiptMessage(recipientPrivateKey_, 
461                                                     recipientCertificates_,
462                                                     recipientCertificates_[0],
463                                                     (AlgorithmID)AlgorithmID.sha256.clone(),
464                                                     (AlgorithmID)AlgorithmID.rsaEncryption.clone(),
465                                                     encryptionCertOfRecipient_,
466                                                     true,
467                                                     session,
468                                                     subject);
469    return msg;
470  } 
471  
472  
473  /**
474   * Validates a signed receipt message received in return to a receipt request
475   * message.
476   *
477   * @param receiptMsg the message containing the signed receipt
478   *
479   * @throws Exception if the receipt validation fails for some reason
480   */
481  public void verifyReceiptContent(Message receiptMsg) throws Exception {
482     // we assume to already know of the signed content        
483    ReceiptContent receiptContent = (ReceiptContent)receiptMsg.getContent();
484    
485    Receipt receipt = (Receipt)receiptContent.getContent();
486        System.out.println("\nReceipt received:");
487        System.out.println(receipt);
488        
489    
490    // verify the signature (we assume only one signer)
491    X509Certificate receiptSigner = null;
492    try {
493      receiptSigner = receiptContent.verify();
494          System.out.println("This receipt content is signed from: "+receiptSigner.getSubjectDN());
495        } catch (SignatureException ex) {
496          System.err.println("Signature verification error!");
497          throw ex;
498    }
499    
500  
501        try {
502          SenderAndReceiptContentDigest sarcd = SenderAndReceiptContentDigest.validateReceiptContent(receiptContent);
503          // now after validation we may remove the kept digest values from the repository
504          SenderAndReceiptContentDigest.removeEntry(sarcd);
505        } catch (ESSException ex) {
506          System.err.println("Signed Receipt validation error!");
507          throw ex;
508        }    
509        System.out.println("ReceiptContent successful validated!");
510  }  
511  
512  /**
513   * Dumps the given message to System.out.
514   * 
515   * @param msg the message to be dumped
516   *
517   * @throws Exception if some error occurs
518   */
519  private static void dumpMessage(Message msg) throws Exception {
520    System.out.println("******************************************************************");
521    System.out.println("Message dump: \n");
522    msg.writeTo(System.out);
523    System.out.println("******************************************************************");
524  }  
525  
526  /**
527   * Main method.
528   */
529  public static void main(String[] argv) throws IOException {
530    
531    try {
532      DemoSMimeUtil.initDemos();
533          (new SignedReceiptDemo()).start();
534        } catch (Exception ex) {
535      ex.printStackTrace();   
536    }       
537
538    DemoUtil.waitKey();
539        
540  }
541}