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/signedData/CounterSignatureDemo.java 30 12.02.25 17:58 Dbratko $ 029// $Revision: 30 $ 030// 031 032package demo.cms.signedData; 033 034import iaik.asn1.CodingException; 035import iaik.asn1.ObjectID; 036import iaik.asn1.structures.AlgorithmID; 037import iaik.asn1.structures.Attribute; 038import iaik.asn1.structures.AttributeValue; 039import iaik.cms.CMSException; 040import iaik.cms.ContentInfo; 041import iaik.cms.ContentInfoStream; 042import iaik.cms.IssuerAndSerialNumber; 043import iaik.cms.SignedData; 044import iaik.cms.SignedDataStream; 045import iaik.cms.SignerInfo; 046import iaik.cms.attributes.CMSContentType; 047import iaik.cms.attributes.CounterSignature; 048import iaik.cms.attributes.SigningTime; 049import iaik.utils.Util; 050import iaik.x509.X509Certificate; 051 052import java.io.ByteArrayInputStream; 053import java.io.ByteArrayOutputStream; 054import java.io.IOException; 055import java.io.InputStream; 056import java.security.NoSuchAlgorithmException; 057import java.security.PrivateKey; 058import java.security.SignatureException; 059 060import demo.DemoUtil; 061import demo.keystore.CMSKeyStore; 062 063/** 064 * This class demonstrates the usage of the CounterSignature attribute. 065 * <p> 066 * A {@link iaik.cms.attributes.CounterSignature CounterSignature} attribute may be included 067 * as an unsigned attribute into a {@link iaik.cms.SignerInfo SignerInfo} for counter signing 068 * (signing in serial) the signature value of a SignerInfo included in a SignedData. The value 069 * of a CounterSignature attribute itself is a SignerInfo. 070 * <p> 071 * This demo shows how a CounterSignature attribute may be added to some SignerInfo that belongs 072 * to a SignedData object just parsed/verified. This class demonstrates adding/verifying of a 073 * CounterSignature attribute to both the {@link iaik.cms.SignedDataStream stream} and the 074 * {@link iaik.cms.SignedData non-stream} implementations of the SignedData type. Since when 075 * parsing an implicit -- where the content is included -- SignedData object, SignerInfos 076 * can not accessed before the data has been processed, adding a counter signature to 077 * a {@link iaik.cms.SignedDataStream SignedDataStream} may require a different proceeding 078 * than adding it to a {@link iaik.cms.SignedData SignedData} object. For that reason a 079 * {@link CounterSignatureListener CounterSignatureListener} is used for the 080 * stream demos to listen on and add the counter signature during the encoding process. 081 * 082 * @see CounterSignatureListener 083 * @see iaik.cms.attributes.CounterSignature 084 * @see iaik.cms.SDSEncodeListener 085 * @see iaik.cms.SignedDataStream 086 * @see iaik.cms.SignerInfo 087 */ 088public class CounterSignatureDemo { 089 090 byte[] message; 091 092 // signing certificate of user 1 093 X509Certificate user1_sign; 094 // signing private key of user 1 095 PrivateKey user1_sign_pk; 096 // signing certificate of user 2 (counter signer) 097 X509Certificate user2_sign; 098 // signing private key of user 2 (counter signer) 099 PrivateKey user2_sign_pk; 100 101 // a certificate chain containing the user certs + CA 102 X509Certificate[] certificates; 103 104 /** 105 * Constructor. 106 * Reads required keys/certs from the demo keystore. 107 */ 108 public CounterSignatureDemo() { 109 110 System.out.println(); 111 System.out.println("**********************************************************************************"); 112 System.out.println("* CounterSignatureDemo demo *"); 113 System.out.println("* (shows the usage of the CounterSignature attribute implementation) *"); 114 System.out.println("**********************************************************************************"); 115 System.out.println(); 116 117 message = "This is a test of the CMS implementation!".getBytes(); 118 // signing certs 119 certificates = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1); 120 user1_sign = certificates[0]; 121 user1_sign_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1); 122 user2_sign = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1)[0]; 123 user2_sign_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1); 124 } 125 126 /** 127 * Creates a CMS <code>SignedData</code> object. 128 * <p> 129 * 130 * @param message the message to be signed, as byte representation 131 * @param mode the mode indicating whether to include the content 132 * (SignedDataStream.IMPLICIT) or not (SignedDataStream.EXPLICIT) 133 * @return the encoding of the <code>SignedData</code> object just created 134 * @throws Exception if the <code>SignedData</code> object cannot 135 * be created for some reason 136 */ 137 public byte[] createSignedDataStream(byte[] message, int mode) throws Exception { 138 139 System.out.println("Create a new message signed by user 1:"); 140 141 // we are testing the stream interface 142 ByteArrayInputStream is = new ByteArrayInputStream(message); 143 // create a new SignedData object which includes the data 144 SignedDataStream signed_data = new SignedDataStream(is, mode); 145 146 // SignedData shall include the certificate chain for verifying 147 signed_data.setCertificates(certificates); 148 149 // cert at index 0 is the user certificate 150 IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(user1_sign); 151 152 // create a new SignerInfo 153 SignerInfo signer_info = new SignerInfo(issuer, (AlgorithmID)AlgorithmID.sha256.clone(), user1_sign_pk); 154 // create some authenticated attributes 155 // the message digest attribute is automatically added 156 Attribute[] attributes = new Attribute[2]; 157 // content type is data 158 attributes[0] = new Attribute(new CMSContentType(ObjectID.cms_data)); 159 // signing time is now 160 attributes[1] = new Attribute(new SigningTime()); 161 // set the attributes 162 signer_info.setSignedAttributes(attributes); 163 // finish the creation of SignerInfo by calling method addSigner 164 try { 165 signed_data.addSignerInfo(signer_info); 166 167 } catch (NoSuchAlgorithmException ex) { 168 throw new CMSException("No implementation for signature algorithm: "+ex.getMessage()); 169 } 170 // ensure block encoding 171 signed_data.setBlockSize(2048); 172 173 // write the data through SignedData to any out-of-band place 174 if (mode == SignedDataStream.EXPLICIT) { 175 InputStream data_is = signed_data.getInputStream(); 176 byte[] buf = new byte[1024]; 177 int r; 178 while ((r = data_is.read(buf)) > 0) { 179 ; // skip data 180 } 181 } 182 183 // return the SignedData as encoded byte array with block size 2048 184 ByteArrayOutputStream os = new ByteArrayOutputStream(); 185 // wrap into ContentInfo 186 ContentInfoStream ci = new ContentInfoStream(signed_data); 187 ci.writeTo(os); 188 return os.toByteArray(); 189 } 190 191 /** 192 * Parses a CMS <code>SignedData</code> object and verifies the signatures 193 * for all participated signers. 194 * 195 * @param signedData the SignedData, as BER encoded byte array 196 * @param message the the message which was transmitted out-of-band (explicit signed) 197 * @param counterSign whether to use a SDSEncodeListener to add a SignerInfo 198 * and encode the SignedData again 199 * 200 * @return the inherent message as byte array, or the BER encoded SignedData if 201 * it shall be encoded again (counter signing phase) 202 * @throws Exception if an error occurs 203 */ 204 public byte[] getSignedDataStream(byte[] signedData, byte[] message, boolean counterSign) 205 throws Exception { 206 207 // we are testing the stream interface 208 ByteArrayInputStream is = new ByteArrayInputStream(signedData); 209 210 // the ByteArrayOutputStream to which to write the content 211 ByteArrayOutputStream os = new ByteArrayOutputStream(); 212 213 SignedDataStream signed_data = new SignedDataStream(is); 214 215 // content included (implicit mode)? 216 boolean implicit = (signed_data.getMode() == SignedDataStream.IMPLICIT); 217 218 if (implicit == false) { 219 // in explicit mode explicitly supply the content for hash computation 220 signed_data.setInputStream(new ByteArrayInputStream(message)); 221 } 222 223 224 if (counterSign) { 225 // we want to write the SignedData again 226 // we add a counter signature attribute to the first signer 227 228 // add the CounterSignature via SDSEncodeListener 229 CounterSignatureListener csl = 230 new CounterSignatureListener(new IssuerAndSerialNumber(user2_sign), 231 (AlgorithmID)AlgorithmID.sha256.clone(), 232 user2_sign_pk); 233 // we only want to counter sign some specific signer 234 csl.setCertOfSignerToBeCounterSigned(user1_sign); 235 236 if (implicit) { 237 // in implicit mode copy data to os 238 csl.setOutputStream(os); 239 signed_data.setSDSEncodeListener(csl); 240 241 } else { 242 signed_data.setSDSEncodeListener(csl); 243 // get an InputStream for reading the signed content 244 InputStream data = signed_data.getInputStream(); 245 Util.copyStream(data, os, null); 246 } 247 248 // ensure block encoding 249 signed_data.setBlockSize(2048); 250 // return the SignedData as encoded byte array with block size 2048 251 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 252 // wrap into ContentInfo 253 ContentInfoStream ci = new ContentInfoStream(signed_data); 254 ci.writeTo(baos); 255 256 // we read the content 257 byte[] content = os.toByteArray(); 258 System.out.println("Content: " + new String(content)); 259 260 // return encoded SignedData 261 return baos.toByteArray(); 262 263 } else { 264 265 // get an InputStream for reading the signed content 266 InputStream data = signed_data.getInputStream(); 267 os = new ByteArrayOutputStream(); 268 Util.copyStream(data, os, null); 269 270 System.out.println("SignedData contains the following signer information:"); 271 SignerInfo[] signer_infos = signed_data.getSignerInfos(); 272 273 int numberOfSignerInfos = signer_infos.length; 274 if (numberOfSignerInfos == 0) { 275 String warning = "Warning: Unsigned message (no SignerInfo included)!"; 276 System.err.println(warning); 277 throw new CMSException(warning); 278 } else { 279 for (int i = 0; i < numberOfSignerInfos; i++) { 280 try { 281 // verify the signed data using the SignerInfo at index i 282 X509Certificate signer_cert = signed_data.verify(i); 283 // if the signature is OK the certificate of the signer is returned 284 System.out.println("Signature OK from signer: "+signer_cert.getSubjectDN()); 285 // get signed attributes 286 // signing time 287 SigningTime signingTime = (SigningTime)signer_infos[i].getSignedAttributeValue(ObjectID.signingTime); 288 if (signingTime != null) { 289 System.out.println("This message has been signed at " + signingTime.get()); 290 } 291 // content type 292 CMSContentType contentType = (CMSContentType)signer_infos[i].getSignedAttributeValue(ObjectID.contentType); 293 if (contentType != null) { 294 System.out.println("The content has CMS content type " + contentType.get().getName()); 295 } 296 // counter signature? 297 Attribute counterSignatureAttribute = signer_infos[i].getUnsignedAttribute(ObjectID.countersignature); 298 if (counterSignatureAttribute != null) { 299 AttributeValue[] counterSignatures = counterSignatureAttribute.getAttributeValues(); 300 System.out.println("This SignerInfo is counter signed from: "); 301 for (int j = 0; j < counterSignatures.length; j++) { 302 CounterSignature counterSignature = (CounterSignature)counterSignatures[j]; 303 try { 304 if (counterSignature.verify(user2_sign.getPublicKey(), signer_infos[i])) { 305 System.out.println("Signature OK from counter signer: "+counterSignature.getSignerIdentifier()); 306 } else { 307 System.out.println("Signature ERROR from counter signer: "+counterSignature.getSignerIdentifier()); 308 } 309 } catch (SignatureException ex) { 310 System.out.println("Signature ERROR from counter signer: "+counterSignature.getSignerIdentifier()); 311 throw new CMSException(ex.toString()); 312 } 313 signingTime = (SigningTime)counterSignature.getSignedAttributeValue(ObjectID.signingTime); 314 if (signingTime != null) { 315 System.out.println("Counter signature has been created " + signingTime.get()); 316 } 317 } 318 } 319 320 } catch (SignatureException ex) { 321 // if the signature is not OK a SignatureException is thrown 322 System.err.println("Signature ERROR from signer: "+signed_data.getCertificate((signer_infos[i].getSignerIdentifier())).getSubjectDN()); 323 throw new CMSException(ex.toString()); 324 } catch (CodingException ex) { 325 throw new CMSException("Attribute decoding error: " + ex.toString()); 326 } 327 } 328 // in practice we also would validate the signer certificate(s) 329 } 330 331 // return content 332 return os.toByteArray(); 333 } 334 } 335 336 337 /** 338 * Creates a CMS <code>SignedData</code> object. 339 * <p> 340 * 341 * @param message the message to be signed, as byte representation 342 * @param mode the mode indicating whether to include the content 343 * (SignedDataStream.IMPLICIT) or not (SignedDataStream.EXPLICIT) 344 * @return the encoding of the <code>SignedData</code> object just created 345 * @throws CMSException if the <code>SignedData</code> object cannot 346 * be created 347 * @throws Exception if an error occurs 348 */ 349 public byte[] createSignedData(byte[] message, int mode) throws Exception { 350 351 System.out.println("Create a new message signed by user 1:"); 352 353 // create a new SignedData object 354 SignedData signed_data = new SignedData(message, mode); 355 356 // SignedData shall include the certificate chain for verifying 357 signed_data.setCertificates(certificates); 358 359 // cert at index 0 is the user certificate 360 IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(user1_sign); 361 362 // create a new SignerInfo 363 SignerInfo signer_info = new SignerInfo(issuer, (AlgorithmID)AlgorithmID.sha256.clone(), user1_sign_pk); 364 // create some authenticated attributes 365 // the message digest attribute is automatically added 366 Attribute[] attributes = new Attribute[2]; 367 // content type is data 368 attributes[0] = new Attribute(new CMSContentType(ObjectID.cms_data)); 369 // signing time is now 370 attributes[1] = new Attribute(new SigningTime()); 371 // set the attributes 372 signer_info.setSignedAttributes(attributes); 373 // finish the creation of SignerInfo by calling method addSigner 374 try { 375 signed_data.addSignerInfo(signer_info); 376 377 } catch (NoSuchAlgorithmException ex) { 378 throw new CMSException("No implementation for signature algorithm: "+ex.getMessage()); 379 } 380 381 // return the SignedData as encoded byte array with block size 2048 382 ByteArrayOutputStream os = new ByteArrayOutputStream(); 383 // wrap into ContentInfo 384 ContentInfo ci = new ContentInfo(signed_data); 385 ci.writeTo(os); 386 return os.toByteArray(); 387 } 388 389 /** 390 * Parses a CMS <code>SignedData</code> object and verifies the signatures 391 * for all participated signers. 392 * 393 * @param signedData the SignedData, as BER encoded byte array 394 * @param message the the message which was transmitted out-of-band (explicit signed) 395 * @param counterSign whether to use a SDSEncodeListener to add a SignerInfo 396 * and encode the SignedData again 397 * 398 * @return the inherent message as byte array, or the BER encoded SignedData if 399 * it shall be encoded again (counter signing phase) 400 * @throws Exception if any error occurs 401 */ 402 public byte[] getSignedData(byte[] signedData, byte[] message, boolean counterSign) 403 throws Exception { 404 405 // we are testing the stream interface 406 ByteArrayInputStream is = new ByteArrayInputStream(signedData); 407 408 SignedData signed_data = new SignedData(is); 409 410 // content included (implicit mode)? 411 boolean implicit = (signed_data.getMode() == SignedData.IMPLICIT); 412 if (implicit == false) { 413 // in explcit mode explictly supply the content data to do the hash calculation 414 signed_data.setContent(message); 415 } 416 417 System.out.println("SignedData contains the following signer information:"); 418 SignerInfo[] signer_infos = signed_data.getSignerInfos(); 419 420 int numberOfSignerInfos = signer_infos.length; 421 if (numberOfSignerInfos == 0) { 422 String warning = "Warning: Unsigned message (no SignerInfo included)!"; 423 System.err.println(warning); 424 throw new CMSException(warning); 425 } else { 426 for (int i = 0; i < numberOfSignerInfos; i++) { 427 try { 428 // verify the signed data using the SignerInfo at index i 429 X509Certificate signer_cert = signed_data.verify(i); 430 // if the signature is OK the certificate of the signer is returned 431 System.out.println("Signature OK from signer: "+signer_cert.getSubjectDN()); 432 // get signed attributes 433 // signing time 434 SigningTime signingTime = (SigningTime)signer_infos[i].getSignedAttributeValue(ObjectID.signingTime); 435 if (signingTime != null) { 436 System.out.println("This message has been signed at " + signingTime.get()); 437 } 438 // content type 439 CMSContentType contentType = (CMSContentType)signer_infos[i].getSignedAttributeValue(ObjectID.contentType); 440 if (contentType != null) { 441 System.out.println("The content has CMS content type " + contentType.get().getName()); 442 } 443 // counter signature? 444 Attribute counterSignatureAttribute = signer_infos[i].getUnsignedAttribute(ObjectID.countersignature); 445 if (counterSignatureAttribute != null) { 446 AttributeValue[] counterSignatures = counterSignatureAttribute.getAttributeValues(); 447 System.out.println("This SignerInfo is counter signed from: "); 448 for (int j = 0; j < counterSignatures.length; j++) { 449 CounterSignature counterSignature = (CounterSignature)counterSignatures[j]; 450 try { 451 if (counterSignature.verify(user2_sign.getPublicKey(), signer_infos[i])) { 452 System.out.println("Signature OK from counter signer: "+counterSignature.getSignerIdentifier()); 453 } else { 454 System.out.println("Signature ERROR from counter signer: "+counterSignature.getSignerIdentifier()); 455 } 456 } catch (SignatureException ex) { 457 System.out.println("Signature ERROR from counter signer: "+counterSignature.getSignerIdentifier()); 458 throw new CMSException(ex.toString()); 459 } 460 signingTime = (SigningTime)counterSignature.getSignedAttributeValue(ObjectID.signingTime); 461 if (signingTime != null) { 462 System.out.println("Counter signature has been created " + signingTime.get()); 463 } 464 } 465 } 466 467 } catch (SignatureException ex) { 468 // if the signature is not OK a SignatureException is thrown 469 System.out.println("Signature ERROR from signer: "+signed_data.getCertificate((signer_infos[i].getSignerIdentifier())).getSubjectDN()); 470 throw new CMSException(ex.toString()); 471 } catch (CodingException ex) { 472 throw new CMSException("Attribute decoding error: " + ex.toString()); 473 } 474 } 475 // in practice we also would validate the signer certificate(s) 476 } 477 478 if (counterSign) { 479 // we want to write the SignedData again 480 // we add a counter signature attribute to the first signer 481 CounterSignature counterSignature = new CounterSignature(new IssuerAndSerialNumber(user2_sign), 482 (AlgorithmID)AlgorithmID.sha256.clone(), user2_sign_pk); 483 // create some authenticated attributes 484 // the message digest attribute is automatically added 485 // signing time is now 486 SigningTime signingTime = new SigningTime(); 487 Attribute[] attributes = { new Attribute(signingTime) }; 488 // set the attributes 489 counterSignature.setSignedAttributes(attributes); 490 // now counter sign first SignerInfo 491 counterSignature.counterSign(signer_infos[0]); 492 // and add the counter signature as unsigned attribute 493 Attribute[] usignedAttributes = { new Attribute(counterSignature) }; 494 signer_infos[0].addUnsignedAttributes(usignedAttributes); 495 496 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 497 ContentInfo ci = new ContentInfo(signed_data); 498 ci.writeTo(baos); 499 signed_data.writeTo(baos); 500 501 // we read the content 502 System.out.println("Content: " + new String(signed_data.getContent())); 503 504 return baos.toByteArray(); 505 506 } else { 507 // return the content 508 return signed_data.getContent(); 509 } 510 511 512 } 513 514 /** 515 * Starts the demo. 516 */ 517 public void start() { 518 519 try { 520 521 byte[] data; 522 byte[] received_message = null; 523 524 // 525 // test CMS Implicit SignedDataStream 526 // 527 System.out.println("\nImplicit SignedDataStream demo [create]:\n"); 528 data = createSignedDataStream(message, SignedDataStream.IMPLICIT); 529 // parse and encode again 530 System.out.println("\nImplicit SignedDataStream demo [counter sign]:\n"); 531 data = getSignedDataStream(data, null, true); 532 // parse 533 System.out.println("\nImplicit SignedDataStream demo [parse]:\n"); 534 received_message = getSignedDataStream(data, null, false); 535 System.out.print("\nSigned content: "); 536 System.out.println(new String(received_message)); 537 538 // 539 // test CMS Explicit SignedDataStream 540 // 541 System.out.println("\nExplicit SignedDataStream demo [create]:\n"); 542 data = createSignedDataStream(message, SignedDataStream.EXPLICIT); 543 // parse and encode again 544 System.out.println("\nExplicit SignedDataStream demo [counter sign]:\n"); 545 data = getSignedDataStream(data, message, true); 546 547 System.out.println("\nExplicit SignedDataStream demo [parse]:\n"); 548 received_message = getSignedDataStream(data, message, false); 549 System.out.print("\nSigned content: "); 550 System.out.println(new String(received_message)); 551 552 // 553 // test CMS Implicit SignedData 554 // 555 System.out.println("\nImplicit SignedData demo [create]:\n"); 556 data = createSignedData(message, SignedData.IMPLICIT); 557 // parse and encode again 558 System.out.println("\nImplicit SignedData demo [counter sign]:\n"); 559 data = getSignedData(data, null, true); 560 // parse 561 System.out.println("\nImplicit SignedData demo [parse]:\n"); 562 received_message = getSignedData(data, null, false); 563 System.out.print("\nSigned content: "); 564 System.out.println(new String(received_message)); 565 566 // 567 // test CMS Explicit SignedData 568 // 569 System.out.println("\nExplicit SignedData demo [create]:\n"); 570 data = createSignedData(message, SignedData.EXPLICIT); 571 // parse and encode again 572 System.out.println("\nExplicit SignedData demo [counter sign]:\n"); 573 data = getSignedData(data, message, true); 574 575 System.out.println("\nExplicit SignedData demo [parse]:\n"); 576 received_message = getSignedData(data, message, false); 577 System.out.print("\nSigned content: "); 578 System.out.println(new String(received_message)); 579 580 } catch (Exception ex) { 581 ex.printStackTrace(); 582 throw new RuntimeException(ex.toString()); 583 } 584 } 585 586 587 /** 588 * Main method. 589 * 590 * @throws IOException 591 * if an I/O error occurs when reading required keys 592 * and certificates from files 593 */ 594 public static void main(String argv[]) throws IOException { 595 try { 596 DemoUtil.initDemos(); 597 (new CounterSignatureDemo()).start(); 598 System.out.println("\nReady!"); 599 } catch (Exception ex) { 600 ex.printStackTrace(); 601 } 602 DemoUtil.waitKey(); 603 } 604}