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