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}