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/cms/tsp/TimeStampDemo.java 20    12.02.25 17:58 Dbratko $
059    // $Revision: 20 $
060    //
061    
062    package demo.cms.tsp;
063    
064    import java.io.ByteArrayInputStream;
065    import java.io.ByteArrayOutputStream;
066    import java.io.IOException;
067    import java.io.InputStream;
068    import java.security.NoSuchAlgorithmException;
069    import java.security.PrivateKey;
070    import java.security.SignatureException;
071    
072    import demo.DemoUtil;
073    import demo.keystore.CMSKeyStore;
074    import iaik.asn1.ObjectID;
075    import iaik.asn1.structures.AlgorithmID;
076    import iaik.asn1.structures.Attribute;
077    import iaik.cms.CMSException;
078    import iaik.cms.ContentInfo;
079    import iaik.cms.ContentInfoStream;
080    import iaik.cms.IssuerAndSerialNumber;
081    import iaik.cms.SignedData;
082    import iaik.cms.SignedDataStream;
083    import iaik.cms.SignerInfo;
084    import iaik.cms.attributes.CMSContentType;
085    import iaik.cms.attributes.SigningTime;
086    import iaik.tsp.TimeStampReq;
087    import iaik.tsp.TimeStampResp;
088    import iaik.utils.Util;
089    import iaik.x509.X509Certificate;
090    
091    /**
092     * This demo shows how to add a time stamp to a SignedData message.
093     * <p> 
094     * For the stream-based part of this demo we use a SDSEncodeListener to add a 
095     * SignatureTimeStampToken attribute the SignerInfo of a SignedDataStream object.
096     * <p>
097     * A {@link iaik.smime.attributes.SignatureTimeStampToken SignatureTimeStampToken} attribute may
098     * be included as an unsigned attribute into a {@link iaik.cms.SignerInfo SignerInfo} for time stamping
099     * the signature value of a SignerInfo included in a SignedData. Using an SignedDataStream encode 
100     * listener for adding a SignatureTimeStampToken may be useful when having to time stamp the signature
101     * calculated from a large data volume. Since reading all the data into memory may cause an OutOfMemory
102     * problem, class {@link iaik.cms.SignedDataStream SignedDataStream} should to be used for 
103     * creating/encoding the SignedData object and the SignatureTimeStampToken may be added by means
104     * of a {@link iaik.cms.SDSEncodeListener SDSEncodeListener}.
105     * <p>
106     * The SDSEncodeListener used by this demo is implemented by class {@link demo.cms.tsp.TimeStampListener
107     * TimeStampListener} assuming that only one SignerInfo is included in the SignedData.
108     * This TSA from which to get the time stamp has to be provided by its HTTP URL, i.e. this demo
109     * only works with time stamp authorities providing a HTTP service (like "http://tsp.iaik.at/tsp/TspRequest").
110     * <p>
111     * To run this demo, you must have the IAIK-TSP (2.x) library in your classpath.
112     * You can get it from <a href = "https://sic.tech/products/public-key-infrastructure/tsp/" target="_blank">
113     * https://sic.tech/products/public-key-infrastructure/tsp/</a>.  
114     *
115     * @see demo.cms.tsp.TimeStampListener
116     * @see iaik.cms.SDSEncodeListener
117     * @see iaik.cms.SignedDataStream
118     * @see iaik.cms.SignedData
119     * @see iaik.cms.SignerInfo 
120     * @see iaik.smime.attributes.SignatureTimeStampToken
121     */
122    public class TimeStampDemo {
123        
124      /**
125       * The (http) url where the time stamp service is running.
126       */
127      String tsaUrl_;  
128          
129      /**
130       * The data to be signed.
131       */ 
132      byte[] message_;
133      
134      /**
135       * The signer certificate chain.
136       */ 
137      X509Certificate[] signerCerts_;
138      
139      /**
140       * Signer private key.
141       */ 
142      PrivateKey signerKey_;
143      
144      /**
145       * Constructor.
146       * Reads required keys/certs from the demo keystore.
147       */
148      public TimeStampDemo() {
149        
150        System.out.println();
151        System.out.println("**********************************************************************************");
152        System.out.println("*                           TimeStampDemo demo                                   *");
153        System.out.println("*   (shows how to add a TimeStampToken attribute to a SignedDataStream object)   *");
154        System.out.println("**********************************************************************************");
155        System.out.println();
156        
157        message_ = "This is a test message!".getBytes();
158        // signer certs
159        signerCerts_ = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
160        // signer key
161        signerKey_ = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
162      }
163      
164      
165      /**
166       * Creates a CMS <code>SignedData</code> object (stream version) and adds
167       * a TimeStampToken as unsigned attribute.
168       * <p>
169       *
170       * @param message the message to be signed, as byte representation
171       * @param mode the mode indicating whether to include the content 
172       *        (SignedDataStream.IMPLICIT) or not (SignedDataStream.EXPLICIT)
173       * @return the encoding of the <code>SignedData</code> object just created
174       * @throws Exception if the <code>SignedData</code> object cannot
175       *                      be created for some reason
176       */
177      public byte[] createSignedDataStream(byte[] message, int mode) throws Exception {
178    
179        System.out.println("Create SignedData message...");
180    
181        // we are testing the stream interface
182        ByteArrayInputStream is = new ByteArrayInputStream(message);
183        // create a new SignedData object 
184        SignedDataStream signedData = new SignedDataStream(is, mode);
185        
186        // SignedData shall include the certificate chain for verifying
187        signedData.setCertificates(signerCerts_);
188    
189        // signer cert is identifed by IssuerAndSerialNumber
190        IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(signerCerts_[0]);
191    
192        // create a new SignerInfo
193        SignerInfo signerInfo = new SignerInfo(issuer, (AlgorithmID)AlgorithmID.sha256.clone(), signerKey_);
194        // create some authenticated attributes
195        // the message digest attribute is automatically added
196        Attribute[] attributes = new Attribute[2];
197        // content type is data
198        attributes[0] = new Attribute(new CMSContentType(ObjectID.cms_data));
199        // signing time is now
200        attributes[1] = new Attribute(new SigningTime());
201        // set the attributes
202        signerInfo.setSignedAttributes(attributes);
203        // finish the creation of SignerInfo by calling method addSigner
204        try {
205          signedData.addSignerInfo(signerInfo);
206    
207        } catch (NoSuchAlgorithmException ex) {
208          throw new CMSException("No implementation for signature algorithm: "+ex.getMessage());
209        }
210        
211        // create and add a TimeStampListener to include a TimeStampToken to be obtained from the specified TSA
212        TimeStampListener tsl = new TimeStampListener(tsaUrl_);
213        tsl.setDebugStream(System.out);
214        signedData.setSDSEncodeListener(tsl);  
215    
216        // if content shall not be included write the data to any out-of-band place
217        if (mode == SignedDataStream.EXPLICIT) {
218          InputStream dataIs = signedData.getInputStream();
219          byte[] buf = new byte[1024];
220          int r;
221          while ((r = dataIs.read(buf)) > 0) {
222            ;   // skip data
223          }  
224        }
225        
226        // ensure block encoding 
227        signedData.setBlockSize(2048);
228        // return the SignedData as encoded byte array
229        ByteArrayOutputStream os = new ByteArrayOutputStream();
230        ContentInfoStream cis = new ContentInfoStream(signedData);
231        cis.writeTo(os);
232        return os.toByteArray();
233      }
234    
235      /**
236       * Parses a CMS <code>SignedData</code> object and verifies the signature.
237       *
238       * @param encoding the SignedData, as BER encoded byte array
239       * @param message the message which was transmitted out-of-band (explicit signed), or <code>null</code>
240       *                in implicit mode
241       *
242       * @return the content data as byte array
243       *
244       * @throws Exception if some error occurs
245       */
246      public byte[] getSignedDataStream(byte[] encoding, byte[] message) throws Exception {
247    
248        // we are testing the stream interface
249        ByteArrayInputStream is = new ByteArrayInputStream(encoding);
250        
251        // the ByteArrayOutputStream to which to write the content
252        ByteArrayOutputStream os = new ByteArrayOutputStream();
253            
254        SignedDataStream signedData = new SignedDataStream(is);
255        // in explcit mode supply the content data received by other means
256        if (message != null) {
257          signedData.setInputStream(new ByteArrayInputStream(message));
258        }
259        
260        // get an InputStream for reading the signed content
261        InputStream data = signedData.getInputStream();
262        Util.copyStream(data, os, null);
263    
264        // in this demo we know that we have only one signer
265        SignerInfo signerInfo = signedData.getSignerInfos()[0];
266         
267        try { 
268          // verify the signature
269          X509Certificate signerCert = signedData.verify(0);
270          // if the signature is OK the certificate of the signer is returned
271          System.out.println("Signature OK from signer: "+signerCert.getSubjectDN());
272        } catch (SignatureException ex) {
273          // if the signature is not OK a SignatureException is thrown
274          System.out.println("Signature ERROR from signer: "+signedData.getCertificate((signerInfo.getSignerIdentifier())).getSubjectDN());
275           throw new CMSException(ex.toString());
276        }
277        // get signed attributes
278        // signing time
279        SigningTime signingTime = (SigningTime)signerInfo.getSignedAttributeValue(ObjectID.signingTime);
280        if (signingTime != null) {
281          System.out.println("This message has been signed at " + signingTime.get());
282        } 
283        // content type
284        CMSContentType contentType = (CMSContentType)signerInfo.getSignedAttributeValue(ObjectID.contentType);
285        if (contentType != null) {
286          System.out.println("The content has CMS content type " + contentType.get().getName());
287        }
288        // check SignatureTimeStampToken
289        TSPDemoUtils.validateSignatureTimeStampToken(signerInfo);    
290        return os.toByteArray();
291      }
292      
293      /**
294       * Creates a CMS <code>SignedData</code> object and adds a TimeStampToken as unsigned attribute.
295       * <p>
296       *
297       * @param message the message to be signed, as byte representation
298       * @param mode the mode indicating whether to include the content 
299       *        (SignedData.IMPLICIT) or not (SignedData.EXPLICIT)
300       * @return the encoding of the <code>SignedData</code> object just created
301       * 
302       * @throws Exception if the <code>SignedData</code> object cannot
303       *                      be created for some reason
304       */
305      public byte[] createSignedData(byte[] message, int mode) throws Exception {
306    
307        System.out.println("Create SignedData message...");
308    
309        // create a new SignedData object 
310        SignedData signedData = new SignedData(message, mode);
311        
312        // SignedData shall include the certificate chain for verifying
313        signedData.setCertificates(signerCerts_);
314    
315        // signer cert is identifed by IssuerAndSerialNumber
316        IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(signerCerts_[0]);
317    
318        // create a new SignerInfo
319        SignerInfo signerInfo = new SignerInfo(issuer, (AlgorithmID)AlgorithmID.sha256.clone(), signerKey_);
320        // create some signed attributes
321        // the message digest attribute is automatically added
322        Attribute[] attributes = new Attribute[2];
323        // content type is data
324        attributes[0] = new Attribute(new CMSContentType(ObjectID.cms_data));
325        // signing time is now
326        attributes[1] = new Attribute(new SigningTime());
327        // set the attributes
328        signerInfo.setSignedAttributes(attributes);
329        // finish the creation of SignerInfo by calling method addSigner
330        try {
331          signedData.addSignerInfo(signerInfo);
332    
333        } catch (NoSuchAlgorithmException ex) {
334          throw new CMSException("No implementation for signature algorithm: "+ex.getMessage());
335        }
336        
337        // now (after signature is calculated by calling addSignerInfo) add time stamp
338        System.out.println("Create time stamp request.");
339        TimeStampReq request = TSPDemoUtils.createRequest(signerInfo, null);
340        System.out.println("Send time stamp request to " + tsaUrl_);
341        TimeStampResp response = TSPDemoUtils.sendRequest(request, tsaUrl_);
342        // validate the response
343        System.out.println("Validate response.");
344        TSPDemoUtils.validateResponse(response, request);
345        System.out.println("Response ok.");
346        // add time stamp
347        System.out.println("Add time stamp to SignerInfo.");
348        TSPDemoUtils.timeStamp(response.getTimeStampToken(), signerInfo);
349    
350        // if content shall not be included write the data to any out-of-band place
351        if (mode == SignedDataStream.EXPLICIT) {
352          InputStream dataIs = signedData.getInputStream();
353          byte[] buf = new byte[1024];
354          int r;
355          while ((r = dataIs.read(buf)) > 0) {
356            ;   // skip data
357          }  
358        }
359        
360        // return the SignedData as encoded byte array
361        ContentInfo ci = new ContentInfo(signedData);
362        return ci.getEncoded();
363      }
364      
365      /**
366       * Parses a CMS <code>SignedData</code> object and verifies the signature.
367       *
368       * @param encoding the SignedData, as BER encoded byte array
369       * @param message the message which was transmitted out-of-band (explicit signed), or <code>null</code>
370       *                in implicit mode
371       *
372       * @return the content data as byte array
373       *
374       * @throws Exception if some error occurs
375       */
376      public byte[] getSignedData(byte[] encoding, byte[] message) throws Exception {
377    
378        ByteArrayInputStream is = new ByteArrayInputStream(encoding);
379        
380        SignedData signedData = new SignedData(is);
381        // in explcit mode supply the content data received by other means
382        if (message != null) {
383          signedData.setContent(message);
384        }
385        
386    
387        // in this demo we know that we have only one signer
388        SignerInfo signerInfo = signedData.getSignerInfos()[0];
389         
390        try { 
391          // verify the signature
392          X509Certificate signerCert = signedData.verify(0);
393          // if the signature is OK the certificate of the signer is returned
394          System.out.println("Signature OK from signer: "+signerCert.getSubjectDN());
395        } catch (SignatureException ex) {
396          // if the signature is not OK a SignatureException is thrown
397          System.out.println("Signature ERROR from signer: "+signedData.getCertificate((signerInfo.getSignerIdentifier())).getSubjectDN());
398          throw new CMSException(ex.toString());
399        }
400        // get signed attributes
401        // signing time
402        SigningTime signingTime = (SigningTime)signerInfo.getSignedAttributeValue(ObjectID.signingTime);
403        if (signingTime != null) {
404          System.out.println("This message has been signed at " + signingTime.get());
405        } 
406        // content type
407        CMSContentType contentType = (CMSContentType)signerInfo.getSignedAttributeValue(ObjectID.contentType);
408        if (contentType != null) {
409          System.out.println("The content has CMS content type " + contentType.get().getName());
410        }
411        // check SignatureTimeStampToken
412        TSPDemoUtils.validateSignatureTimeStampToken(signerInfo);
413        return signedData.getContent();
414      }
415      
416      
417      /**
418       * Starts the demo.
419       */
420      public void start() {
421        
422        TSPServer.setDebugStream(System.out);
423        final TSPServer tspServer = new TSPServer();
424        
425        // start TSP server in a separate thread
426        new Thread() {
427          public void run() {
428             tspServer.start();    
429          }
430        }.start();
431      
432        // tsp server is running on local host
433        tsaUrl_ = "http://localhost:" + tspServer.getPort();
434        try {
435            
436          byte[] data;
437          byte[] receivedMessage = null;  
438          
439          //
440          // Implicit SignedDataStream
441          //
442          System.out.println("\nImplicit SignedDataStream TSP demo [create]:\n");
443          data = createSignedDataStream(message_, SignedDataStream.IMPLICIT);
444          // parse
445          System.out.println("\nImplicit SignedDataStream TSP demo [parse]:\n");
446          receivedMessage = getSignedDataStream(data, null);
447          System.out.print("\nSigned content: ");
448          System.out.println(new String(receivedMessage));
449    
450          //
451          // Explicit SignedDataStream
452          //
453          System.out.println("\nExplicit SignedDataStream TSP demo [create]:\n");
454          data = createSignedDataStream(message_, SignedDataStream.EXPLICIT);
455          // parse
456          System.out.println("\nExplicit SignedDataStream TSP demo [parse]:\n");
457          receivedMessage = getSignedDataStream(data, message_);
458          System.out.print("\nSigned content: ");
459          System.out.println(new String(receivedMessage));
460          
461          // non stream
462          
463          //
464          // Implicit SignedData
465          //
466          System.out.println("\nImplicit SignedData TSP demo [create]:\n");
467          data = createSignedData(message_, SignedData.IMPLICIT);
468          // parse
469          System.out.println("\nImplicit SignedData TSP demo [parse]:\n");
470          receivedMessage = getSignedData(data, null);
471          System.out.print("\nSigned content: ");
472          System.out.println(new String(receivedMessage));
473    
474          //
475          // Explicit SignedData
476          //
477          System.out.println("\nExplicit SignedData TSP demo [create]:\n");
478          data = createSignedData(message_, SignedData.EXPLICIT);
479          // parse
480          System.out.println("\nExplicit SignedData TSP demo [parse]:\n");
481          receivedMessage = getSignedData(data, message_);
482          System.out.print("\nSigned content: ");
483          System.out.println(new String(receivedMessage));
484    
485          
486    
487            } catch (Exception ex) {
488              ex.printStackTrace();
489              throw new RuntimeException(ex.toString());
490            } finally {
491          // stop server
492          tspServer.stop();
493        }
494      }
495    
496        
497      /**
498       * Main method.
499       * 
500       * @throws IOException 
501       *            if an I/O error occurs when reading required keys
502       *            and certificates from files
503       */
504      public static void main(String argv[]) throws IOException {
505       try {
506         DemoUtil.initDemos();
507         (new TimeStampDemo()).start();
508         System.out.println("\nReady!");
509       } catch (Exception ex) {    
510         ex.printStackTrace();      
511       }
512       
513       DemoUtil.waitKey();
514      }
515    }