Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/quite/humla-spongycastle.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'pkix/src/main/java/org/spongycastle/tsp')
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/GenTimeAccuracy.java60
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/TSPAlgorithms.java35
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/TSPException.java28
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/TSPIOException.java30
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/TSPUtil.java209
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/TSPValidationException.java34
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/TimeStampRequest.java267
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/TimeStampRequestGenerator.java163
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/TimeStampResponse.java189
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/TimeStampResponseGenerator.java353
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/TimeStampToken.java393
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenGenerator.java380
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenInfo.java121
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedData.java204
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataGenerator.java70
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataParser.java207
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedGenerator.java88
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/cms/ImprintDigestInvalidException.java21
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/cms/MetaDataUtil.java76
-rw-r--r--pkix/src/main/java/org/spongycastle/tsp/cms/TimeStampDataUtil.java256
20 files changed, 3184 insertions, 0 deletions
diff --git a/pkix/src/main/java/org/spongycastle/tsp/GenTimeAccuracy.java b/pkix/src/main/java/org/spongycastle/tsp/GenTimeAccuracy.java
new file mode 100644
index 00000000..bb042e90
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/GenTimeAccuracy.java
@@ -0,0 +1,60 @@
+package org.spongycastle.tsp;
+
+import org.spongycastle.asn1.ASN1Integer;
+import org.spongycastle.asn1.tsp.Accuracy;
+
+public class GenTimeAccuracy
+{
+ private Accuracy accuracy;
+
+ public GenTimeAccuracy(Accuracy accuracy)
+ {
+ this.accuracy = accuracy;
+ }
+
+ public int getSeconds()
+ {
+ return getTimeComponent(accuracy.getSeconds());
+ }
+
+ public int getMillis()
+ {
+ return getTimeComponent(accuracy.getMillis());
+ }
+
+ public int getMicros()
+ {
+ return getTimeComponent(accuracy.getMicros());
+ }
+
+ private int getTimeComponent(
+ ASN1Integer time)
+ {
+ if (time != null)
+ {
+ return time.getValue().intValue();
+ }
+
+ return 0;
+ }
+
+ public String toString()
+ { // digits
+ return getSeconds() + "." + format(getMillis()) + format(getMicros());
+ }
+
+ private String format(int v)
+ {
+ if (v < 10)
+ {
+ return "00" + v;
+ }
+
+ if (v < 100)
+ {
+ return "0" + v;
+ }
+
+ return Integer.toString(v);
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/TSPAlgorithms.java b/pkix/src/main/java/org/spongycastle/tsp/TSPAlgorithms.java
new file mode 100644
index 00000000..fa5c6f06
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/TSPAlgorithms.java
@@ -0,0 +1,35 @@
+package org.spongycastle.tsp;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.spongycastle.asn1.ASN1ObjectIdentifier;
+import org.spongycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.spongycastle.asn1.nist.NISTObjectIdentifiers;
+import org.spongycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+
+/**
+ * Recognised hash algorithms for the time stamp protocol.
+ */
+public interface TSPAlgorithms
+{
+ public static final ASN1ObjectIdentifier MD5 = PKCSObjectIdentifiers.md5;
+
+ public static final ASN1ObjectIdentifier SHA1 = OIWObjectIdentifiers.idSHA1;
+
+ public static final ASN1ObjectIdentifier SHA224 = NISTObjectIdentifiers.id_sha224;
+ public static final ASN1ObjectIdentifier SHA256 = NISTObjectIdentifiers.id_sha256;
+ public static final ASN1ObjectIdentifier SHA384 = NISTObjectIdentifiers.id_sha384;
+ public static final ASN1ObjectIdentifier SHA512 = NISTObjectIdentifiers.id_sha512;
+
+ public static final ASN1ObjectIdentifier RIPEMD128 = TeleTrusTObjectIdentifiers.ripemd128;
+ public static final ASN1ObjectIdentifier RIPEMD160 = TeleTrusTObjectIdentifiers.ripemd160;
+ public static final ASN1ObjectIdentifier RIPEMD256 = TeleTrusTObjectIdentifiers.ripemd256;
+
+ public static final ASN1ObjectIdentifier GOST3411 = CryptoProObjectIdentifiers.gostR3411;
+
+ public static final Set ALLOWED = new HashSet(Arrays.asList(new ASN1ObjectIdentifier[] { GOST3411, MD5, SHA1, SHA224, SHA256, SHA384, SHA512, RIPEMD128, RIPEMD160, RIPEMD256 }));
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/TSPException.java b/pkix/src/main/java/org/spongycastle/tsp/TSPException.java
new file mode 100644
index 00000000..6125ceff
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/TSPException.java
@@ -0,0 +1,28 @@
+package org.spongycastle.tsp;
+
+public class TSPException
+ extends Exception
+{
+ Throwable underlyingException;
+
+ public TSPException(String message)
+ {
+ super(message);
+ }
+
+ public TSPException(String message, Throwable e)
+ {
+ super(message);
+ underlyingException = e;
+ }
+
+ public Exception getUnderlyingException()
+ {
+ return (Exception)underlyingException;
+ }
+
+ public Throwable getCause()
+ {
+ return underlyingException;
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/TSPIOException.java b/pkix/src/main/java/org/spongycastle/tsp/TSPIOException.java
new file mode 100644
index 00000000..418ff1b2
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/TSPIOException.java
@@ -0,0 +1,30 @@
+package org.spongycastle.tsp;
+
+import java.io.IOException;
+
+public class TSPIOException
+ extends IOException
+{
+ Throwable underlyingException;
+
+ public TSPIOException(String message)
+ {
+ super(message);
+ }
+
+ public TSPIOException(String message, Throwable e)
+ {
+ super(message);
+ underlyingException = e;
+ }
+
+ public Exception getUnderlyingException()
+ {
+ return (Exception)underlyingException;
+ }
+
+ public Throwable getCause()
+ {
+ return underlyingException;
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/TSPUtil.java b/pkix/src/main/java/org/spongycastle/tsp/TSPUtil.java
new file mode 100644
index 00000000..23a0454c
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/TSPUtil.java
@@ -0,0 +1,209 @@
+package org.spongycastle.tsp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.spongycastle.asn1.ASN1Encodable;
+import org.spongycastle.asn1.ASN1EncodableVector;
+import org.spongycastle.asn1.ASN1ObjectIdentifier;
+import org.spongycastle.asn1.ASN1Set;
+import org.spongycastle.asn1.cms.Attribute;
+import org.spongycastle.asn1.cms.AttributeTable;
+import org.spongycastle.asn1.cms.ContentInfo;
+import org.spongycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.spongycastle.asn1.nist.NISTObjectIdentifiers;
+import org.spongycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.spongycastle.asn1.x509.ExtendedKeyUsage;
+import org.spongycastle.asn1.x509.Extension;
+import org.spongycastle.asn1.x509.Extensions;
+import org.spongycastle.asn1.x509.ExtensionsGenerator;
+import org.spongycastle.asn1.x509.KeyPurposeId;
+import org.spongycastle.cert.X509CertificateHolder;
+import org.spongycastle.cms.SignerInformation;
+import org.spongycastle.operator.DigestCalculator;
+import org.spongycastle.operator.DigestCalculatorProvider;
+import org.spongycastle.operator.OperatorCreationException;
+import org.spongycastle.util.Arrays;
+import org.spongycastle.util.Integers;
+
+public class TSPUtil
+{
+ private static List EMPTY_LIST = Collections.unmodifiableList(new ArrayList());
+
+ private static final Map digestLengths = new HashMap();
+ private static final Map digestNames = new HashMap();
+
+ static
+ {
+ digestLengths.put(PKCSObjectIdentifiers.md5.getId(), Integers.valueOf(16));
+ digestLengths.put(OIWObjectIdentifiers.idSHA1.getId(), Integers.valueOf(20));
+ digestLengths.put(NISTObjectIdentifiers.id_sha224.getId(), Integers.valueOf(28));
+ digestLengths.put(NISTObjectIdentifiers.id_sha256.getId(), Integers.valueOf(32));
+ digestLengths.put(NISTObjectIdentifiers.id_sha384.getId(), Integers.valueOf(48));
+ digestLengths.put(NISTObjectIdentifiers.id_sha512.getId(), Integers.valueOf(64));
+ digestLengths.put(TeleTrusTObjectIdentifiers.ripemd128.getId(), Integers.valueOf(16));
+ digestLengths.put(TeleTrusTObjectIdentifiers.ripemd160.getId(), Integers.valueOf(20));
+ digestLengths.put(TeleTrusTObjectIdentifiers.ripemd256.getId(), Integers.valueOf(32));
+ digestLengths.put(CryptoProObjectIdentifiers.gostR3411.getId(), Integers.valueOf(32));
+
+ digestNames.put(PKCSObjectIdentifiers.md5.getId(), "MD5");
+ digestNames.put(OIWObjectIdentifiers.idSHA1.getId(), "SHA1");
+ digestNames.put(NISTObjectIdentifiers.id_sha224.getId(), "SHA224");
+ digestNames.put(NISTObjectIdentifiers.id_sha256.getId(), "SHA256");
+ digestNames.put(NISTObjectIdentifiers.id_sha384.getId(), "SHA384");
+ digestNames.put(NISTObjectIdentifiers.id_sha512.getId(), "SHA512");
+ digestNames.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1");
+ digestNames.put(PKCSObjectIdentifiers.sha224WithRSAEncryption.getId(), "SHA224");
+ digestNames.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256");
+ digestNames.put(PKCSObjectIdentifiers.sha384WithRSAEncryption.getId(), "SHA384");
+ digestNames.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512");
+ digestNames.put(TeleTrusTObjectIdentifiers.ripemd128.getId(), "RIPEMD128");
+ digestNames.put(TeleTrusTObjectIdentifiers.ripemd160.getId(), "RIPEMD160");
+ digestNames.put(TeleTrusTObjectIdentifiers.ripemd256.getId(), "RIPEMD256");
+ digestNames.put(CryptoProObjectIdentifiers.gostR3411.getId(), "GOST3411");
+ }
+
+ /**
+ * Fetches the signature time-stamp attributes from a SignerInformation object.
+ * Checks that the MessageImprint for each time-stamp matches the signature field.
+ * (see RFC 3161 Appendix A).
+ *
+ * @param signerInfo a SignerInformation to search for time-stamps
+ * @param digCalcProvider provider for digest calculators
+ * @return a collection of TimeStampToken objects
+ * @throws TSPValidationException
+ */
+ public static Collection getSignatureTimestamps(SignerInformation signerInfo, DigestCalculatorProvider digCalcProvider)
+ throws TSPValidationException
+ {
+ List timestamps = new ArrayList();
+
+ AttributeTable unsignedAttrs = signerInfo.getUnsignedAttributes();
+ if (unsignedAttrs != null)
+ {
+ ASN1EncodableVector allTSAttrs = unsignedAttrs.getAll(
+ PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
+ for (int i = 0; i < allTSAttrs.size(); ++i)
+ {
+ Attribute tsAttr = (Attribute)allTSAttrs.get(i);
+ ASN1Set tsAttrValues = tsAttr.getAttrValues();
+ for (int j = 0; j < tsAttrValues.size(); ++j)
+ {
+ try
+ {
+ ContentInfo contentInfo = ContentInfo.getInstance(tsAttrValues.getObjectAt(j));
+ TimeStampToken timeStampToken = new TimeStampToken(contentInfo);
+ TimeStampTokenInfo tstInfo = timeStampToken.getTimeStampInfo();
+
+ DigestCalculator digCalc = digCalcProvider.get(tstInfo.getHashAlgorithm());
+
+ OutputStream dOut = digCalc.getOutputStream();
+
+ dOut.write(signerInfo.getSignature());
+ dOut.close();
+
+ byte[] expectedDigest = digCalc.getDigest();
+
+ if (!Arrays.constantTimeAreEqual(expectedDigest, tstInfo.getMessageImprintDigest()))
+ {
+ throw new TSPValidationException("Incorrect digest in message imprint");
+ }
+
+ timestamps.add(timeStampToken);
+ }
+ catch (OperatorCreationException e)
+ {
+ throw new TSPValidationException("Unknown hash algorithm specified in timestamp");
+ }
+ catch (Exception e)
+ {
+ throw new TSPValidationException("Timestamp could not be parsed");
+ }
+ }
+ }
+ }
+
+ return timestamps;
+ }
+
+ /**
+ * Validate the passed in certificate as being of the correct type to be used
+ * for time stamping. To be valid it must have an ExtendedKeyUsage extension
+ * which has a key purpose identifier of id-kp-timeStamping.
+ *
+ * @param cert the certificate of interest.
+ * @throws TSPValidationException if the certificate fails on one of the check points.
+ */
+ public static void validateCertificate(
+ X509CertificateHolder cert)
+ throws TSPValidationException
+ {
+ if (cert.toASN1Structure().getVersionNumber() != 3)
+ {
+ throw new IllegalArgumentException("Certificate must have an ExtendedKeyUsage extension.");
+ }
+
+ Extension ext = cert.getExtension(Extension.extendedKeyUsage);
+ if (ext == null)
+ {
+ throw new TSPValidationException("Certificate must have an ExtendedKeyUsage extension.");
+ }
+
+ if (!ext.isCritical())
+ {
+ throw new TSPValidationException("Certificate must have an ExtendedKeyUsage extension marked as critical.");
+ }
+
+ ExtendedKeyUsage extKey = ExtendedKeyUsage.getInstance(ext.getParsedValue());
+
+ if (!extKey.hasKeyPurposeId(KeyPurposeId.id_kp_timeStamping) || extKey.size() != 1)
+ {
+ throw new TSPValidationException("ExtendedKeyUsage not solely time stamping.");
+ }
+ }
+
+ static int getDigestLength(
+ String digestAlgOID)
+ throws TSPException
+ {
+ Integer length = (Integer)digestLengths.get(digestAlgOID);
+
+ if (length != null)
+ {
+ return length.intValue();
+ }
+
+ throw new TSPException("digest algorithm cannot be found.");
+ }
+
+ static List getExtensionOIDs(Extensions extensions)
+ {
+ if (extensions == null)
+ {
+ return EMPTY_LIST;
+ }
+
+ return Collections.unmodifiableList(java.util.Arrays.asList(extensions.getExtensionOIDs()));
+ }
+
+ static void addExtension(ExtensionsGenerator extGenerator, ASN1ObjectIdentifier oid, boolean isCritical, ASN1Encodable value)
+ throws TSPIOException
+ {
+ try
+ {
+ extGenerator.addExtension(oid, isCritical, value);
+ }
+ catch (IOException e)
+ {
+ throw new TSPIOException("cannot encode extension: " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/TSPValidationException.java b/pkix/src/main/java/org/spongycastle/tsp/TSPValidationException.java
new file mode 100644
index 00000000..f89ac6c1
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/TSPValidationException.java
@@ -0,0 +1,34 @@
+package org.spongycastle.tsp;
+
+/**
+ * Exception thrown if a TSP request or response fails to validate.
+ * <p>
+ * If a failure code is associated with the exception it can be retrieved using
+ * the getFailureCode() method.
+ */
+public class TSPValidationException
+ extends TSPException
+{
+ private int failureCode = -1;
+
+ public TSPValidationException(String message)
+ {
+ super(message);
+ }
+
+ public TSPValidationException(String message, int failureCode)
+ {
+ super(message);
+ this.failureCode = failureCode;
+ }
+
+ /**
+ * Return the failure code associated with this exception - if one is set.
+ *
+ * @return the failure code if set, -1 otherwise.
+ */
+ public int getFailureCode()
+ {
+ return failureCode;
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/TimeStampRequest.java b/pkix/src/main/java/org/spongycastle/tsp/TimeStampRequest.java
new file mode 100644
index 00000000..54ed40ec
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/TimeStampRequest.java
@@ -0,0 +1,267 @@
+package org.spongycastle.tsp;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.spongycastle.asn1.ASN1InputStream;
+import org.spongycastle.asn1.ASN1ObjectIdentifier;
+import org.spongycastle.asn1.cmp.PKIFailureInfo;
+import org.spongycastle.asn1.tsp.TimeStampReq;
+import org.spongycastle.asn1.x509.Extension;
+import org.spongycastle.asn1.x509.Extensions;
+
+/**
+ * Base class for an RFC 3161 Time Stamp Request.
+ */
+public class TimeStampRequest
+{
+ private static Set EMPTY_SET = Collections.unmodifiableSet(new HashSet());
+
+ private TimeStampReq req;
+ private Extensions extensions;
+
+ public TimeStampRequest(TimeStampReq req)
+ {
+ this.req = req;
+ this.extensions = req.getExtensions();
+ }
+
+ /**
+ * Create a TimeStampRequest from the past in byte array.
+ *
+ * @param req byte array containing the request.
+ * @throws IOException if the request is malformed.
+ */
+ public TimeStampRequest(byte[] req)
+ throws IOException
+ {
+ this(new ByteArrayInputStream(req));
+ }
+
+ /**
+ * Create a TimeStampRequest from the past in input stream.
+ *
+ * @param in input stream containing the request.
+ * @throws IOException if the request is malformed.
+ */
+ public TimeStampRequest(InputStream in)
+ throws IOException
+ {
+ this(loadRequest(in));
+ }
+
+ private static TimeStampReq loadRequest(InputStream in)
+ throws IOException
+ {
+ try
+ {
+ return TimeStampReq.getInstance(new ASN1InputStream(in).readObject());
+ }
+ catch (ClassCastException e)
+ {
+ throw new IOException("malformed request: " + e);
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw new IOException("malformed request: " + e);
+ }
+ }
+
+ public int getVersion()
+ {
+ return req.getVersion().getValue().intValue();
+ }
+
+ public ASN1ObjectIdentifier getMessageImprintAlgOID()
+ {
+ return req.getMessageImprint().getHashAlgorithm().getAlgorithm();
+ }
+
+ public byte[] getMessageImprintDigest()
+ {
+ return req.getMessageImprint().getHashedMessage();
+ }
+
+ public ASN1ObjectIdentifier getReqPolicy()
+ {
+ if (req.getReqPolicy() != null)
+ {
+ return req.getReqPolicy();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public BigInteger getNonce()
+ {
+ if (req.getNonce() != null)
+ {
+ return req.getNonce().getValue();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public boolean getCertReq()
+ {
+ if (req.getCertReq() != null)
+ {
+ return req.getCertReq().isTrue();
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Validate the timestamp request, checking the digest to see if it is of an
+ * accepted type and whether it is of the correct length for the algorithm specified.
+ *
+ * @param algorithms a set of OIDs giving accepted algorithms.
+ * @param policies if non-null a set of policies OIDs we are willing to sign under.
+ * @param extensions if non-null a set of extensions OIDs we are willing to accept.
+ * @throws TSPException if the request is invalid, or processing fails.
+ */
+ public void validate(
+ Set algorithms,
+ Set policies,
+ Set extensions)
+ throws TSPException
+ {
+ algorithms = convert(algorithms);
+ policies = convert(policies);
+ extensions = convert(extensions);
+
+ if (!algorithms.contains(this.getMessageImprintAlgOID()))
+ {
+ throw new TSPValidationException("request contains unknown algorithm.", PKIFailureInfo.badAlg);
+ }
+
+ if (policies != null && this.getReqPolicy() != null && !policies.contains(this.getReqPolicy()))
+ {
+ throw new TSPValidationException("request contains unknown policy.", PKIFailureInfo.unacceptedPolicy);
+ }
+
+ if (this.getExtensions() != null && extensions != null)
+ {
+ Enumeration en = this.getExtensions().oids();
+ while(en.hasMoreElements())
+ {
+ String oid = ((ASN1ObjectIdentifier)en.nextElement()).getId();
+ if (!extensions.contains(oid))
+ {
+ throw new TSPValidationException("request contains unknown extension.", PKIFailureInfo.unacceptedExtension);
+ }
+ }
+ }
+
+ int digestLength = TSPUtil.getDigestLength(this.getMessageImprintAlgOID().getId());
+
+ if (digestLength != this.getMessageImprintDigest().length)
+ {
+ throw new TSPValidationException("imprint digest the wrong length.", PKIFailureInfo.badDataFormat);
+ }
+ }
+
+ /**
+ * return the ASN.1 encoded representation of this object.
+ * @return the default ASN,1 byte encoding for the object.
+ */
+ public byte[] getEncoded() throws IOException
+ {
+ return req.getEncoded();
+ }
+
+ Extensions getExtensions()
+ {
+ return extensions;
+ }
+
+ public boolean hasExtensions()
+ {
+ return extensions != null;
+ }
+
+ public Extension getExtension(ASN1ObjectIdentifier oid)
+ {
+ if (extensions != null)
+ {
+ return extensions.getExtension(oid);
+ }
+
+ return null;
+ }
+
+ public List getExtensionOIDs()
+ {
+ return TSPUtil.getExtensionOIDs(extensions);
+ }
+
+ /**
+ * Returns a set of ASN1ObjectIdentifiers giving the non-critical extensions.
+ * @return a set of ASN1ObjectIdentifiers.
+ */
+ public Set getNonCriticalExtensionOIDs()
+ {
+ if (extensions == null)
+ {
+ return EMPTY_SET;
+ }
+
+ return Collections.unmodifiableSet(new HashSet(Arrays.asList(extensions.getNonCriticalExtensionOIDs())));
+ }
+
+ /**
+ * Returns a set of ASN1ObjectIdentifiers giving the critical extensions.
+ * @return a set of ASN1ObjectIdentifiers.
+ */
+ public Set getCriticalExtensionOIDs()
+ {
+ if (extensions == null)
+ {
+ return EMPTY_SET;
+ }
+
+ return Collections.unmodifiableSet(new HashSet(Arrays.asList(extensions.getCriticalExtensionOIDs())));
+ }
+
+ private Set convert(Set orig)
+ {
+ if (orig == null)
+ {
+ return orig;
+ }
+
+ Set con = new HashSet(orig.size());
+
+ for (Iterator it = orig.iterator(); it.hasNext();)
+ {
+ Object o = it.next();
+
+ if (o instanceof String)
+ {
+ con.add(new ASN1ObjectIdentifier((String)o));
+ }
+ else
+ {
+ con.add(o);
+ }
+ }
+
+ return con;
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/TimeStampRequestGenerator.java b/pkix/src/main/java/org/spongycastle/tsp/TimeStampRequestGenerator.java
new file mode 100644
index 00000000..382b366a
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/TimeStampRequestGenerator.java
@@ -0,0 +1,163 @@
+package org.spongycastle.tsp;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+import org.spongycastle.asn1.ASN1Boolean;
+import org.spongycastle.asn1.ASN1Encodable;
+import org.spongycastle.asn1.ASN1Integer;
+import org.spongycastle.asn1.ASN1ObjectIdentifier;
+import org.spongycastle.asn1.DERNull;
+import org.spongycastle.asn1.tsp.MessageImprint;
+import org.spongycastle.asn1.tsp.TimeStampReq;
+import org.spongycastle.asn1.x509.AlgorithmIdentifier;
+import org.spongycastle.asn1.x509.Extensions;
+import org.spongycastle.asn1.x509.ExtensionsGenerator;
+
+/**
+ * Generator for RFC 3161 Time Stamp Request objects.
+ */
+public class TimeStampRequestGenerator
+{
+ private ASN1ObjectIdentifier reqPolicy;
+
+ private ASN1Boolean certReq;
+ private ExtensionsGenerator extGenerator = new ExtensionsGenerator();
+
+ public TimeStampRequestGenerator()
+ {
+ }
+
+ /**
+ * @deprecated use method taking ASN1ObjectIdentifier
+ * @param reqPolicy
+ */
+ public void setReqPolicy(
+ String reqPolicy)
+ {
+ this.reqPolicy= new ASN1ObjectIdentifier(reqPolicy);
+ }
+
+ public void setReqPolicy(
+ ASN1ObjectIdentifier reqPolicy)
+ {
+ this.reqPolicy= reqPolicy;
+ }
+
+ public void setCertReq(
+ boolean certReq)
+ {
+ this.certReq = ASN1Boolean.getInstance(certReq);
+ }
+
+ /**
+ * add a given extension field for the standard extensions tag (tag 3)
+ * @throws IOException
+ * @deprecated use method taking ASN1ObjectIdentifier
+ */
+ public void addExtension(
+ String OID,
+ boolean critical,
+ ASN1Encodable value)
+ throws IOException
+ {
+ this.addExtension(OID, critical, value.toASN1Primitive().getEncoded());
+ }
+
+ /**
+ * add a given extension field for the standard extensions tag
+ * The value parameter becomes the contents of the octet string associated
+ * with the extension.
+ * @deprecated use method taking ASN1ObjectIdentifier
+ */
+ public void addExtension(
+ String OID,
+ boolean critical,
+ byte[] value)
+ {
+ extGenerator.addExtension(new ASN1ObjectIdentifier(OID), critical, value);
+ }
+
+ /**
+ * add a given extension field for the standard extensions tag (tag 3)
+ * @throws TSPIOException
+ */
+ public void addExtension(
+ ASN1ObjectIdentifier oid,
+ boolean isCritical,
+ ASN1Encodable value)
+ throws TSPIOException
+ {
+ TSPUtil.addExtension(extGenerator, oid, isCritical, value);
+ }
+
+ /**
+ * add a given extension field for the standard extensions tag
+ * The value parameter becomes the contents of the octet string associated
+ * with the extension.
+ */
+ public void addExtension(
+ ASN1ObjectIdentifier oid,
+ boolean isCritical,
+ byte[] value)
+ {
+ extGenerator.addExtension(oid, isCritical, value);
+ }
+
+ /**
+ * @deprecated use method taking ANS1ObjectIdentifier
+ */
+ public TimeStampRequest generate(
+ String digestAlgorithm,
+ byte[] digest)
+ {
+ return this.generate(digestAlgorithm, digest, null);
+ }
+
+ /**
+ * @deprecated use method taking ANS1ObjectIdentifier
+ */
+ public TimeStampRequest generate(
+ String digestAlgorithmOID,
+ byte[] digest,
+ BigInteger nonce)
+ {
+ if (digestAlgorithmOID == null)
+ {
+ throw new IllegalArgumentException("No digest algorithm specified");
+ }
+
+ ASN1ObjectIdentifier digestAlgOID = new ASN1ObjectIdentifier(digestAlgorithmOID);
+
+ AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, DERNull.INSTANCE);
+ MessageImprint messageImprint = new MessageImprint(algID, digest);
+
+ Extensions ext = null;
+
+ if (!extGenerator.isEmpty())
+ {
+ ext = extGenerator.generate();
+ }
+
+ if (nonce != null)
+ {
+ return new TimeStampRequest(new TimeStampReq(messageImprint,
+ reqPolicy, new ASN1Integer(nonce), certReq, ext));
+ }
+ else
+ {
+ return new TimeStampRequest(new TimeStampReq(messageImprint,
+ reqPolicy, null, certReq, ext));
+ }
+ }
+
+ public TimeStampRequest generate(ASN1ObjectIdentifier digestAlgorithm, byte[] digest)
+ {
+ return generate(digestAlgorithm.getId(), digest);
+ }
+
+ public TimeStampRequest generate(ASN1ObjectIdentifier digestAlgorithm, byte[] digest, BigInteger nonce)
+ {
+ return generate(digestAlgorithm.getId(), digest, nonce);
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/TimeStampResponse.java b/pkix/src/main/java/org/spongycastle/tsp/TimeStampResponse.java
new file mode 100644
index 00000000..cc327f45
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/TimeStampResponse.java
@@ -0,0 +1,189 @@
+package org.spongycastle.tsp;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.spongycastle.asn1.ASN1InputStream;
+import org.spongycastle.asn1.cmp.PKIFailureInfo;
+import org.spongycastle.asn1.cmp.PKIFreeText;
+import org.spongycastle.asn1.cmp.PKIStatus;
+import org.spongycastle.asn1.cms.Attribute;
+import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.spongycastle.asn1.tsp.TimeStampResp;
+import org.spongycastle.util.Arrays;
+
+/**
+ * Base class for an RFC 3161 Time Stamp Response object.
+ */
+public class TimeStampResponse
+{
+ TimeStampResp resp;
+ TimeStampToken timeStampToken;
+
+ public TimeStampResponse(TimeStampResp resp)
+ throws TSPException, IOException
+ {
+ this.resp = resp;
+
+ if (resp.getTimeStampToken() != null)
+ {
+ timeStampToken = new TimeStampToken(resp.getTimeStampToken());
+ }
+ }
+
+ /**
+ * Create a TimeStampResponse from a byte array containing an ASN.1 encoding.
+ *
+ * @param resp the byte array containing the encoded response.
+ * @throws TSPException if the response is malformed.
+ * @throws IOException if the byte array doesn't represent an ASN.1 encoding.
+ */
+ public TimeStampResponse(byte[] resp)
+ throws TSPException, IOException
+ {
+ this(new ByteArrayInputStream(resp));
+ }
+
+ /**
+ * Create a TimeStampResponse from an input stream containing an ASN.1 encoding.
+ *
+ * @param in the input stream containing the encoded response.
+ * @throws TSPException if the response is malformed.
+ * @throws IOException if the stream doesn't represent an ASN.1 encoding.
+ */
+ public TimeStampResponse(InputStream in)
+ throws TSPException, IOException
+ {
+ this(readTimeStampResp(in));
+ }
+
+ private static TimeStampResp readTimeStampResp(
+ InputStream in)
+ throws IOException, TSPException
+ {
+ try
+ {
+ return TimeStampResp.getInstance(new ASN1InputStream(in).readObject());
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw new TSPException("malformed timestamp response: " + e, e);
+ }
+ catch (ClassCastException e)
+ {
+ throw new TSPException("malformed timestamp response: " + e, e);
+ }
+ }
+
+ public int getStatus()
+ {
+ return resp.getStatus().getStatus().intValue();
+ }
+
+ public String getStatusString()
+ {
+ if (resp.getStatus().getStatusString() != null)
+ {
+ StringBuffer statusStringBuf = new StringBuffer();
+ PKIFreeText text = resp.getStatus().getStatusString();
+ for (int i = 0; i != text.size(); i++)
+ {
+ statusStringBuf.append(text.getStringAt(i).getString());
+ }
+ return statusStringBuf.toString();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public PKIFailureInfo getFailInfo()
+ {
+ if (resp.getStatus().getFailInfo() != null)
+ {
+ return new PKIFailureInfo(resp.getStatus().getFailInfo());
+ }
+
+ return null;
+ }
+
+ public TimeStampToken getTimeStampToken()
+ {
+ return timeStampToken;
+ }
+
+ /**
+ * Check this response against to see if it a well formed response for
+ * the passed in request. Validation will include checking the time stamp
+ * token if the response status is GRANTED or GRANTED_WITH_MODS.
+ *
+ * @param request the request to be checked against
+ * @throws TSPException if the request can not match this response.
+ */
+ public void validate(
+ TimeStampRequest request)
+ throws TSPException
+ {
+ TimeStampToken tok = this.getTimeStampToken();
+
+ if (tok != null)
+ {
+ TimeStampTokenInfo tstInfo = tok.getTimeStampInfo();
+
+ if (request.getNonce() != null && !request.getNonce().equals(tstInfo.getNonce()))
+ {
+ throw new TSPValidationException("response contains wrong nonce value.");
+ }
+
+ if (this.getStatus() != PKIStatus.GRANTED && this.getStatus() != PKIStatus.GRANTED_WITH_MODS)
+ {
+ throw new TSPValidationException("time stamp token found in failed request.");
+ }
+
+ if (!Arrays.constantTimeAreEqual(request.getMessageImprintDigest(), tstInfo.getMessageImprintDigest()))
+ {
+ throw new TSPValidationException("response for different message imprint digest.");
+ }
+
+ if (!tstInfo.getMessageImprintAlgOID().equals(request.getMessageImprintAlgOID()))
+ {
+ throw new TSPValidationException("response for different message imprint algorithm.");
+ }
+
+ Attribute scV1 = tok.getSignedAttributes().get(PKCSObjectIdentifiers.id_aa_signingCertificate);
+ Attribute scV2 = tok.getSignedAttributes().get(PKCSObjectIdentifiers.id_aa_signingCertificateV2);
+
+ if (scV1 == null && scV2 == null)
+ {
+ throw new TSPValidationException("no signing certificate attribute present.");
+ }
+
+ if (scV1 != null && scV2 != null)
+ {
+ /*
+ * RFC 5035 5.4. If both attributes exist in a single message,
+ * they are independently evaluated.
+ */
+ }
+
+ if (request.getReqPolicy() != null && !request.getReqPolicy().equals(tstInfo.getPolicy()))
+ {
+ throw new TSPValidationException("TSA policy wrong for request.");
+ }
+ }
+ else if (this.getStatus() == PKIStatus.GRANTED || this.getStatus() == PKIStatus.GRANTED_WITH_MODS)
+ {
+ throw new TSPValidationException("no time stamp token found and one expected.");
+ }
+ }
+
+ /**
+ * return the ASN.1 encoded representation of this object.
+ */
+ public byte[] getEncoded() throws IOException
+ {
+ return resp.getEncoded();
+ }
+} \ No newline at end of file
diff --git a/pkix/src/main/java/org/spongycastle/tsp/TimeStampResponseGenerator.java b/pkix/src/main/java/org/spongycastle/tsp/TimeStampResponseGenerator.java
new file mode 100644
index 00000000..57e83aa2
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/TimeStampResponseGenerator.java
@@ -0,0 +1,353 @@
+package org.spongycastle.tsp;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.spongycastle.asn1.ASN1EncodableVector;
+import org.spongycastle.asn1.ASN1Integer;
+import org.spongycastle.asn1.ASN1ObjectIdentifier;
+import org.spongycastle.asn1.DERBitString;
+import org.spongycastle.asn1.DERSequence;
+import org.spongycastle.asn1.DERUTF8String;
+import org.spongycastle.asn1.cmp.PKIFailureInfo;
+import org.spongycastle.asn1.cmp.PKIFreeText;
+import org.spongycastle.asn1.cmp.PKIStatus;
+import org.spongycastle.asn1.cmp.PKIStatusInfo;
+import org.spongycastle.asn1.cms.ContentInfo;
+import org.spongycastle.asn1.tsp.TimeStampResp;
+
+/**
+ * Generator for RFC 3161 Time Stamp Responses.
+ * <p>
+ * New generate methods have been introduced to give people more control over what ends up in the message.
+ * Unfortunately it turns out that in some cases fields like statusString must be left out otherwise a an
+ * otherwise valid timestamp will be rejected.
+ * </p>
+ * If you're after the most control with generating a response use:
+ * <pre>
+ * TimeStampResponse tsResp;
+ *
+ * try
+ * {
+ * tsResp = tsRespGen.generateGrantedResponse(request, new BigInteger("23"), new Date());
+ * }
+ * catch (Exception e)
+ * {
+ * tsResp = tsRespGen.generateRejectedResponse(e);
+ * }
+ * </pre>
+ * The generate method does this, but provides a status string of "Operation Okay".
+ * <p>
+ * It should be pointed out that generateRejectedResponse() may also, on very rare occasions throw a TSPException.
+ * In the event that happens, there's a serious internal problem with your responder.
+ * </p>
+ */
+public class TimeStampResponseGenerator
+{
+ int status;
+
+ ASN1EncodableVector statusStrings;
+
+ int failInfo;
+ private TimeStampTokenGenerator tokenGenerator;
+ private Set acceptedAlgorithms;
+ private Set acceptedPolicies;
+ private Set acceptedExtensions;
+
+ /**
+ *
+ * @param tokenGenerator
+ * @param acceptedAlgorithms a set of OIDs giving accepted algorithms.
+ */
+ public TimeStampResponseGenerator(
+ TimeStampTokenGenerator tokenGenerator,
+ Set acceptedAlgorithms)
+ {
+ this(tokenGenerator, acceptedAlgorithms, null, null);
+ }
+
+ /**
+ *
+ * @param tokenGenerator
+ * @param acceptedAlgorithms a set of OIDs giving accepted algorithms.
+ * @param acceptedPolicies if non-null a set of policies OIDs we are willing to sign under.
+ */
+ public TimeStampResponseGenerator(
+ TimeStampTokenGenerator tokenGenerator,
+ Set acceptedAlgorithms,
+ Set acceptedPolicies)
+ {
+ this(tokenGenerator, acceptedAlgorithms, acceptedPolicies, null);
+ }
+
+ /**
+ *
+ * @param tokenGenerator
+ * @param acceptedAlgorithms a set of OIDs giving accepted algorithms.
+ * @param acceptedPolicies if non-null a set of policies OIDs we are willing to sign under.
+ * @param acceptedExtensions if non-null a set of extensions OIDs we are willing to accept.
+ */
+ public TimeStampResponseGenerator(
+ TimeStampTokenGenerator tokenGenerator,
+ Set acceptedAlgorithms,
+ Set acceptedPolicies,
+ Set acceptedExtensions)
+ {
+ this.tokenGenerator = tokenGenerator;
+ this.acceptedAlgorithms = convert(acceptedAlgorithms);
+ this.acceptedPolicies = convert(acceptedPolicies);
+ this.acceptedExtensions = convert(acceptedExtensions);
+
+ statusStrings = new ASN1EncodableVector();
+ }
+
+ private void addStatusString(String statusString)
+ {
+ statusStrings.add(new DERUTF8String(statusString));
+ }
+
+ private void setFailInfoField(int field)
+ {
+ failInfo = failInfo | field;
+ }
+
+ private PKIStatusInfo getPKIStatusInfo()
+ {
+ ASN1EncodableVector v = new ASN1EncodableVector();
+
+ v.add(new ASN1Integer(status));
+
+ if (statusStrings.size() > 0)
+ {
+ v.add(PKIFreeText.getInstance(new DERSequence(statusStrings)));
+ }
+
+ if (failInfo != 0)
+ {
+ DERBitString failInfoBitString = new FailInfo(failInfo);
+ v.add(failInfoBitString);
+ }
+
+ return PKIStatusInfo.getInstance(new DERSequence(v));
+ }
+
+ /**
+ * Return an appropriate TimeStampResponse.
+ * <p>
+ * If genTime is null a timeNotAvailable error response will be returned. Calling generate() is the
+ * equivalent of:
+ * <pre>
+ * TimeStampResponse tsResp;
+ *
+ * try
+ * {
+ * tsResp = tsRespGen.generateGrantedResponse(request, serialNumber, genTime, "Operation Okay");
+ * }
+ * catch (Exception e)
+ * {
+ * tsResp = tsRespGen.generateRejectedResponse(e);
+ * }
+ * </pre>
+ * @param request the request this response is for.
+ * @param serialNumber serial number for the response token.
+ * @param genTime generation time for the response token.
+ * @return a TimeStampResponse.
+ * @throws TSPException
+ */
+ public TimeStampResponse generate(
+ TimeStampRequest request,
+ BigInteger serialNumber,
+ Date genTime)
+ throws TSPException
+ {
+ try
+ {
+ return this.generateGrantedResponse(request, serialNumber, genTime, "Operation Okay");
+ }
+ catch (Exception e)
+ {
+ return this.generateRejectedResponse(e);
+ }
+ }
+
+ /**
+ * Return a granted response, if the passed in request passes validation.
+ * <p>
+ * If genTime is null a timeNotAvailable or a validation exception occurs a TSPValidationException will
+ * be thrown. The parent TSPException will only occur on some sort of system failure.
+ * </p>
+ * @param request the request this response is for.
+ * @param serialNumber serial number for the response token.
+ * @param genTime generation time for the response token.
+ * @return the TimeStampResponse with a status of PKIStatus.GRANTED
+ * @throws TSPException on validation exception or internal error.
+ */
+ public TimeStampResponse generateGrantedResponse(
+ TimeStampRequest request,
+ BigInteger serialNumber,
+ Date genTime)
+ throws TSPException
+ {
+ return generateGrantedResponse(request, serialNumber, genTime, null);
+ }
+
+ /**
+ * Return a granted response, if the passed in request passes validation with the passed in status string.
+ * <p>
+ * If genTime is null a timeNotAvailable or a validation exception occurs a TSPValidationException will
+ * be thrown. The parent TSPException will only occur on some sort of system failure.
+ * </p>
+ * @param request the request this response is for.
+ * @param serialNumber serial number for the response token.
+ * @param genTime generation time for the response token.
+ * @return the TimeStampResponse with a status of PKIStatus.GRANTED
+ * @throws TSPException on validation exception or internal error.
+ */
+ public TimeStampResponse generateGrantedResponse(
+ TimeStampRequest request,
+ BigInteger serialNumber,
+ Date genTime,
+ String statusString)
+ throws TSPException
+ {
+ if (genTime == null)
+ {
+ throw new TSPValidationException("The time source is not available.", PKIFailureInfo.timeNotAvailable);
+ }
+
+ request.validate(acceptedAlgorithms, acceptedPolicies, acceptedExtensions);
+
+ status = PKIStatus.GRANTED;
+ statusStrings = new ASN1EncodableVector();
+
+ if (statusString != null)
+ {
+ this.addStatusString(statusString);
+ }
+
+ PKIStatusInfo pkiStatusInfo = getPKIStatusInfo();
+
+ ContentInfo tstTokenContentInfo;
+ try
+ {
+ tstTokenContentInfo = tokenGenerator.generate(request, serialNumber, genTime).toCMSSignedData().toASN1Structure();
+ }
+ catch (TSPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new TSPException(
+ "Timestamp token received cannot be converted to ContentInfo", e);
+ }
+
+ TimeStampResp resp = new TimeStampResp(pkiStatusInfo, tstTokenContentInfo);
+
+ try
+ {
+ return new TimeStampResponse(resp);
+ }
+ catch (IOException e)
+ {
+ throw new TSPException("created badly formatted response!");
+ }
+ }
+
+ /**
+ * Generate a generic rejection response based on a TSPValidationException or
+ * an Exception. Exceptions which are not an instance of TSPValidationException
+ * will be treated as systemFailure. The return value of exception.getMessage() will
+ * be used as the status string for the response.
+ *
+ * @param exception the exception thrown on validating the request.
+ * @return a TimeStampResponse.
+ * @throws TSPException if a failure response cannot be generated.
+ */
+ public TimeStampResponse generateRejectedResponse(Exception exception)
+ throws TSPException
+ {
+ if (exception instanceof TSPValidationException)
+ {
+ return generateFailResponse(PKIStatus.REJECTION, ((TSPValidationException)exception).getFailureCode(), exception.getMessage());
+ }
+ else
+ {
+ return generateFailResponse(PKIStatus.REJECTION, PKIFailureInfo.systemFailure, exception.getMessage());
+ }
+ }
+
+ /**
+ * Generate a non-granted TimeStampResponse with chosen status and FailInfoField.
+ *
+ * @param status the PKIStatus to set.
+ * @param failInfoField the FailInfoField to set.
+ * @param statusString an optional string describing the failure.
+ * @return a TimeStampResponse with a failInfoField and optional statusString
+ * @throws TSPException in case the response could not be created
+ */
+ public TimeStampResponse generateFailResponse(int status, int failInfoField, String statusString)
+ throws TSPException
+ {
+ this.status = status;
+ this.statusStrings = new ASN1EncodableVector();
+
+ this.setFailInfoField(failInfoField);
+
+ if (statusString != null)
+ {
+ this.addStatusString(statusString);
+ }
+
+ PKIStatusInfo pkiStatusInfo = getPKIStatusInfo();
+
+ TimeStampResp resp = new TimeStampResp(pkiStatusInfo, null);
+
+ try
+ {
+ return new TimeStampResponse(resp);
+ }
+ catch (IOException e)
+ {
+ throw new TSPException("created badly formatted response!");
+ }
+ }
+
+ private Set convert(Set orig)
+ {
+ if (orig == null)
+ {
+ return orig;
+ }
+
+ Set con = new HashSet(orig.size());
+
+ for (Iterator it = orig.iterator(); it.hasNext();)
+ {
+ Object o = it.next();
+
+ if (o instanceof String)
+ {
+ con.add(new ASN1ObjectIdentifier((String)o));
+ }
+ else
+ {
+ con.add(o);
+ }
+ }
+
+ return con;
+ }
+
+ class FailInfo extends DERBitString
+ {
+ FailInfo(int failInfoValue)
+ {
+ super(getBytes(failInfoValue), getPadBits(failInfoValue));
+ }
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/TimeStampToken.java b/pkix/src/main/java/org/spongycastle/tsp/TimeStampToken.java
new file mode 100644
index 00000000..00d2a903
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/TimeStampToken.java
@@ -0,0 +1,393 @@
+package org.spongycastle.tsp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Date;
+
+import org.spongycastle.asn1.ASN1InputStream;
+import org.spongycastle.asn1.cms.Attribute;
+import org.spongycastle.asn1.cms.AttributeTable;
+import org.spongycastle.asn1.cms.ContentInfo;
+import org.spongycastle.asn1.cms.IssuerAndSerialNumber;
+import org.spongycastle.asn1.ess.ESSCertID;
+import org.spongycastle.asn1.ess.ESSCertIDv2;
+import org.spongycastle.asn1.ess.SigningCertificate;
+import org.spongycastle.asn1.ess.SigningCertificateV2;
+import org.spongycastle.asn1.nist.NISTObjectIdentifiers;
+import org.spongycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.spongycastle.asn1.tsp.TSTInfo;
+import org.spongycastle.asn1.x500.X500Name;
+import org.spongycastle.asn1.x509.AlgorithmIdentifier;
+import org.spongycastle.asn1.x509.GeneralName;
+import org.spongycastle.asn1.x509.IssuerSerial;
+import org.spongycastle.cert.X509CertificateHolder;
+import org.spongycastle.cms.CMSException;
+import org.spongycastle.cms.CMSProcessable;
+import org.spongycastle.cms.CMSSignedData;
+import org.spongycastle.cms.SignerId;
+import org.spongycastle.cms.SignerInformation;
+import org.spongycastle.cms.SignerInformationVerifier;
+import org.spongycastle.operator.DigestCalculator;
+import org.spongycastle.operator.OperatorCreationException;
+import org.spongycastle.util.Arrays;
+import org.spongycastle.util.Store;
+
+/**
+ * Carrier class for a TimeStampToken.
+ */
+public class TimeStampToken
+{
+ CMSSignedData tsToken;
+
+ SignerInformation tsaSignerInfo;
+
+ Date genTime;
+
+ TimeStampTokenInfo tstInfo;
+
+ CertID certID;
+
+ public TimeStampToken(ContentInfo contentInfo)
+ throws TSPException, IOException
+ {
+ this(getSignedData(contentInfo));
+ }
+
+ private static CMSSignedData getSignedData(ContentInfo contentInfo)
+ throws TSPException
+ {
+ try
+ {
+ return new CMSSignedData(contentInfo);
+ }
+ catch (CMSException e)
+ {
+ throw new TSPException("TSP parsing error: " + e.getMessage(), e.getCause());
+ }
+ }
+
+ public TimeStampToken(CMSSignedData signedData)
+ throws TSPException, IOException
+ {
+ this.tsToken = signedData;
+
+ if (!this.tsToken.getSignedContentTypeOID().equals(PKCSObjectIdentifiers.id_ct_TSTInfo.getId()))
+ {
+ throw new TSPValidationException("ContentInfo object not for a time stamp.");
+ }
+
+ Collection signers = tsToken.getSignerInfos().getSigners();
+
+ if (signers.size() != 1)
+ {
+ throw new IllegalArgumentException("Time-stamp token signed by "
+ + signers.size()
+ + " signers, but it must contain just the TSA signature.");
+ }
+
+ tsaSignerInfo = (SignerInformation)signers.iterator().next();
+
+ try
+ {
+ CMSProcessable content = tsToken.getSignedContent();
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ content.write(bOut);
+
+ ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+ this.tstInfo = new TimeStampTokenInfo(TSTInfo.getInstance(aIn.readObject()));
+
+ Attribute attr = tsaSignerInfo.getSignedAttributes().get(PKCSObjectIdentifiers.id_aa_signingCertificate);
+
+ if (attr != null)
+ {
+ SigningCertificate signCert = SigningCertificate.getInstance(attr.getAttrValues().getObjectAt(0));
+
+ this.certID = new CertID(ESSCertID.getInstance(signCert.getCerts()[0]));
+ }
+ else
+ {
+ attr = tsaSignerInfo.getSignedAttributes().get(PKCSObjectIdentifiers.id_aa_signingCertificateV2);
+
+ if (attr == null)
+ {
+ throw new TSPValidationException("no signing certificate attribute found, time stamp invalid.");
+ }
+
+ SigningCertificateV2 signCertV2 = SigningCertificateV2.getInstance(attr.getAttrValues().getObjectAt(0));
+
+ this.certID = new CertID(ESSCertIDv2.getInstance(signCertV2.getCerts()[0]));
+ }
+ }
+ catch (CMSException e)
+ {
+ throw new TSPException(e.getMessage(), e.getUnderlyingException());
+ }
+ }
+
+ public TimeStampTokenInfo getTimeStampInfo()
+ {
+ return tstInfo;
+ }
+
+ public SignerId getSID()
+ {
+ return tsaSignerInfo.getSID();
+ }
+
+ public AttributeTable getSignedAttributes()
+ {
+ return tsaSignerInfo.getSignedAttributes();
+ }
+
+ public AttributeTable getUnsignedAttributes()
+ {
+ return tsaSignerInfo.getUnsignedAttributes();
+ }
+
+ public Store getCertificates()
+ {
+ return tsToken.getCertificates();
+ }
+
+ public Store getCRLs()
+ {
+ return tsToken.getCRLs();
+ }
+
+ public Store getAttributeCertificates()
+ {
+ return tsToken.getAttributeCertificates();
+ }
+
+ /**
+ * Validate the time stamp token.
+ * <p>
+ * To be valid the token must be signed by the passed in certificate and
+ * the certificate must be the one referred to by the SigningCertificate
+ * attribute included in the hashed attributes of the token. The
+ * certificate must also have the ExtendedKeyUsageExtension with only
+ * KeyPurposeId.id_kp_timeStamping and have been valid at the time the
+ * timestamp was created.
+ * </p>
+ * <p>
+ * A successful call to validate means all the above are true.
+ * </p>
+ *
+ * @param sigVerifier the content verifier create the objects required to verify the CMS object in the timestamp.
+ * @throws TSPException if an exception occurs in processing the token.
+ * @throws TSPValidationException if the certificate or signature fail to be valid.
+ * @throws IllegalArgumentException if the sigVerifierProvider has no associated certificate.
+ */
+ public void validate(
+ SignerInformationVerifier sigVerifier)
+ throws TSPException, TSPValidationException
+ {
+ if (!sigVerifier.hasAssociatedCertificate())
+ {
+ throw new IllegalArgumentException("verifier provider needs an associated certificate");
+ }
+
+ try
+ {
+ X509CertificateHolder certHolder = sigVerifier.getAssociatedCertificate();
+ DigestCalculator calc = sigVerifier.getDigestCalculator(certID.getHashAlgorithm());
+
+ OutputStream cOut = calc.getOutputStream();
+
+ cOut.write(certHolder.getEncoded());
+ cOut.close();
+
+ if (!Arrays.constantTimeAreEqual(certID.getCertHash(), calc.getDigest()))
+ {
+ throw new TSPValidationException("certificate hash does not match certID hash.");
+ }
+
+ if (certID.getIssuerSerial() != null)
+ {
+ IssuerAndSerialNumber issuerSerial = new IssuerAndSerialNumber(certHolder.toASN1Structure());
+
+ if (!certID.getIssuerSerial().getSerial().equals(issuerSerial.getSerialNumber()))
+ {
+ throw new TSPValidationException("certificate serial number does not match certID for signature.");
+ }
+
+ GeneralName[] names = certID.getIssuerSerial().getIssuer().getNames();
+ boolean found = false;
+
+ for (int i = 0; i != names.length; i++)
+ {
+ if (names[i].getTagNo() == 4 && X500Name.getInstance(names[i].getName()).equals(X500Name.getInstance(issuerSerial.getName())))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ throw new TSPValidationException("certificate name does not match certID for signature. ");
+ }
+ }
+
+ TSPUtil.validateCertificate(certHolder);
+
+ if (!certHolder.isValidOn(tstInfo.getGenTime()))
+ {
+ throw new TSPValidationException("certificate not valid when time stamp created.");
+ }
+
+ if (!tsaSignerInfo.verify(sigVerifier))
+ {
+ throw new TSPValidationException("signature not created by certificate.");
+ }
+ }
+ catch (CMSException e)
+ {
+ if (e.getUnderlyingException() != null)
+ {
+ throw new TSPException(e.getMessage(), e.getUnderlyingException());
+ }
+ else
+ {
+ throw new TSPException("CMS exception: " + e, e);
+ }
+ }
+ catch (IOException e)
+ {
+ throw new TSPException("problem processing certificate: " + e, e);
+ }
+ catch (OperatorCreationException e)
+ {
+ throw new TSPException("unable to create digest: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Return true if the signature on time stamp token is valid.
+ * <p>
+ * Note: this is a much weaker proof of correctness than calling validate().
+ * </p>
+ *
+ * @param sigVerifier the content verifier create the objects required to verify the CMS object in the timestamp.
+ * @return true if the signature matches, false otherwise.
+ * @throws TSPException if the signature cannot be processed or the provider cannot match the algorithm.
+ */
+ public boolean isSignatureValid(
+ SignerInformationVerifier sigVerifier)
+ throws TSPException
+ {
+ try
+ {
+ return tsaSignerInfo.verify(sigVerifier);
+ }
+ catch (CMSException e)
+ {
+ if (e.getUnderlyingException() != null)
+ {
+ throw new TSPException(e.getMessage(), e.getUnderlyingException());
+ }
+ else
+ {
+ throw new TSPException("CMS exception: " + e, e);
+ }
+ }
+ }
+
+ /**
+ * Return the underlying CMSSignedData object.
+ *
+ * @return the underlying CMS structure.
+ */
+ public CMSSignedData toCMSSignedData()
+ {
+ return tsToken;
+ }
+
+ /**
+ * Return a ASN.1 encoded byte stream representing the encoded object.
+ *
+ * @throws IOException if encoding fails.
+ */
+ public byte[] getEncoded()
+ throws IOException
+ {
+ return tsToken.getEncoded();
+ }
+
+ // perhaps this should be done using an interface on the ASN.1 classes...
+ private class CertID
+ {
+ private ESSCertID certID;
+ private ESSCertIDv2 certIDv2;
+
+ CertID(ESSCertID certID)
+ {
+ this.certID = certID;
+ this.certIDv2 = null;
+ }
+
+ CertID(ESSCertIDv2 certID)
+ {
+ this.certIDv2 = certID;
+ this.certID = null;
+ }
+
+ public String getHashAlgorithmName()
+ {
+ if (certID != null)
+ {
+ return "SHA-1";
+ }
+ else
+ {
+ if (NISTObjectIdentifiers.id_sha256.equals(certIDv2.getHashAlgorithm().getAlgorithm()))
+ {
+ return "SHA-256";
+ }
+ return certIDv2.getHashAlgorithm().getAlgorithm().getId();
+ }
+ }
+
+ public AlgorithmIdentifier getHashAlgorithm()
+ {
+ if (certID != null)
+ {
+ return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1);
+ }
+ else
+ {
+ return certIDv2.getHashAlgorithm();
+ }
+ }
+
+ public byte[] getCertHash()
+ {
+ if (certID != null)
+ {
+ return certID.getCertHash();
+ }
+ else
+ {
+ return certIDv2.getCertHash();
+ }
+ }
+
+ public IssuerSerial getIssuerSerial()
+ {
+ if (certID != null)
+ {
+ return certID.getIssuerSerial();
+ }
+ else
+ {
+ return certIDv2.getIssuerSerial();
+ }
+ }
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenGenerator.java b/pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenGenerator.java
new file mode 100644
index 00000000..f3783f80
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenGenerator.java
@@ -0,0 +1,380 @@
+package org.spongycastle.tsp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.spongycastle.asn1.ASN1Boolean;
+import org.spongycastle.asn1.ASN1Encoding;
+import org.spongycastle.asn1.ASN1GeneralizedTime;
+import org.spongycastle.asn1.ASN1Integer;
+import org.spongycastle.asn1.ASN1ObjectIdentifier;
+import org.spongycastle.asn1.DERNull;
+import org.spongycastle.asn1.cms.AttributeTable;
+import org.spongycastle.asn1.ess.ESSCertID;
+import org.spongycastle.asn1.ess.ESSCertIDv2;
+import org.spongycastle.asn1.ess.SigningCertificate;
+import org.spongycastle.asn1.ess.SigningCertificateV2;
+import org.spongycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.spongycastle.asn1.tsp.Accuracy;
+import org.spongycastle.asn1.tsp.MessageImprint;
+import org.spongycastle.asn1.tsp.TSTInfo;
+import org.spongycastle.asn1.x509.AlgorithmIdentifier;
+import org.spongycastle.asn1.x509.GeneralName;
+import org.spongycastle.asn1.x509.GeneralNames;
+import org.spongycastle.asn1.x509.IssuerSerial;
+import org.spongycastle.cert.X509CertificateHolder;
+import org.spongycastle.cms.CMSAttributeTableGenerationException;
+import org.spongycastle.cms.CMSAttributeTableGenerator;
+import org.spongycastle.cms.CMSException;
+import org.spongycastle.cms.CMSProcessableByteArray;
+import org.spongycastle.cms.CMSSignedData;
+import org.spongycastle.cms.CMSSignedDataGenerator;
+import org.spongycastle.cms.SignerInfoGenerator;
+import org.spongycastle.operator.DigestCalculator;
+import org.spongycastle.util.CollectionStore;
+import org.spongycastle.util.Store;
+
+/**
+ * Currently the class supports ESSCertID by if a digest calculator based on SHA1 is passed in, otherwise it uses
+ * ESSCertIDv2. In the event you need to pass both types, you will need to override the SignedAttributeGenerator
+ * for the SignerInfoGeneratorBuilder you are using. For the default for ESSCertIDv2 the code will look something
+ * like the following:
+ * <pre>
+ * final ESSCertID essCertid = new ESSCertID(certHashSha1, issuerSerial);
+ * final ESSCertIDv2 essCertidV2 = new ESSCertIDv2(certHashSha256, issuerSerial);
+ *
+ * signerInfoGenBuilder.setSignedAttributeGenerator(new CMSAttributeTableGenerator()
+ * {
+ * public AttributeTable getAttributes(Map parameters)
+ * throws CMSAttributeTableGenerationException
+ * {
+ * CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator();
+ *
+ * AttributeTable table = attrGen.getAttributes(parameters);
+ *
+ * table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid));
+ * table = table.add(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new SigningCertificateV2(essCertidV2));
+ *
+ * return table;
+ * }
+ * });
+ * </pre>
+ */
+public class TimeStampTokenGenerator
+{
+ int accuracySeconds = -1;
+
+ int accuracyMillis = -1;
+
+ int accuracyMicros = -1;
+
+ boolean ordering = false;
+
+ GeneralName tsa = null;
+
+ private ASN1ObjectIdentifier tsaPolicyOID;
+
+ private List certs = new ArrayList();
+ private List crls = new ArrayList();
+ private List attrCerts = new ArrayList();
+ private Map otherRevoc = new HashMap();
+ private SignerInfoGenerator signerInfoGen;
+
+ /**
+ * Basic Constructor - set up a calculator based on signerInfoGen with a ESSCertID calculated from
+ * the signer's associated certificate using the sha1DigestCalculator. If alternate values are required
+ * for id-aa-signingCertificate they should be added to the signerInfoGen object before it is passed in,
+ * otherwise a standard digest based value will be added.
+ *
+ * @param signerInfoGen the generator for the signer we are using.
+ * @param digestCalculator calculator for to use for digest of certificate.
+ * @param tsaPolicy tasPolicy to send.
+ * @throws IllegalArgumentException if calculator is not SHA-1 or there is no associated certificate for the signer,
+ * @throws TSPException if the signer certificate cannot be processed.
+ */
+ public TimeStampTokenGenerator(
+ final SignerInfoGenerator signerInfoGen,
+ DigestCalculator digestCalculator,
+ ASN1ObjectIdentifier tsaPolicy)
+ throws IllegalArgumentException, TSPException
+ {
+ this(signerInfoGen, digestCalculator, tsaPolicy, false);
+ }
+
+ /**
+ * Basic Constructor - set up a calculator based on signerInfoGen with a ESSCertID calculated from
+ * the signer's associated certificate using the sha1DigestCalculator. If alternate values are required
+ * for id-aa-signingCertificate they should be added to the signerInfoGen object before it is passed in,
+ * otherwise a standard digest based value will be added.
+ *
+ * @param signerInfoGen the generator for the signer we are using.
+ * @param digestCalculator calculator for to use for digest of certificate.
+ * @param tsaPolicy tasPolicy to send.
+ * @param isIssuerSerialIncluded should issuerSerial be included in the ESSCertIDs, true if yes, by default false.
+ * @throws IllegalArgumentException if calculator is not SHA-1 or there is no associated certificate for the signer,
+ * @throws TSPException if the signer certificate cannot be processed.
+ */
+ public TimeStampTokenGenerator(
+ final SignerInfoGenerator signerInfoGen,
+ DigestCalculator digestCalculator,
+ ASN1ObjectIdentifier tsaPolicy,
+ boolean isIssuerSerialIncluded)
+ throws IllegalArgumentException, TSPException
+ {
+ this.signerInfoGen = signerInfoGen;
+ this.tsaPolicyOID = tsaPolicy;
+
+ if (!signerInfoGen.hasAssociatedCertificate())
+ {
+ throw new IllegalArgumentException("SignerInfoGenerator must have an associated certificate");
+ }
+
+ X509CertificateHolder assocCert = signerInfoGen.getAssociatedCertificate();
+ TSPUtil.validateCertificate(assocCert);
+
+ try
+ {
+ OutputStream dOut = digestCalculator.getOutputStream();
+
+ dOut.write(assocCert.getEncoded());
+
+ dOut.close();
+
+ if (digestCalculator.getAlgorithmIdentifier().getAlgorithm().equals(OIWObjectIdentifiers.idSHA1))
+ {
+ final ESSCertID essCertid = new ESSCertID(digestCalculator.getDigest(),
+ isIssuerSerialIncluded ? new IssuerSerial(new GeneralNames(new GeneralName(assocCert.getIssuer())), assocCert.getSerialNumber())
+ : null);
+
+ this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator()
+ {
+ public AttributeTable getAttributes(Map parameters)
+ throws CMSAttributeTableGenerationException
+ {
+ AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters);
+
+ if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificate) == null)
+ {
+ return table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid));
+ }
+
+ return table;
+ }
+ }, signerInfoGen.getUnsignedAttributeTableGenerator());
+ }
+ else
+ {
+ AlgorithmIdentifier digAlgID = new AlgorithmIdentifier(digestCalculator.getAlgorithmIdentifier().getAlgorithm());
+ final ESSCertIDv2 essCertid = new ESSCertIDv2(digAlgID, digestCalculator.getDigest(),
+ isIssuerSerialIncluded ? new IssuerSerial(new GeneralNames(new GeneralName(assocCert.getIssuer())), new ASN1Integer(assocCert.getSerialNumber()))
+ : null);
+
+ this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator()
+ {
+ public AttributeTable getAttributes(Map parameters)
+ throws CMSAttributeTableGenerationException
+ {
+ AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters);
+
+ if (table.get(PKCSObjectIdentifiers.id_aa_signingCertificateV2) == null)
+ {
+ return table.add(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new SigningCertificateV2(essCertid));
+ }
+
+ return table;
+ }
+ }, signerInfoGen.getUnsignedAttributeTableGenerator());
+ }
+ }
+ catch (IOException e)
+ {
+ throw new TSPException("Exception processing certificate.", e);
+ }
+ }
+
+ /**
+ * Add the store of X509 Certificates to the generator.
+ *
+ * @param certStore a Store containing X509CertificateHolder objects
+ */
+ public void addCertificates(
+ Store certStore)
+ {
+ certs.addAll(certStore.getMatches(null));
+ }
+
+ /**
+ *
+ * @param crlStore a Store containing X509CRLHolder objects.
+ */
+ public void addCRLs(
+ Store crlStore)
+ {
+ crls.addAll(crlStore.getMatches(null));
+ }
+
+ /**
+ *
+ * @param attrStore a Store containing X509AttributeCertificate objects.
+ */
+ public void addAttributeCertificates(
+ Store attrStore)
+ {
+ attrCerts.addAll(attrStore.getMatches(null));
+ }
+
+ /**
+ * Add a Store of otherRevocationData to the CRL set to be included with the generated TimeStampToken.
+ *
+ * @param otherRevocationInfoFormat the OID specifying the format of the otherRevocationInfo data.
+ * @param otherRevocationInfos a Store of otherRevocationInfo data to add.
+ */
+ public void addOtherRevocationInfo(
+ ASN1ObjectIdentifier otherRevocationInfoFormat,
+ Store otherRevocationInfos)
+ {
+ otherRevoc.put(otherRevocationInfoFormat, otherRevocationInfos.getMatches(null));
+ }
+
+ public void setAccuracySeconds(int accuracySeconds)
+ {
+ this.accuracySeconds = accuracySeconds;
+ }
+
+ public void setAccuracyMillis(int accuracyMillis)
+ {
+ this.accuracyMillis = accuracyMillis;
+ }
+
+ public void setAccuracyMicros(int accuracyMicros)
+ {
+ this.accuracyMicros = accuracyMicros;
+ }
+
+ public void setOrdering(boolean ordering)
+ {
+ this.ordering = ordering;
+ }
+
+ public void setTSA(GeneralName tsa)
+ {
+ this.tsa = tsa;
+ }
+
+ /**
+ * Generate a TimeStampToken for the passed in request and serialNumber marking it with the passed in genTime.
+ *
+ * @param request the originating request.
+ * @param serialNumber serial number for the TimeStampToken
+ * @param genTime token generation time.
+ * @return a TimeStampToken
+ * @throws TSPException
+ */
+ public TimeStampToken generate(
+ TimeStampRequest request,
+ BigInteger serialNumber,
+ Date genTime)
+ throws TSPException
+ {
+ ASN1ObjectIdentifier digestAlgOID = request.getMessageImprintAlgOID();
+
+ AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, DERNull.INSTANCE);
+ MessageImprint messageImprint = new MessageImprint(algID, request.getMessageImprintDigest());
+
+ Accuracy accuracy = null;
+ if (accuracySeconds > 0 || accuracyMillis > 0 || accuracyMicros > 0)
+ {
+ ASN1Integer seconds = null;
+ if (accuracySeconds > 0)
+ {
+ seconds = new ASN1Integer(accuracySeconds);
+ }
+
+ ASN1Integer millis = null;
+ if (accuracyMillis > 0)
+ {
+ millis = new ASN1Integer(accuracyMillis);
+ }
+
+ ASN1Integer micros = null;
+ if (accuracyMicros > 0)
+ {
+ micros = new ASN1Integer(accuracyMicros);
+ }
+
+ accuracy = new Accuracy(seconds, millis, micros);
+ }
+
+ ASN1Boolean derOrdering = null;
+ if (ordering)
+ {
+ derOrdering = new ASN1Boolean(ordering);
+ }
+
+ ASN1Integer nonce = null;
+ if (request.getNonce() != null)
+ {
+ nonce = new ASN1Integer(request.getNonce());
+ }
+
+ ASN1ObjectIdentifier tsaPolicy = tsaPolicyOID;
+ if (request.getReqPolicy() != null)
+ {
+ tsaPolicy = request.getReqPolicy();
+ }
+
+ TSTInfo tstInfo = new TSTInfo(tsaPolicy,
+ messageImprint, new ASN1Integer(serialNumber),
+ new ASN1GeneralizedTime(genTime), accuracy, derOrdering,
+ nonce, tsa, request.getExtensions());
+
+ try
+ {
+ CMSSignedDataGenerator signedDataGenerator = new CMSSignedDataGenerator();
+
+ if (request.getCertReq())
+ {
+ // TODO: do we need to check certs non-empty?
+ signedDataGenerator.addCertificates(new CollectionStore(certs));
+ signedDataGenerator.addAttributeCertificates(new CollectionStore(attrCerts));
+ }
+
+ signedDataGenerator.addCRLs(new CollectionStore(crls));
+
+ if (!otherRevoc.isEmpty())
+ {
+ for (Iterator it = otherRevoc.keySet().iterator(); it.hasNext();)
+ {
+ ASN1ObjectIdentifier format = (ASN1ObjectIdentifier)it.next();
+
+ signedDataGenerator.addOtherRevocationInfo(format, new CollectionStore((Collection)otherRevoc.get(format)));
+ }
+ }
+
+ signedDataGenerator.addSignerInfoGenerator(signerInfoGen);
+
+ byte[] derEncodedTSTInfo = tstInfo.getEncoded(ASN1Encoding.DER);
+
+ CMSSignedData signedData = signedDataGenerator.generate(new CMSProcessableByteArray(PKCSObjectIdentifiers.id_ct_TSTInfo, derEncodedTSTInfo), true);
+
+ return new TimeStampToken(signedData);
+ }
+ catch (CMSException cmsEx)
+ {
+ throw new TSPException("Error generating time-stamp token", cmsEx);
+ }
+ catch (IOException e)
+ {
+ throw new TSPException("Exception encoding info", e);
+ }
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenInfo.java b/pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenInfo.java
new file mode 100644
index 00000000..e9f70650
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/TimeStampTokenInfo.java
@@ -0,0 +1,121 @@
+package org.spongycastle.tsp;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.text.ParseException;
+import java.util.Date;
+
+import org.spongycastle.asn1.ASN1ObjectIdentifier;
+import org.spongycastle.asn1.tsp.Accuracy;
+import org.spongycastle.asn1.tsp.TSTInfo;
+import org.spongycastle.asn1.x509.AlgorithmIdentifier;
+import org.spongycastle.asn1.x509.GeneralName;
+
+public class TimeStampTokenInfo
+{
+ TSTInfo tstInfo;
+ Date genTime;
+
+ TimeStampTokenInfo(TSTInfo tstInfo)
+ throws TSPException, IOException
+ {
+ this.tstInfo = tstInfo;
+
+ try
+ {
+ this.genTime = tstInfo.getGenTime().getDate();
+ }
+ catch (ParseException e)
+ {
+ throw new TSPException("unable to parse genTime field");
+ }
+ }
+
+ public boolean isOrdered()
+ {
+ return tstInfo.getOrdering().isTrue();
+ }
+
+ public Accuracy getAccuracy()
+ {
+ return tstInfo.getAccuracy();
+ }
+
+ public Date getGenTime()
+ {
+ return genTime;
+ }
+
+ public GenTimeAccuracy getGenTimeAccuracy()
+ {
+ if (this.getAccuracy() != null)
+ {
+ return new GenTimeAccuracy(this.getAccuracy());
+ }
+
+ return null;
+ }
+
+ public ASN1ObjectIdentifier getPolicy()
+ {
+ return tstInfo.getPolicy();
+ }
+
+ public BigInteger getSerialNumber()
+ {
+ return tstInfo.getSerialNumber().getValue();
+ }
+
+ public GeneralName getTsa()
+ {
+ return tstInfo.getTsa();
+ }
+
+ /**
+ * @return the nonce value, null if there isn't one.
+ */
+ public BigInteger getNonce()
+ {
+ if (tstInfo.getNonce() != null)
+ {
+ return tstInfo.getNonce().getValue();
+ }
+
+ return null;
+ }
+
+ public AlgorithmIdentifier getHashAlgorithm()
+ {
+ return tstInfo.getMessageImprint().getHashAlgorithm();
+ }
+
+ public ASN1ObjectIdentifier getMessageImprintAlgOID()
+ {
+ return tstInfo.getMessageImprint().getHashAlgorithm().getAlgorithm();
+ }
+
+ public byte[] getMessageImprintDigest()
+ {
+ return tstInfo.getMessageImprint().getHashedMessage();
+ }
+
+ public byte[] getEncoded()
+ throws IOException
+ {
+ return tstInfo.getEncoded();
+ }
+
+ /**
+ * @deprecated use toASN1Structure
+ * @return
+ */
+ public TSTInfo toTSTInfo()
+ {
+ return tstInfo;
+ }
+
+ public TSTInfo toASN1Structure()
+ {
+ return tstInfo;
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedData.java b/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedData.java
new file mode 100644
index 00000000..844f123a
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedData.java
@@ -0,0 +1,204 @@
+package org.spongycastle.tsp.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.spongycastle.asn1.ASN1InputStream;
+import org.spongycastle.asn1.DERIA5String;
+import org.spongycastle.asn1.cms.AttributeTable;
+import org.spongycastle.asn1.cms.CMSObjectIdentifiers;
+import org.spongycastle.asn1.cms.ContentInfo;
+import org.spongycastle.asn1.cms.Evidence;
+import org.spongycastle.asn1.cms.TimeStampAndCRL;
+import org.spongycastle.asn1.cms.TimeStampTokenEvidence;
+import org.spongycastle.asn1.cms.TimeStampedData;
+import org.spongycastle.cms.CMSException;
+import org.spongycastle.operator.DigestCalculator;
+import org.spongycastle.operator.DigestCalculatorProvider;
+import org.spongycastle.operator.OperatorCreationException;
+import org.spongycastle.tsp.TimeStampToken;
+
+public class CMSTimeStampedData
+{
+ private TimeStampedData timeStampedData;
+ private ContentInfo contentInfo;
+ private TimeStampDataUtil util;
+
+ public CMSTimeStampedData(ContentInfo contentInfo)
+ {
+ this.initialize(contentInfo);
+ }
+
+ public CMSTimeStampedData(InputStream in)
+ throws IOException
+ {
+ try
+ {
+ initialize(ContentInfo.getInstance(new ASN1InputStream(in).readObject()));
+ }
+ catch (ClassCastException e)
+ {
+ throw new IOException("Malformed content: " + e);
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw new IOException("Malformed content: " + e);
+ }
+ }
+
+ public CMSTimeStampedData(byte[] baseData)
+ throws IOException
+ {
+ this(new ByteArrayInputStream(baseData));
+ }
+
+ private void initialize(ContentInfo contentInfo)
+ {
+ this.contentInfo = contentInfo;
+
+ if (CMSObjectIdentifiers.timestampedData.equals(contentInfo.getContentType()))
+ {
+ this.timeStampedData = TimeStampedData.getInstance(contentInfo.getContent());
+ }
+ else
+ {
+ throw new IllegalArgumentException("Malformed content - type must be " + CMSObjectIdentifiers.timestampedData.getId());
+ }
+
+ util = new TimeStampDataUtil(this.timeStampedData);
+ }
+
+ public byte[] calculateNextHash(DigestCalculator calculator)
+ throws CMSException
+ {
+ return util.calculateNextHash(calculator);
+ }
+
+ /**
+ * Return a new timeStampedData object with the additional token attached.
+ *
+ * @throws CMSException
+ */
+ public CMSTimeStampedData addTimeStamp(TimeStampToken token)
+ throws CMSException
+ {
+ TimeStampAndCRL[] timeStamps = util.getTimeStamps();
+ TimeStampAndCRL[] newTimeStamps = new TimeStampAndCRL[timeStamps.length + 1];
+
+ System.arraycopy(timeStamps, 0, newTimeStamps, 0, timeStamps.length);
+
+ newTimeStamps[timeStamps.length] = new TimeStampAndCRL(token.toCMSSignedData().toASN1Structure());
+
+ return new CMSTimeStampedData(new ContentInfo(CMSObjectIdentifiers.timestampedData, new TimeStampedData(timeStampedData.getDataUri(), timeStampedData.getMetaData(), timeStampedData.getContent(), new Evidence(new TimeStampTokenEvidence(newTimeStamps)))));
+ }
+
+ public byte[] getContent()
+ {
+ if (timeStampedData.getContent() != null)
+ {
+ return timeStampedData.getContent().getOctets();
+ }
+
+ return null;
+ }
+
+ public URI getDataUri()
+ throws URISyntaxException
+ {
+ DERIA5String dataURI = this.timeStampedData.getDataUri();
+
+ if (dataURI != null)
+ {
+ return new URI(dataURI.getString());
+ }
+
+ return null;
+ }
+
+ public String getFileName()
+ {
+ return util.getFileName();
+ }
+
+ public String getMediaType()
+ {
+ return util.getMediaType();
+ }
+
+ public AttributeTable getOtherMetaData()
+ {
+ return util.getOtherMetaData();
+ }
+
+ public TimeStampToken[] getTimeStampTokens()
+ throws CMSException
+ {
+ return util.getTimeStampTokens();
+ }
+
+ /**
+ * Initialise the passed in calculator with the MetaData for this message, if it is
+ * required as part of the initial message imprint calculation.
+ *
+ * @param calculator the digest calculator to be initialised.
+ * @throws CMSException if the MetaData is required and cannot be processed
+ */
+ public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+ throws CMSException
+ {
+ util.initialiseMessageImprintDigestCalculator(calculator);
+ }
+
+ /**
+ * Returns an appropriately initialised digest calculator based on the message imprint algorithm
+ * described in the first time stamp in the TemporalData for this message. If the metadata is required
+ * to be included in the digest calculation, the returned calculator will be pre-initialised.
+ *
+ * @param calculatorProvider a provider of DigestCalculator objects.
+ * @return an initialised digest calculator.
+ * @throws OperatorCreationException if the provider is unable to create the calculator.
+ */
+ public DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider)
+ throws OperatorCreationException
+ {
+ return util.getMessageImprintDigestCalculator(calculatorProvider);
+ }
+
+ /**
+ * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData.
+ *
+ * @param calculatorProvider provider for digest calculators
+ * @param dataDigest the calculated data digest for the message
+ * @throws ImprintDigestInvalidException if an imprint digest fails to compare
+ * @throws CMSException if an exception occurs processing the message.
+ */
+ public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest)
+ throws ImprintDigestInvalidException, CMSException
+ {
+ util.validate(calculatorProvider, dataDigest);
+ }
+
+ /**
+ * Validate the passed in timestamp token against the tokens and data present in the message.
+ *
+ * @param calculatorProvider provider for digest calculators
+ * @param dataDigest the calculated data digest for the message.
+ * @param timeStampToken the timestamp token of interest.
+ * @throws ImprintDigestInvalidException if the token is not present in the message, or an imprint digest fails to compare.
+ * @throws CMSException if an exception occurs processing the message.
+ */
+ public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken)
+ throws ImprintDigestInvalidException, CMSException
+ {
+ util.validate(calculatorProvider, dataDigest, timeStampToken);
+ }
+
+ public byte[] getEncoded()
+ throws IOException
+ {
+ return contentInfo.getEncoded();
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataGenerator.java b/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataGenerator.java
new file mode 100644
index 00000000..a1ad0c0f
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataGenerator.java
@@ -0,0 +1,70 @@
+package org.spongycastle.tsp.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.spongycastle.asn1.ASN1OctetString;
+import org.spongycastle.asn1.BEROctetString;
+import org.spongycastle.asn1.DERIA5String;
+import org.spongycastle.asn1.cms.CMSObjectIdentifiers;
+import org.spongycastle.asn1.cms.ContentInfo;
+import org.spongycastle.asn1.cms.Evidence;
+import org.spongycastle.asn1.cms.TimeStampAndCRL;
+import org.spongycastle.asn1.cms.TimeStampTokenEvidence;
+import org.spongycastle.asn1.cms.TimeStampedData;
+import org.spongycastle.cms.CMSException;
+import org.spongycastle.tsp.TimeStampToken;
+import org.spongycastle.util.io.Streams;
+
+public class CMSTimeStampedDataGenerator
+ extends CMSTimeStampedGenerator
+{
+ public CMSTimeStampedData generate(TimeStampToken timeStamp) throws CMSException
+ {
+ return generate(timeStamp, (InputStream)null);
+ }
+
+ public CMSTimeStampedData generate(TimeStampToken timeStamp, byte[] content) throws CMSException
+ {
+ return generate(timeStamp, new ByteArrayInputStream(content));
+ }
+
+ public CMSTimeStampedData generate(TimeStampToken timeStamp, InputStream content)
+ throws CMSException
+ {
+ ByteArrayOutputStream contentOut = new ByteArrayOutputStream();
+
+ if (content != null)
+ {
+ try
+ {
+ Streams.pipeAll(content, contentOut);
+ }
+ catch (IOException e)
+ {
+ throw new CMSException("exception encapsulating content: " + e.getMessage(), e);
+ }
+ }
+
+ ASN1OctetString encContent = null;
+
+ if (contentOut.size() != 0)
+ {
+ encContent = new BEROctetString(contentOut.toByteArray());
+ }
+
+ TimeStampAndCRL stamp = new TimeStampAndCRL(timeStamp.toCMSSignedData().toASN1Structure());
+
+ DERIA5String asn1DataUri = null;
+
+ if (dataUri != null)
+ {
+ asn1DataUri = new DERIA5String(dataUri.toString());
+ }
+
+ return new CMSTimeStampedData(new ContentInfo(CMSObjectIdentifiers.timestampedData, new TimeStampedData(asn1DataUri, metaData, encContent, new Evidence(new TimeStampTokenEvidence(stamp)))));
+ }
+}
+
diff --git a/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataParser.java b/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataParser.java
new file mode 100644
index 00000000..c3518b77
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedDataParser.java
@@ -0,0 +1,207 @@
+package org.spongycastle.tsp.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.spongycastle.asn1.BERTags;
+import org.spongycastle.asn1.DERIA5String;
+import org.spongycastle.asn1.cms.AttributeTable;
+import org.spongycastle.asn1.cms.CMSObjectIdentifiers;
+import org.spongycastle.asn1.cms.ContentInfoParser;
+import org.spongycastle.asn1.cms.TimeStampedDataParser;
+import org.spongycastle.cms.CMSContentInfoParser;
+import org.spongycastle.cms.CMSException;
+import org.spongycastle.operator.DigestCalculator;
+import org.spongycastle.operator.DigestCalculatorProvider;
+import org.spongycastle.operator.OperatorCreationException;
+import org.spongycastle.tsp.TimeStampToken;
+import org.spongycastle.util.io.Streams;
+
+public class CMSTimeStampedDataParser
+ extends CMSContentInfoParser
+{
+ private TimeStampedDataParser timeStampedData;
+ private TimeStampDataUtil util;
+
+ public CMSTimeStampedDataParser(InputStream in)
+ throws CMSException
+ {
+ super(in);
+
+ initialize(_contentInfo);
+ }
+
+ public CMSTimeStampedDataParser(byte[] baseData)
+ throws CMSException
+ {
+ this(new ByteArrayInputStream(baseData));
+ }
+
+ private void initialize(ContentInfoParser contentInfo)
+ throws CMSException
+ {
+ try
+ {
+ if (CMSObjectIdentifiers.timestampedData.equals(contentInfo.getContentType()))
+ {
+ this.timeStampedData = TimeStampedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));
+ }
+ else
+ {
+ throw new IllegalArgumentException("Malformed content - type must be " + CMSObjectIdentifiers.timestampedData.getId());
+ }
+ }
+ catch (IOException e)
+ {
+ throw new CMSException("parsing exception: " + e.getMessage(), e);
+ }
+ }
+
+ public byte[] calculateNextHash(DigestCalculator calculator)
+ throws CMSException
+ {
+ return util.calculateNextHash(calculator);
+ }
+
+ public InputStream getContent()
+ {
+ if (timeStampedData.getContent() != null)
+ {
+ return timeStampedData.getContent().getOctetStream();
+ }
+
+ return null;
+ }
+
+ public URI getDataUri()
+ throws URISyntaxException
+ {
+ DERIA5String dataURI = this.timeStampedData.getDataUri();
+
+ if (dataURI != null)
+ {
+ return new URI(dataURI.getString());
+ }
+
+ return null;
+ }
+
+ public String getFileName()
+ {
+ return util.getFileName();
+ }
+
+ public String getMediaType()
+ {
+ return util.getMediaType();
+ }
+
+ public AttributeTable getOtherMetaData()
+ {
+ return util.getOtherMetaData();
+ }
+
+ /**
+ * Initialise the passed in calculator with the MetaData for this message, if it is
+ * required as part of the initial message imprint calculation.
+ *
+ * @param calculator the digest calculator to be initialised.
+ * @throws CMSException if the MetaData is required and cannot be processed
+ */
+ public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+ throws CMSException
+ {
+ util.initialiseMessageImprintDigestCalculator(calculator);
+ }
+
+ /**
+ * Returns an appropriately initialised digest calculator based on the message imprint algorithm
+ * described in the first time stamp in the TemporalData for this message. If the metadata is required
+ * to be included in the digest calculation, the returned calculator will be pre-initialised.
+ *
+ * @param calculatorProvider a provider of DigestCalculator objects.
+ * @return an initialised digest calculator.
+ * @throws OperatorCreationException if the provider is unable to create the calculator.
+ */
+ public DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider)
+ throws OperatorCreationException
+ {
+ try
+ {
+ parseTimeStamps();
+ }
+ catch (CMSException e)
+ {
+ throw new OperatorCreationException("unable to extract algorithm ID: " + e.getMessage(), e);
+ }
+
+ return util.getMessageImprintDigestCalculator(calculatorProvider);
+ }
+
+ public TimeStampToken[] getTimeStampTokens()
+ throws CMSException
+ {
+ parseTimeStamps();
+
+ return util.getTimeStampTokens();
+ }
+
+ /**
+ * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData.
+ *
+ * @param calculatorProvider provider for digest calculators
+ * @param dataDigest the calculated data digest for the message
+ * @throws ImprintDigestInvalidException if an imprint digest fails to compare
+ * @throws CMSException if an exception occurs processing the message.
+ */
+ public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest)
+ throws ImprintDigestInvalidException, CMSException
+ {
+ parseTimeStamps();
+
+ util.validate(calculatorProvider, dataDigest);
+ }
+
+ /**
+ * Validate the passed in timestamp token against the tokens and data present in the message.
+ *
+ * @param calculatorProvider provider for digest calculators
+ * @param dataDigest the calculated data digest for the message.
+ * @param timeStampToken the timestamp token of interest.
+ * @throws ImprintDigestInvalidException if the token is not present in the message, or an imprint digest fails to compare.
+ * @throws CMSException if an exception occurs processing the message.
+ */
+ public void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken)
+ throws ImprintDigestInvalidException, CMSException
+ {
+ parseTimeStamps();
+
+ util.validate(calculatorProvider, dataDigest, timeStampToken);
+ }
+
+ private void parseTimeStamps()
+ throws CMSException
+ {
+ try
+ {
+ if (util == null)
+ {
+ InputStream cont = this.getContent();
+
+ if (cont != null)
+ {
+ Streams.drain(cont);
+ }
+
+ util = new TimeStampDataUtil(timeStampedData);
+ }
+ }
+ catch (IOException e)
+ {
+ throw new CMSException("unable to parse evidence block: " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedGenerator.java b/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedGenerator.java
new file mode 100644
index 00000000..9dbeb97f
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/cms/CMSTimeStampedGenerator.java
@@ -0,0 +1,88 @@
+package org.spongycastle.tsp.cms;
+
+import java.net.URI;
+
+import org.spongycastle.asn1.ASN1Boolean;
+import org.spongycastle.asn1.DERIA5String;
+import org.spongycastle.asn1.DERUTF8String;
+import org.spongycastle.asn1.cms.Attributes;
+import org.spongycastle.asn1.cms.MetaData;
+import org.spongycastle.cms.CMSException;
+import org.spongycastle.operator.DigestCalculator;
+
+public class CMSTimeStampedGenerator
+{
+ protected MetaData metaData;
+ protected URI dataUri;
+
+ /**
+ * Set the dataURI to be included in message.
+ *
+ * @param dataUri URI for the data the initial message imprint digest is based on.
+ */
+ public void setDataUri(URI dataUri)
+ {
+ this.dataUri = dataUri;
+ }
+
+ /**
+ * Set the MetaData for the generated message.
+ *
+ * @param hashProtected true if the MetaData should be included in first imprint calculation, false otherwise.
+ * @param fileName optional file name, may be null.
+ * @param mediaType optional media type, may be null.
+ */
+ public void setMetaData(boolean hashProtected, String fileName, String mediaType)
+ {
+ setMetaData(hashProtected, fileName, mediaType, null);
+ }
+
+ /**
+ * Set the MetaData for the generated message.
+ *
+ * @param hashProtected true if the MetaData should be included in first imprint calculation, false otherwise.
+ * @param fileName optional file name, may be null.
+ * @param mediaType optional media type, may be null.
+ * @param attributes optional attributes, may be null.
+ */
+ public void setMetaData(boolean hashProtected, String fileName, String mediaType, Attributes attributes)
+ {
+ DERUTF8String asn1FileName = null;
+
+ if (fileName != null)
+ {
+ asn1FileName = new DERUTF8String(fileName);
+ }
+
+ DERIA5String asn1MediaType = null;
+
+ if (mediaType != null)
+ {
+ asn1MediaType = new DERIA5String(mediaType);
+ }
+
+ setMetaData(hashProtected, asn1FileName, asn1MediaType, attributes);
+ }
+
+ private void setMetaData(boolean hashProtected, DERUTF8String fileName, DERIA5String mediaType, Attributes attributes)
+ {
+ this.metaData = new MetaData(ASN1Boolean.getInstance(hashProtected), fileName, mediaType, attributes);
+ }
+
+ /**
+ * Initialise the passed in calculator with the MetaData for this message, if it is
+ * required as part of the initial message imprint calculation. After initialisation the
+ * calculator can then be used to calculate the initial message imprint digest for the first
+ * timestamp.
+ *
+ * @param calculator the digest calculator to be initialised.
+ * @throws CMSException if the MetaData is required and cannot be processed
+ */
+ public void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+ throws CMSException
+ {
+ MetaDataUtil util = new MetaDataUtil(metaData);
+
+ util.initialiseMessageImprintDigestCalculator(calculator);
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/cms/ImprintDigestInvalidException.java b/pkix/src/main/java/org/spongycastle/tsp/cms/ImprintDigestInvalidException.java
new file mode 100644
index 00000000..1dfc2bb3
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/cms/ImprintDigestInvalidException.java
@@ -0,0 +1,21 @@
+package org.spongycastle.tsp.cms;
+
+import org.spongycastle.tsp.TimeStampToken;
+
+public class ImprintDigestInvalidException
+ extends Exception
+{
+ private TimeStampToken token;
+
+ public ImprintDigestInvalidException(String message, TimeStampToken token)
+ {
+ super(message);
+
+ this.token = token;
+ }
+
+ public TimeStampToken getTimeStampToken()
+ {
+ return token;
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/cms/MetaDataUtil.java b/pkix/src/main/java/org/spongycastle/tsp/cms/MetaDataUtil.java
new file mode 100644
index 00000000..f4ad579a
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/cms/MetaDataUtil.java
@@ -0,0 +1,76 @@
+package org.spongycastle.tsp.cms;
+
+import java.io.IOException;
+
+import org.spongycastle.asn1.ASN1Encoding;
+import org.spongycastle.asn1.ASN1String;
+import org.spongycastle.asn1.cms.Attributes;
+import org.spongycastle.asn1.cms.MetaData;
+import org.spongycastle.cms.CMSException;
+import org.spongycastle.operator.DigestCalculator;
+
+class MetaDataUtil
+{
+ private final MetaData metaData;
+
+ MetaDataUtil(MetaData metaData)
+ {
+ this.metaData = metaData;
+ }
+
+ void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+ throws CMSException
+ {
+ if (metaData != null && metaData.isHashProtected())
+ {
+ try
+ {
+ calculator.getOutputStream().write(metaData.getEncoded(ASN1Encoding.DER));
+ }
+ catch (IOException e)
+ {
+ throw new CMSException("unable to initialise calculator from metaData: " + e.getMessage(), e);
+ }
+ }
+ }
+
+ String getFileName()
+ {
+ if (metaData != null)
+ {
+ return convertString(metaData.getFileName());
+ }
+
+ return null;
+ }
+
+ String getMediaType()
+ {
+ if (metaData != null)
+ {
+ return convertString(metaData.getMediaType());
+ }
+
+ return null;
+ }
+
+ Attributes getOtherMetaData()
+ {
+ if (metaData != null)
+ {
+ return metaData.getOtherMetaData();
+ }
+
+ return null;
+ }
+
+ private String convertString(ASN1String s)
+ {
+ if (s != null)
+ {
+ return s.toString();
+ }
+
+ return null;
+ }
+}
diff --git a/pkix/src/main/java/org/spongycastle/tsp/cms/TimeStampDataUtil.java b/pkix/src/main/java/org/spongycastle/tsp/cms/TimeStampDataUtil.java
new file mode 100644
index 00000000..fd1bcc8d
--- /dev/null
+++ b/pkix/src/main/java/org/spongycastle/tsp/cms/TimeStampDataUtil.java
@@ -0,0 +1,256 @@
+package org.spongycastle.tsp.cms;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.spongycastle.asn1.ASN1Encoding;
+import org.spongycastle.asn1.ASN1ObjectIdentifier;
+import org.spongycastle.asn1.cms.AttributeTable;
+import org.spongycastle.asn1.cms.ContentInfo;
+import org.spongycastle.asn1.cms.Evidence;
+import org.spongycastle.asn1.cms.TimeStampAndCRL;
+import org.spongycastle.asn1.cms.TimeStampedData;
+import org.spongycastle.asn1.cms.TimeStampedDataParser;
+import org.spongycastle.asn1.x509.AlgorithmIdentifier;
+import org.spongycastle.cms.CMSException;
+import org.spongycastle.operator.DigestCalculator;
+import org.spongycastle.operator.DigestCalculatorProvider;
+import org.spongycastle.operator.OperatorCreationException;
+import org.spongycastle.tsp.TSPException;
+import org.spongycastle.tsp.TimeStampToken;
+import org.spongycastle.tsp.TimeStampTokenInfo;
+import org.spongycastle.util.Arrays;
+
+class TimeStampDataUtil
+{
+ private final TimeStampAndCRL[] timeStamps;
+
+ private final MetaDataUtil metaDataUtil;
+
+ TimeStampDataUtil(TimeStampedData timeStampedData)
+ {
+ this.metaDataUtil = new MetaDataUtil(timeStampedData.getMetaData());
+
+ Evidence evidence = timeStampedData.getTemporalEvidence();
+ this.timeStamps = evidence.getTstEvidence().toTimeStampAndCRLArray();
+ }
+
+ TimeStampDataUtil(TimeStampedDataParser timeStampedData)
+ throws IOException
+ {
+ this.metaDataUtil = new MetaDataUtil(timeStampedData.getMetaData());
+
+ Evidence evidence = timeStampedData.getTemporalEvidence();
+ this.timeStamps = evidence.getTstEvidence().toTimeStampAndCRLArray();
+ }
+
+ TimeStampToken getTimeStampToken(TimeStampAndCRL timeStampAndCRL)
+ throws CMSException
+ {
+ ContentInfo timeStampToken = timeStampAndCRL.getTimeStampToken();
+
+ try
+ {
+ TimeStampToken token = new TimeStampToken(timeStampToken);
+ return token;
+ }
+ catch (IOException e)
+ {
+ throw new CMSException("unable to parse token data: " + e.getMessage(), e);
+ }
+ catch (TSPException e)
+ {
+ if (e.getCause() instanceof CMSException)
+ {
+ throw (CMSException)e.getCause();
+ }
+
+ throw new CMSException("token data invalid: " + e.getMessage(), e);
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw new CMSException("token data invalid: " + e.getMessage(), e);
+ }
+ }
+
+ void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
+ throws CMSException
+ {
+ metaDataUtil.initialiseMessageImprintDigestCalculator(calculator);
+ }
+
+ DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider)
+ throws OperatorCreationException
+ {
+ TimeStampToken token;
+
+ try
+ {
+ token = this.getTimeStampToken(timeStamps[0]);
+
+ TimeStampTokenInfo info = token.getTimeStampInfo();
+ ASN1ObjectIdentifier algOID = info.getMessageImprintAlgOID();
+
+ DigestCalculator calc = calculatorProvider.get(new AlgorithmIdentifier(algOID));
+
+ initialiseMessageImprintDigestCalculator(calc);
+
+ return calc;
+ }
+ catch (CMSException e)
+ {
+ throw new OperatorCreationException("unable to extract algorithm ID: " + e.getMessage(), e);
+ }
+ }
+
+ TimeStampToken[] getTimeStampTokens()
+ throws CMSException
+ {
+ TimeStampToken[] tokens = new TimeStampToken[timeStamps.length];
+ for (int i = 0; i < timeStamps.length; i++)
+ {
+ tokens[i] = this.getTimeStampToken(timeStamps[i]);
+ }
+
+ return tokens;
+ }
+
+ TimeStampAndCRL[] getTimeStamps()
+ {
+ return timeStamps;
+ }
+
+ byte[] calculateNextHash(DigestCalculator calculator)
+ throws CMSException
+ {
+ TimeStampAndCRL tspToken = timeStamps[timeStamps.length - 1];
+
+ OutputStream out = calculator.getOutputStream();
+
+ try
+ {
+ out.write(tspToken.getEncoded(ASN1Encoding.DER));
+
+ out.close();
+
+ return calculator.getDigest();
+ }
+ catch (IOException e)
+ {
+ throw new CMSException("exception calculating hash: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData.
+ */
+ void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest)
+ throws ImprintDigestInvalidException, CMSException
+ {
+ byte[] currentDigest = dataDigest;
+
+ for (int i = 0; i < timeStamps.length; i++)
+ {
+ try
+ {
+ TimeStampToken token = this.getTimeStampToken(timeStamps[i]);
+ if (i > 0)
+ {
+ TimeStampTokenInfo info = token.getTimeStampInfo();
+ DigestCalculator calculator = calculatorProvider.get(info.getHashAlgorithm());
+
+ calculator.getOutputStream().write(timeStamps[i - 1].getEncoded(ASN1Encoding.DER));
+
+ currentDigest = calculator.getDigest();
+ }
+
+ this.compareDigest(token, currentDigest);
+ }
+ catch (IOException e)
+ {
+ throw new CMSException("exception calculating hash: " + e.getMessage(), e);
+ }
+ catch (OperatorCreationException e)
+ {
+ throw new CMSException("cannot create digest: " + e.getMessage(), e);
+ }
+ }
+ }
+
+ void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken)
+ throws ImprintDigestInvalidException, CMSException
+ {
+ byte[] currentDigest = dataDigest;
+ byte[] encToken;
+
+ try
+ {
+ encToken = timeStampToken.getEncoded();
+ }
+ catch (IOException e)
+ {
+ throw new CMSException("exception encoding timeStampToken: " + e.getMessage(), e);
+ }
+
+ for (int i = 0; i < timeStamps.length; i++)
+ {
+ try
+ {
+ TimeStampToken token = this.getTimeStampToken(timeStamps[i]);
+ if (i > 0)
+ {
+ TimeStampTokenInfo info = token.getTimeStampInfo();
+ DigestCalculator calculator = calculatorProvider.get(info.getHashAlgorithm());
+
+ calculator.getOutputStream().write(timeStamps[i - 1].getEncoded(ASN1Encoding.DER));
+
+ currentDigest = calculator.getDigest();
+ }
+
+ this.compareDigest(token, currentDigest);
+
+ if (Arrays.areEqual(token.getEncoded(), encToken))
+ {
+ return;
+ }
+ }
+ catch (IOException e)
+ {
+ throw new CMSException("exception calculating hash: " + e.getMessage(), e);
+ }
+ catch (OperatorCreationException e)
+ {
+ throw new CMSException("cannot create digest: " + e.getMessage(), e);
+ }
+ }
+
+ throw new ImprintDigestInvalidException("passed in token not associated with timestamps present", timeStampToken);
+ }
+
+ private void compareDigest(TimeStampToken timeStampToken, byte[] digest)
+ throws ImprintDigestInvalidException
+ {
+ TimeStampTokenInfo info = timeStampToken.getTimeStampInfo();
+ byte[] tsrMessageDigest = info.getMessageImprintDigest();
+
+ if (!Arrays.areEqual(digest, tsrMessageDigest))
+ {
+ throw new ImprintDigestInvalidException("hash calculated is different from MessageImprintDigest found in TimeStampToken", timeStampToken);
+ }
+ }
+
+ String getFileName()
+ {
+ return metaDataUtil.getFileName();
+ }
+
+ String getMediaType()
+ {
+ return metaDataUtil.getMediaType();
+ }
+
+ AttributeTable getOtherMetaData()
+ {
+ return new AttributeTable(metaDataUtil.getOtherMetaData());
+ }
+}