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 = new StringBuffer(sBuild.toString().substring(0, dotIndex + 2));
511             }
512             break;
513         case R_HUNDREDTHS_OF_SECONDS:
514             if (sBuild.length() > dotIndex + 3)
515             {
516                 sBuild = new StringBuffer(sBuild.toString().substring(0, dotIndex + 3));
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 = new StringBuffer(sBuild.toString().substring(0, sBuild.length() - 1));
530         }
531 
532         if (sBuild.length() - 1 == dotIndex)
533         {
534             sBuild = new StringBuffer(sBuild.toString().substring(0, sBuild.length() - 1));
535         }
536 
537         sBuild.append("Z");
538 
539         return new ASN1GeneralizedTime(sBuild.toString());
540     }
541 }
542