1 package org.bouncycastle.tsp; 2 3 import java.io.IOException; 4 import java.io.OutputStream; 5 import java.math.BigInteger; 6 import java.text.SimpleDateFormat; 7 import java.util.ArrayList; 8 import java.util.Collection; 9 import java.util.Date; 10 import java.util.Enumeration; 11 import java.util.HashMap; 12 import java.util.Iterator; 13 import java.util.List; 14 import java.util.Locale; 15 import java.util.Map; 16 import java.util.SimpleTimeZone; 17 18 import org.bouncycastle.asn1.ASN1Boolean; 19 import org.bouncycastle.asn1.ASN1Encoding; 20 import org.bouncycastle.asn1.ASN1GeneralizedTime; 21 import org.bouncycastle.asn1.ASN1Integer; 22 import org.bouncycastle.asn1.ASN1ObjectIdentifier; 23 import org.bouncycastle.asn1.cms.AttributeTable; 24 import org.bouncycastle.asn1.ess.ESSCertID; 25 import org.bouncycastle.asn1.ess.ESSCertIDv2; 26 import org.bouncycastle.asn1.ess.SigningCertificate; 27 import org.bouncycastle.asn1.ess.SigningCertificateV2; 28 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers; 29 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; 30 import org.bouncycastle.asn1.tsp.Accuracy; 31 import org.bouncycastle.asn1.tsp.MessageImprint; 32 import org.bouncycastle.asn1.tsp.TSTInfo; 33 import org.bouncycastle.asn1.x509.AlgorithmIdentifier; 34 import org.bouncycastle.asn1.x509.Extensions; 35 import org.bouncycastle.asn1.x509.ExtensionsGenerator; 36 import org.bouncycastle.asn1.x509.GeneralName; 37 import org.bouncycastle.asn1.x509.GeneralNames; 38 import org.bouncycastle.asn1.x509.IssuerSerial; 39 import org.bouncycastle.cert.X509CertificateHolder; 40 import org.bouncycastle.cms.CMSAttributeTableGenerationException; 41 import org.bouncycastle.cms.CMSAttributeTableGenerator; 42 import org.bouncycastle.cms.CMSException; 43 import org.bouncycastle.cms.CMSProcessableByteArray; 44 import org.bouncycastle.cms.CMSSignedData; 45 import org.bouncycastle.cms.CMSSignedDataGenerator; 46 import org.bouncycastle.cms.SignerInfoGenerator; 47 import org.bouncycastle.operator.DigestCalculator; 48 import org.bouncycastle.util.CollectionStore; 49 import org.bouncycastle.util.Store; 50 51 /** 52 * Currently the class supports ESSCertID by if a digest calculator based on SHA1 is passed in, otherwise it uses 53 * ESSCertIDv2. In the event you need to pass both types, you will need to override the SignedAttributeGenerator 54 * for the SignerInfoGeneratorBuilder you are using. For the default for ESSCertIDv2 the code will look something 55 * like the following: 56 * <pre> 57 * final ESSCertID essCertid = new ESSCertID(certHashSha1, issuerSerial); 58 * final ESSCertIDv2 essCertidV2 = new ESSCertIDv2(certHashSha256, issuerSerial); 59 * 60 * signerInfoGenBuilder.setSignedAttributeGenerator(new CMSAttributeTableGenerator() 61 * { 62 * public AttributeTable getAttributes(Map parameters) 63 * throws CMSAttributeTableGenerationException 64 * { 65 * CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(); 66 * 67 * AttributeTable table = attrGen.getAttributes(parameters); 68 * 69 * table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid)); 70 * table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new SigningCertificateV2(essCertidV2)); 71 * 72 * return table; 73 * } 74 * }); 75 * </pre> 76 */ 77 public class TimeStampTokenGenerator 78 { 79 /** 80 * Create time-stamps with a resolution of 1 second (the default). 81 */ 82 public static final int R_SECONDS = 0; 83 84 /** 85 * Create time-stamps with a resolution of 1 tenth of a second. 86 */ 87 public static final int R_TENTHS_OF_SECONDS = 1; 88 89 /** 90 * Create time-stamps with a resolution of 1 hundredth of a second. 91 */ 92 public static final int R_HUNDREDTHS_OF_SECONDS = 2; 93 94 /** 95 * @deprecated use R_HUNDREDTHS_OF_SECONDS - this field will be deleted!! 96 */ 97 public static final int R_MICROSECONDS = 2; 98 99 /** 100 * Create time-stamps with a resolution of 1 millisecond. 101 */ 102 public static final int R_MILLISECONDS = 3; 103 104 private int resolution = R_SECONDS; 105 private Locale locale = null; // default locale 106 107 private int accuracySeconds = -1; 108 109 private int accuracyMillis = -1; 110 111 private int accuracyMicros = -1; 112 113 boolean ordering = false; 114 115 GeneralName tsa = null; 116 117 private ASN1ObjectIdentifier tsaPolicyOID; 118 119 private List certs = new ArrayList(); 120 private List crls = new ArrayList(); 121 private List attrCerts = new ArrayList(); 122 private Map otherRevoc = new HashMap(); 123 private SignerInfoGenerator signerInfoGen; 124 125 /** 126 * Basic Constructor - set up a calculator based on signerInfoGen with a ESSCertID calculated from 127 * the signer's associated certificate using the sha1DigestCalculator. If alternate values are required 128 * for id-aa-signingCertificate they should be added to the signerInfoGen object before it is passed in, 129 * otherwise a standard digest based value will be added. 130 * 131 * @param signerInfoGen the generator for the signer we are using. 132 * @param digestCalculator calculator for to use for digest of certificate. 133 * @param tsaPolicy tasPolicy to send. 134 * @throws IllegalArgumentException if calculator is not SHA-1 or there is no associated certificate for the signer, 135 * @throws TSPException if the signer certificate cannot be processed. 136 */ TimeStampTokenGenerator( final SignerInfoGenerator signerInfoGen, DigestCalculator digestCalculator, ASN1ObjectIdentifier tsaPolicy)137 public TimeStampTokenGenerator( 138 final SignerInfoGenerator signerInfoGen, 139 DigestCalculator digestCalculator, 140 ASN1ObjectIdentifier tsaPolicy) 141 throws IllegalArgumentException, TSPException 142 { 143 this(signerInfoGen, digestCalculator, tsaPolicy, false); 144 } 145 146 /** 147 * Basic Constructor - set up a calculator based on signerInfoGen with a ESSCertID calculated from 148 * the signer's associated certificate using the sha1DigestCalculator. If alternate values are required 149 * for id-aa-signingCertificate they should be added to the signerInfoGen object before it is passed in, 150 * otherwise a standard digest based value will be added. 151 * 152 * @param signerInfoGen the generator for the signer we are using. 153 * @param digestCalculator calculator for to use for digest of certificate. 154 * @param tsaPolicy tasPolicy to send. 155 * @param isIssuerSerialIncluded should issuerSerial be included in the ESSCertIDs, true if yes, by default false. 156 * @throws IllegalArgumentException if calculator is not SHA-1 or there is no associated certificate for the signer, 157 * @throws TSPException if the signer certificate cannot be processed. 158 */ TimeStampTokenGenerator( final SignerInfoGenerator signerInfoGen, DigestCalculator digestCalculator, ASN1ObjectIdentifier tsaPolicy, boolean isIssuerSerialIncluded)159 public TimeStampTokenGenerator( 160 final SignerInfoGenerator signerInfoGen, 161 DigestCalculator digestCalculator, 162 ASN1ObjectIdentifier tsaPolicy, 163 boolean isIssuerSerialIncluded) 164 throws IllegalArgumentException, TSPException 165 { 166 this.signerInfoGen = signerInfoGen; 167 this.tsaPolicyOID = tsaPolicy; 168 169 if (!signerInfoGen.hasAssociatedCertificate()) 170 { 171 throw new IllegalArgumentException("SignerInfoGenerator must have an associated certificate"); 172 } 173 174 X509CertificateHolder assocCert = signerInfoGen.getAssociatedCertificate(); 175 TSPUtil.validateCertificate(assocCert); 176 177 try 178 { 179 OutputStream dOut = digestCalculator.getOutputStream(); 180 181 dOut.write(assocCert.getEncoded()); 182 183 dOut.close(); 184 185 if (digestCalculator.getAlgorithmIdentifier().getAlgorithm().equals(OIWObjectIdentifiers.idSHA1)) 186 { 187 final ESSCertID essCertid = new ESSCertID(digestCalculator.getDigest(), 188 isIssuerSerialIncluded ? new IssuerSerial(new GeneralNames(new GeneralName(assocCert.getIssuer())), assocCert.getSerialNumber()) 189 : null); 190 191 this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator() 192 { 193 public AttributeTable getAttributes(Map parameters) 194 throws CMSAttributeTableGenerationException 195 { 196 AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters); 197 198 if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificate) == null) 199 { 200 return table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid)); 201 } 202 203 return table; 204 } 205 }, signerInfoGen.getUnsignedAttributeTableGenerator()); 206 } 207 else 208 { 209 AlgorithmIdentifier digAlgID = new AlgorithmIdentifier(digestCalculator.getAlgorithmIdentifier().getAlgorithm()); 210 final ESSCertIDv2 essCertid = new ESSCertIDv2(digAlgID, digestCalculator.getDigest(), 211 isIssuerSerialIncluded ? new IssuerSerial(new GeneralNames(new GeneralName(assocCert.getIssuer())), new ASN1Integer(assocCert.getSerialNumber())) 212 : null); 213 214 this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator() 215 { 216 public AttributeTable getAttributes(Map parameters) 217 throws CMSAttributeTableGenerationException 218 { 219 AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters); 220 221 if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificateV2) == null) 222 { 223 return table.add(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new SigningCertificateV2(essCertid)); 224 } 225 226 return table; 227 } 228 }, signerInfoGen.getUnsignedAttributeTableGenerator()); 229 } 230 } 231 catch (IOException e) 232 { 233 throw new TSPException("Exception processing certificate.", e); 234 } 235 } 236 237 /** 238 * Add the store of X509 Certificates to the generator. 239 * 240 * @param certStore a Store containing X509CertificateHolder objects 241 */ addCertificates( Store certStore)242 public void addCertificates( 243 Store certStore) 244 { 245 certs.addAll(certStore.getMatches(null)); 246 } 247 248 /** 249 * 250 * @param crlStore a Store containing X509CRLHolder objects. 251 */ addCRLs( Store crlStore)252 public void addCRLs( 253 Store crlStore) 254 { 255 crls.addAll(crlStore.getMatches(null)); 256 } 257 258 /** 259 * 260 * @param attrStore a Store containing X509AttributeCertificate objects. 261 */ addAttributeCertificates( Store attrStore)262 public void addAttributeCertificates( 263 Store attrStore) 264 { 265 attrCerts.addAll(attrStore.getMatches(null)); 266 } 267 268 /** 269 * Add a Store of otherRevocationData to the CRL set to be included with the generated TimeStampToken. 270 * 271 * @param otherRevocationInfoFormat the OID specifying the format of the otherRevocationInfo data. 272 * @param otherRevocationInfos a Store of otherRevocationInfo data to add. 273 */ addOtherRevocationInfo( ASN1ObjectIdentifier otherRevocationInfoFormat, Store otherRevocationInfos)274 public void addOtherRevocationInfo( 275 ASN1ObjectIdentifier otherRevocationInfoFormat, 276 Store otherRevocationInfos) 277 { 278 otherRevoc.put(otherRevocationInfoFormat, otherRevocationInfos.getMatches(null)); 279 } 280 281 /** 282 * Set the resolution of the time stamp - R_SECONDS (the default), R_TENTH_OF_SECONDS, R_MICROSECONDS, R_MILLISECONDS 283 * 284 * @param resolution resolution of timestamps to be produced. 285 */ setResolution(int resolution)286 public void setResolution(int resolution) 287 { 288 this.resolution = resolution; 289 } 290 291 /** 292 * Set a Locale for time creation - you may need to use this if the default locale 293 * doesn't use a Gregorian calender so that the GeneralizedTime produced is compatible with other ASN.1 implementations. 294 * 295 * @param locale a locale to use for converting system time into a GeneralizedTime. 296 */ setLocale(Locale locale)297 public void setLocale(Locale locale) 298 { 299 this.locale = locale; 300 } 301 setAccuracySeconds(int accuracySeconds)302 public void setAccuracySeconds(int accuracySeconds) 303 { 304 this.accuracySeconds = accuracySeconds; 305 } 306 setAccuracyMillis(int accuracyMillis)307 public void setAccuracyMillis(int accuracyMillis) 308 { 309 this.accuracyMillis = accuracyMillis; 310 } 311 setAccuracyMicros(int accuracyMicros)312 public void setAccuracyMicros(int accuracyMicros) 313 { 314 this.accuracyMicros = accuracyMicros; 315 } 316 setOrdering(boolean ordering)317 public void setOrdering(boolean ordering) 318 { 319 this.ordering = ordering; 320 } 321 setTSA(GeneralName tsa)322 public void setTSA(GeneralName tsa) 323 { 324 this.tsa = tsa; 325 } 326 327 /** 328 * Generate a TimeStampToken for the passed in request and serialNumber marking it with the passed in genTime. 329 * 330 * @param request the originating request. 331 * @param serialNumber serial number for the TimeStampToken 332 * @param genTime token generation time. 333 * @return a TimeStampToken 334 * @throws TSPException 335 */ generate( TimeStampRequest request, BigInteger serialNumber, Date genTime)336 public TimeStampToken generate( 337 TimeStampRequest request, 338 BigInteger serialNumber, 339 Date genTime) 340 throws TSPException 341 { 342 return generate(request, serialNumber, genTime, null); 343 } 344 345 /** 346 * Generate a TimeStampToken for the passed in request and serialNumber marking it with the passed in genTime. 347 * 348 * @param request the originating request. 349 * @param serialNumber serial number for the TimeStampToken 350 * @param genTime token generation time. 351 * @param additionalExtensions extra extensions to be added to the response token. 352 * @return a TimeStampToken 353 * @throws TSPException 354 */ generate( TimeStampRequest request, BigInteger serialNumber, Date genTime, Extensions additionalExtensions)355 public TimeStampToken generate( 356 TimeStampRequest request, 357 BigInteger serialNumber, 358 Date genTime, 359 Extensions additionalExtensions) 360 throws TSPException 361 { 362 AlgorithmIdentifier algID = request.getMessageImprintAlgID(); 363 MessageImprint messageImprint = new MessageImprint(algID, request.getMessageImprintDigest()); 364 365 Accuracy accuracy = null; 366 if (accuracySeconds > 0 || accuracyMillis > 0 || accuracyMicros > 0) 367 { 368 ASN1Integer seconds = null; 369 if (accuracySeconds > 0) 370 { 371 seconds = new ASN1Integer(accuracySeconds); 372 } 373 374 ASN1Integer millis = null; 375 if (accuracyMillis > 0) 376 { 377 millis = new ASN1Integer(accuracyMillis); 378 } 379 380 ASN1Integer micros = null; 381 if (accuracyMicros > 0) 382 { 383 micros = new ASN1Integer(accuracyMicros); 384 } 385 386 accuracy = new Accuracy(seconds, millis, micros); 387 } 388 389 ASN1Boolean derOrdering = null; 390 if (ordering) 391 { 392 derOrdering = ASN1Boolean.getInstance(ordering); 393 } 394 395 ASN1Integer nonce = null; 396 if (request.getNonce() != null) 397 { 398 nonce = new ASN1Integer(request.getNonce()); 399 } 400 401 ASN1ObjectIdentifier tsaPolicy = tsaPolicyOID; 402 if (request.getReqPolicy() != null) 403 { 404 tsaPolicy = request.getReqPolicy(); 405 } 406 407 Extensions respExtensions = request.getExtensions(); 408 if (additionalExtensions != null) 409 { 410 ExtensionsGenerator extGen = new ExtensionsGenerator(); 411 412 if (respExtensions != null) 413 { 414 for (Enumeration en = respExtensions.oids(); en.hasMoreElements(); ) 415 { 416 extGen.addExtension(respExtensions.getExtension(ASN1ObjectIdentifier.getInstance(en.nextElement()))); 417 } 418 } 419 for (Enumeration en = additionalExtensions.oids(); en.hasMoreElements(); ) 420 { 421 extGen.addExtension(additionalExtensions.getExtension(ASN1ObjectIdentifier.getInstance(en.nextElement()))); 422 } 423 424 respExtensions = extGen.generate(); 425 } 426 427 ASN1GeneralizedTime timeStampTime; 428 if (resolution == R_SECONDS) 429 { 430 timeStampTime = (locale == null) ? new ASN1GeneralizedTime(genTime) : new ASN1GeneralizedTime(genTime, locale); 431 } 432 else 433 { 434 timeStampTime = createGeneralizedTime(genTime); 435 } 436 437 TSTInfo tstInfo = new TSTInfo(tsaPolicy, 438 messageImprint, new ASN1Integer(serialNumber), 439 timeStampTime, accuracy, derOrdering, 440 nonce, tsa, respExtensions); 441 442 try 443 { 444 CMSSignedDataGenerator signedDataGenerator = new CMSSignedDataGenerator(); 445 446 if (request.getCertReq()) 447 { 448 // TODO: do we need to check certs non-empty? 449 signedDataGenerator.addCertificates(new CollectionStore(certs)); 450 signedDataGenerator.addAttributeCertificates(new CollectionStore(attrCerts)); 451 } 452 453 signedDataGenerator.addCRLs(new CollectionStore(crls)); 454 455 if (!otherRevoc.isEmpty()) 456 { 457 for (Iterator it = otherRevoc.keySet().iterator(); it.hasNext();) 458 { 459 ASN1ObjectIdentifier format = (ASN1ObjectIdentifier)it.next(); 460 461 signedDataGenerator.addOtherRevocationInfo(format, new CollectionStore((Collection)otherRevoc.get(format))); 462 } 463 } 464 465 signedDataGenerator.addSignerInfoGenerator(signerInfoGen); 466 467 byte[] derEncodedTSTInfo = tstInfo.getEncoded(ASN1Encoding.DER); 468 469 CMSSignedData signedData = signedDataGenerator.generate(new CMSProcessableByteArray(PKCSObjectIdentifiers.id_ct_TSTInfo, derEncodedTSTInfo), true); 470 471 return new TimeStampToken(signedData); 472 } 473 catch (CMSException cmsEx) 474 { 475 throw new TSPException("Error generating time-stamp token", cmsEx); 476 } 477 catch (IOException e) 478 { 479 throw new TSPException("Exception encoding info", e); 480 } 481 } 482 483 // we need to produce a correct DER encoding GeneralizedTime here as the BC ASN.1 library doesn't handle this properly yet. createGeneralizedTime(Date time)484 private ASN1GeneralizedTime createGeneralizedTime(Date time) 485 throws TSPException 486 { 487 String format = "yyyyMMddHHmmss.SSS"; 488 SimpleDateFormat dateF = (locale == null) ? new SimpleDateFormat(format) : new SimpleDateFormat(format, locale); 489 dateF.setTimeZone(new SimpleTimeZone(0, "Z")); 490 StringBuffer sBuild = new StringBuffer(dateF.format(time)); 491 int dotIndex = 9; 492 while (dotIndex != sBuild.length() && sBuild.charAt(dotIndex) != '.') 493 { 494 dotIndex++; 495 } 496 497 if (dotIndex == sBuild.length()) 498 { 499 // came back in seconds only, just return 500 sBuild.append("Z"); 501 return new ASN1GeneralizedTime(sBuild.toString()); 502 } 503 504 // trim to resolution 505 switch (resolution) 506 { 507 case R_TENTHS_OF_SECONDS: 508 if (sBuild.length() > dotIndex + 2) 509 { 510 sBuild.delete(dotIndex + 2, sBuild.length()); 511 } 512 break; 513 case R_HUNDREDTHS_OF_SECONDS: 514 if (sBuild.length() > dotIndex + 3) 515 { 516 sBuild.delete(dotIndex + 3, sBuild.length()); 517 } 518 break; 519 case R_MILLISECONDS: 520 // do nothing 521 break; 522 default: 523 throw new TSPException("unknown time-stamp resolution: " + resolution); 524 } 525 526 // remove trailing zeros 527 while (sBuild.charAt(sBuild.length() - 1) == '0') 528 { 529 sBuild.deleteCharAt(sBuild.length() - 1); 530 } 531 532 if (sBuild.length() - 1 == dotIndex) 533 { 534 sBuild.deleteCharAt(sBuild.length() - 1); 535 } 536 537 sBuild.append("Z"); 538 539 return new ASN1GeneralizedTime(sBuild.toString()); 540 } 541 } 542