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 }