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 }