summary refs log tree commit diff
path: root/crypto/src/tsp
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2013-06-28 15:26:06 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2013-06-28 15:26:06 +0700
commit44288db4414158ac9b98a507b15e81d0d3c66ca6 (patch)
treeaa5ef88948ebb68ed6c8df81eb5da889641a9b50 /crypto/src/tsp
parentSet up text/binary handling for existing file types (diff)
downloadBouncyCastle.NET-ed25519-44288db4414158ac9b98a507b15e81d0d3c66ca6.tar.xz
Initial import of old CVS repository
Diffstat (limited to 'crypto/src/tsp')
-rw-r--r--crypto/src/tsp/GenTimeAccuracy.cs33
-rw-r--r--crypto/src/tsp/TSPAlgorithms.cs48
-rw-r--r--crypto/src/tsp/TSPException.cs28
-rw-r--r--crypto/src/tsp/TSPUtil.cs202
-rw-r--r--crypto/src/tsp/TSPValidationException.cs44
-rw-r--r--crypto/src/tsp/TimeStampRequest.cs196
-rw-r--r--crypto/src/tsp/TimeStampRequestGenerator.cs139
-rw-r--r--crypto/src/tsp/TimeStampResponse.cs184
-rw-r--r--crypto/src/tsp/TimeStampResponseGenerator.cs210
-rw-r--r--crypto/src/tsp/TimeStampToken.cs305
-rw-r--r--crypto/src/tsp/TimeStampTokenGenerator.cs245
-rw-r--r--crypto/src/tsp/TimeStampTokenInfo.cs107
12 files changed, 1741 insertions, 0 deletions
diff --git a/crypto/src/tsp/GenTimeAccuracy.cs b/crypto/src/tsp/GenTimeAccuracy.cs
new file mode 100644
index 000000000..8a2f29989
--- /dev/null
+++ b/crypto/src/tsp/GenTimeAccuracy.cs
@@ -0,0 +1,33 @@
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Tsp;
+
+namespace Org.BouncyCastle.Tsp
+{
+	public class GenTimeAccuracy
+	{
+		private Accuracy accuracy;
+
+		public GenTimeAccuracy(
+			Accuracy accuracy)
+		{
+			this.accuracy = accuracy;
+		}
+
+		public int Seconds { get { return GetTimeComponent(accuracy.Seconds); } }
+
+		public int Millis { get { return GetTimeComponent(accuracy.Millis); } }
+
+		public int Micros { get { return GetTimeComponent(accuracy.Micros); } }
+
+		private int GetTimeComponent(
+			DerInteger time)
+		{
+			return time == null ? 0 : time.Value.IntValue;
+		}
+
+		public override string ToString()
+		{
+			return Seconds + "." + Millis.ToString("000") + Micros.ToString("000");
+		}
+	}
+}
diff --git a/crypto/src/tsp/TSPAlgorithms.cs b/crypto/src/tsp/TSPAlgorithms.cs
new file mode 100644
index 000000000..e3dfc7916
--- /dev/null
+++ b/crypto/src/tsp/TSPAlgorithms.cs
@@ -0,0 +1,48 @@
+using System.Collections;
+
+using Org.BouncyCastle.Asn1.CryptoPro;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Oiw;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.TeleTrust;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tsp
+{
+	/**
+	 * Recognised hash algorithms for the time stamp protocol.
+	 */
+	public abstract class TspAlgorithms
+	{
+		public static readonly string MD5 = PkcsObjectIdentifiers.MD5.Id;
+
+		public static readonly string Sha1 = OiwObjectIdentifiers.IdSha1.Id;
+
+		public static readonly string Sha224 = NistObjectIdentifiers.IdSha224.Id;
+		public static readonly string Sha256 = NistObjectIdentifiers.IdSha256.Id;
+		public static readonly string Sha384 = NistObjectIdentifiers.IdSha384.Id;
+		public static readonly string Sha512 = NistObjectIdentifiers.IdSha512.Id;
+
+		public static readonly string RipeMD128 = TeleTrusTObjectIdentifiers.RipeMD128.Id;
+		public static readonly string RipeMD160 = TeleTrusTObjectIdentifiers.RipeMD160.Id;
+		public static readonly string RipeMD256 = TeleTrusTObjectIdentifiers.RipeMD256.Id;
+
+		public static readonly string Gost3411 = CryptoProObjectIdentifiers.GostR3411.Id;
+
+		public static readonly IList Allowed;
+
+		static TspAlgorithms()
+		{
+			string[] algs = new string[]
+			{
+				Gost3411, MD5, Sha1, Sha224, Sha256, Sha384, Sha512, RipeMD128, RipeMD160, RipeMD256
+			};
+
+			Allowed = Platform.CreateArrayList();
+			foreach (string alg in algs)
+			{
+				Allowed.Add(alg);
+			}
+		}
+	}
+}
diff --git a/crypto/src/tsp/TSPException.cs b/crypto/src/tsp/TSPException.cs
new file mode 100644
index 000000000..3917e96a7
--- /dev/null
+++ b/crypto/src/tsp/TSPException.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace Org.BouncyCastle.Tsp
+{
+#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT)
+    [Serializable]
+#endif
+    public class TspException
+		: Exception
+	{
+		public TspException()
+		{
+		}
+
+		public TspException(
+			string message)
+			: base(message)
+		{
+		}
+
+		public TspException(
+			string		message,
+			Exception	e)
+			: base(message, e)
+		{
+		}
+	}
+}
diff --git a/crypto/src/tsp/TSPUtil.cs b/crypto/src/tsp/TSPUtil.cs
new file mode 100644
index 000000000..1026914f4
--- /dev/null
+++ b/crypto/src/tsp/TSPUtil.cs
@@ -0,0 +1,202 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.CryptoPro;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Oiw;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.TeleTrust;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Cms;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+using Org.BouncyCastle.X509;
+
+namespace Org.BouncyCastle.Tsp
+{
+	public class TspUtil
+	{
+		private static ISet EmptySet = CollectionUtilities.ReadOnly(new HashSet());
+		private static IList EmptyList = CollectionUtilities.ReadOnly(Platform.CreateArrayList());
+
+		private static readonly IDictionary digestLengths = Platform.CreateHashtable();
+        private static readonly IDictionary digestNames = Platform.CreateHashtable();
+
+		static TspUtil()
+		{
+			digestLengths.Add(PkcsObjectIdentifiers.MD5.Id, 16);
+			digestLengths.Add(OiwObjectIdentifiers.IdSha1.Id, 20);
+			digestLengths.Add(NistObjectIdentifiers.IdSha224.Id, 28);
+			digestLengths.Add(NistObjectIdentifiers.IdSha256.Id, 32);
+			digestLengths.Add(NistObjectIdentifiers.IdSha384.Id, 48);
+			digestLengths.Add(NistObjectIdentifiers.IdSha512.Id, 64);
+			digestLengths.Add(TeleTrusTObjectIdentifiers.RipeMD128.Id, 16);
+			digestLengths.Add(TeleTrusTObjectIdentifiers.RipeMD160.Id, 20);
+			digestLengths.Add(TeleTrusTObjectIdentifiers.RipeMD256.Id, 32);
+			digestLengths.Add(CryptoProObjectIdentifiers.GostR3411.Id, 32);
+
+			digestNames.Add(PkcsObjectIdentifiers.MD5.Id, "MD5");
+			digestNames.Add(OiwObjectIdentifiers.IdSha1.Id, "SHA1");
+			digestNames.Add(NistObjectIdentifiers.IdSha224.Id, "SHA224");
+			digestNames.Add(NistObjectIdentifiers.IdSha256.Id, "SHA256");
+			digestNames.Add(NistObjectIdentifiers.IdSha384.Id, "SHA384");
+			digestNames.Add(NistObjectIdentifiers.IdSha512.Id, "SHA512");
+			digestNames.Add(PkcsObjectIdentifiers.Sha1WithRsaEncryption.Id, "SHA1");
+			digestNames.Add(PkcsObjectIdentifiers.Sha224WithRsaEncryption.Id, "SHA224");
+			digestNames.Add(PkcsObjectIdentifiers.Sha256WithRsaEncryption.Id, "SHA256");
+			digestNames.Add(PkcsObjectIdentifiers.Sha384WithRsaEncryption.Id, "SHA384");
+			digestNames.Add(PkcsObjectIdentifiers.Sha512WithRsaEncryption.Id, "SHA512");
+			digestNames.Add(TeleTrusTObjectIdentifiers.RipeMD128.Id, "RIPEMD128");
+			digestNames.Add(TeleTrusTObjectIdentifiers.RipeMD160.Id, "RIPEMD160");
+			digestNames.Add(TeleTrusTObjectIdentifiers.RipeMD256.Id, "RIPEMD256");
+			digestNames.Add(CryptoProObjectIdentifiers.GostR3411.Id, "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
+	     * @return a collection of TimeStampToken objects
+	     * @throws TSPValidationException
+	     */
+		public static ICollection GetSignatureTimestamps(
+			SignerInformation signerInfo)
+		{
+			IList timestamps = Platform.CreateArrayList();
+
+			Asn1.Cms.AttributeTable unsignedAttrs = signerInfo.UnsignedAttributes;
+			if (unsignedAttrs != null)
+			{
+				foreach (Asn1.Cms.Attribute tsAttr in unsignedAttrs.GetAll(
+					PkcsObjectIdentifiers.IdAASignatureTimeStampToken))
+				{
+					foreach (Asn1Encodable asn1 in tsAttr.AttrValues)
+					{
+						try
+						{
+							Asn1.Cms.ContentInfo contentInfo = Asn1.Cms.ContentInfo.GetInstance(
+								asn1.ToAsn1Object());
+							TimeStampToken timeStampToken = new TimeStampToken(contentInfo);
+							TimeStampTokenInfo tstInfo = timeStampToken.TimeStampInfo;
+
+							byte[] expectedDigest = DigestUtilities.CalculateDigest(
+								GetDigestAlgName(tstInfo.MessageImprintAlgOid),
+							    signerInfo.GetSignature());
+
+							if (!Arrays.ConstantTimeAreEqual(expectedDigest, tstInfo.GetMessageImprintDigest()))
+								throw new TspValidationException("Incorrect digest in message imprint");
+
+							timestamps.Add(timeStampToken);
+						}
+						catch (SecurityUtilityException)
+						{
+							throw new TspValidationException("Unknown hash algorithm specified in timestamp");
+						}
+						catch (Exception)
+						{
+							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 certicate fails on one of the check points.
+		 */
+		public static void ValidateCertificate(
+			X509Certificate cert)
+		{
+			if (cert.Version != 3)
+				throw new ArgumentException("Certificate must have an ExtendedKeyUsage extension.");
+
+			Asn1OctetString ext = cert.GetExtensionValue(X509Extensions.ExtendedKeyUsage);
+			if (ext == null)
+				throw new TspValidationException("Certificate must have an ExtendedKeyUsage extension.");
+
+			if (!cert.GetCriticalExtensionOids().Contains(X509Extensions.ExtendedKeyUsage.Id))
+				throw new TspValidationException("Certificate must have an ExtendedKeyUsage extension marked as critical.");
+
+			try
+			{
+				ExtendedKeyUsage extKey = ExtendedKeyUsage.GetInstance(
+					Asn1Object.FromByteArray(ext.GetOctets()));
+
+				if (!extKey.HasKeyPurposeId(KeyPurposeID.IdKPTimeStamping) || extKey.Count != 1)
+					throw new TspValidationException("ExtendedKeyUsage not solely time stamping.");
+			}
+			catch (IOException)
+			{
+				throw new TspValidationException("cannot process ExtendedKeyUsage extension");
+			}
+		}
+
+		/// <summary>
+		/// Return the digest algorithm using one of the standard JCA string
+		/// representations rather than the algorithm identifier (if possible).
+		/// </summary>
+		internal static string GetDigestAlgName(
+			string digestAlgOID)
+		{
+			string digestName = (string) digestNames[digestAlgOID];
+
+			return digestName != null ? digestName : digestAlgOID;
+		}
+
+		internal static int GetDigestLength(
+			string digestAlgOID)
+		{
+			if (!digestLengths.Contains(digestAlgOID))
+				throw new TspException("digest algorithm cannot be found.");
+
+			return (int)digestLengths[digestAlgOID];
+		}
+
+		internal static IDigest CreateDigestInstance(
+			String digestAlgOID)
+		{
+	        string digestName = GetDigestAlgName(digestAlgOID);
+
+			return DigestUtilities.GetDigest(digestName);
+		}
+
+		internal static ISet GetCriticalExtensionOids(X509Extensions extensions)
+		{
+			if (extensions == null)
+				return EmptySet;
+
+			return CollectionUtilities.ReadOnly(new HashSet(extensions.GetCriticalExtensionOids()));
+		}
+
+		internal static ISet GetNonCriticalExtensionOids(X509Extensions extensions)
+		{
+			if (extensions == null)
+				return EmptySet;
+
+			// TODO: should probably produce a set that imposes correct ordering
+			return CollectionUtilities.ReadOnly(new HashSet(extensions.GetNonCriticalExtensionOids()));
+		}
+		
+		internal static IList GetExtensionOids(X509Extensions extensions)
+		{
+			if (extensions == null)
+				return EmptyList;
+
+			return CollectionUtilities.ReadOnly(Platform.CreateArrayList(extensions.GetExtensionOids()));
+		}
+	}
+}
diff --git a/crypto/src/tsp/TSPValidationException.cs b/crypto/src/tsp/TSPValidationException.cs
new file mode 100644
index 000000000..8ef2ec6cf
--- /dev/null
+++ b/crypto/src/tsp/TSPValidationException.cs
@@ -0,0 +1,44 @@
+using System;
+
+namespace Org.BouncyCastle.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.</p>
+	 */
+#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT)
+    [Serializable]
+#endif
+    public class TspValidationException
+		: TspException
+	{
+		private int failureCode;
+
+		public TspValidationException(
+			string message)
+			: base(message)
+		{
+			this.failureCode = -1;
+		}
+
+		public TspValidationException(
+			string	message,
+			int		failureCode)
+			: base(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 FailureCode
+		{
+			get { return failureCode; }
+		}
+	}
+}
diff --git a/crypto/src/tsp/TimeStampRequest.cs b/crypto/src/tsp/TimeStampRequest.cs
new file mode 100644
index 000000000..6b9699379
--- /dev/null
+++ b/crypto/src/tsp/TimeStampRequest.cs
@@ -0,0 +1,196 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cmp;
+using Org.BouncyCastle.Asn1.Tsp;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.X509;
+
+namespace Org.BouncyCastle.Tsp
+{
+	/**
+	 * Base class for an RFC 3161 Time Stamp Request.
+	 */
+	public class TimeStampRequest
+		: X509ExtensionBase
+	{
+		private TimeStampReq req;
+		private X509Extensions extensions;
+
+		public TimeStampRequest(
+			TimeStampReq req)
+		{
+			this.req = req;
+			this.extensions = req.Extensions;
+		}
+
+		/**
+		* 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)
+			: this(new Asn1InputStream(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(
+			Stream input)
+			: this(new Asn1InputStream(input))
+		{
+		}
+
+		private TimeStampRequest(
+			Asn1InputStream str)
+		{
+			try
+			{
+				this.req = TimeStampReq.GetInstance(str.ReadObject());
+			}
+			catch (InvalidCastException e)
+			{
+				throw new IOException("malformed request: " + e);
+			}
+			catch (ArgumentException e)
+			{
+				throw new IOException("malformed request: " + e);
+			}
+		}
+
+		public int Version
+		{
+			get { return req.Version.Value.IntValue; }
+		}
+
+		public string MessageImprintAlgOid
+		{
+			get { return req.MessageImprint.HashAlgorithm.ObjectID.Id; }
+		}
+
+		public byte[] GetMessageImprintDigest()
+		{
+			return req.MessageImprint.GetHashedMessage();
+		}
+
+		public string ReqPolicy
+		{
+			get
+			{
+				return req.ReqPolicy == null
+					?	null
+					:	req.ReqPolicy.Id;
+			}
+		}
+
+		public BigInteger Nonce
+		{
+			get
+			{
+				return req.Nonce == null
+					?	null
+					:	req.Nonce.Value;
+			}
+		}
+
+		public bool CertReq
+		{
+			get
+			{
+				return req.CertReq == null
+					?	false
+					:	req.CertReq.IsTrue;
+			}
+		}
+
+		/**
+		* 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 string OIDS giving accepted algorithms.
+		* @param policies if non-null a set of policies we are willing to sign under.
+		* @param extensions if non-null a set of extensions we are willing to accept.
+		* @throws TspException if the request is invalid, or processing fails.
+		*/
+		public void Validate(
+			IList algorithms,
+			IList policies,
+			IList extensions)
+		{
+			if (!algorithms.Contains(this.MessageImprintAlgOid))
+			{
+				throw new TspValidationException("request contains unknown algorithm.", PkiFailureInfo.BadAlg);
+			}
+
+			if (policies != null && this.ReqPolicy != null && !policies.Contains(this.ReqPolicy))
+			{
+				throw new TspValidationException("request contains unknown policy.", PkiFailureInfo.UnacceptedPolicy);
+			}
+
+			if (this.Extensions != null && extensions != null)
+			{
+				foreach (DerObjectIdentifier oid in this.Extensions.ExtensionOids)
+				{
+					if (!extensions.Contains(oid.Id))
+					{
+						throw new TspValidationException("request contains unknown extension.",
+							PkiFailureInfo.UnacceptedExtension);
+					}
+				}
+			}
+
+			int digestLength = TspUtil.GetDigestLength(this.MessageImprintAlgOid);
+
+			if (digestLength != this.GetMessageImprintDigest().Length)
+			{
+				throw new TspValidationException("imprint digest the wrong length.",
+					PkiFailureInfo.BadDataFormat);
+			}
+		}
+
+		/**
+		 * return the ASN.1 encoded representation of this object.
+		 */
+		public byte[] GetEncoded()
+		{
+			return req.GetEncoded();
+		}
+
+		internal X509Extensions Extensions
+		{
+			get { return req.Extensions; }
+		}
+		
+		public virtual bool HasExtensions
+		{
+			get { return extensions != null; }
+		}
+
+		public virtual X509Extension GetExtension(DerObjectIdentifier oid)
+		{
+			return extensions == null ? null : extensions.GetExtension(oid);
+		}
+
+		public virtual IList GetExtensionOids()
+		{
+			return TspUtil.GetExtensionOids(extensions);
+		}
+
+		protected override X509Extensions GetX509Extensions()
+		{
+			return Extensions;
+		}
+	}
+}
diff --git a/crypto/src/tsp/TimeStampRequestGenerator.cs b/crypto/src/tsp/TimeStampRequestGenerator.cs
new file mode 100644
index 000000000..2c698e476
--- /dev/null
+++ b/crypto/src/tsp/TimeStampRequestGenerator.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Tsp;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Math;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tsp
+{
+	/**
+	 * Generator for RFC 3161 Time Stamp Request objects.
+	 */
+	public class TimeStampRequestGenerator
+	{
+		private DerObjectIdentifier reqPolicy;
+
+		private DerBoolean certReq;
+
+		private IDictionary extensions = Platform.CreateHashtable();
+        private IList       extOrdering = Platform.CreateArrayList();
+
+		public void SetReqPolicy(
+			string reqPolicy)
+		{
+			this.reqPolicy = new DerObjectIdentifier(reqPolicy);
+		}
+
+		public void SetCertReq(
+			bool certReq)
+		{
+			this.certReq = DerBoolean.GetInstance(certReq);
+		}
+
+		/**
+		 * add a given extension field for the standard extensions tag (tag 3)
+		 * @throws IOException
+		 */
+		[Obsolete("Use method taking DerObjectIdentifier")]
+		public void AddExtension(
+			string			oid,
+			bool			critical,
+			Asn1Encodable	value)
+		{
+			this.AddExtension(oid, critical, value.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.
+		*/
+		[Obsolete("Use method taking DerObjectIdentifier")]
+		public void AddExtension(
+			string	oid,
+			bool	critical,
+			byte[]	value)
+		{
+			DerObjectIdentifier derOid = new DerObjectIdentifier(oid);
+			extensions[derOid] = new X509Extension(critical, new DerOctetString(value));
+			extOrdering.Add(derOid);
+		}
+
+		/**
+		 * add a given extension field for the standard extensions tag (tag 3)
+		 * @throws IOException
+		 */
+		public virtual void AddExtension(
+			DerObjectIdentifier	oid,
+			bool				critical,
+			Asn1Encodable 		extValue)
+		{
+			this.AddExtension(oid, critical, extValue.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.
+		 */
+		public virtual void AddExtension(
+			DerObjectIdentifier	oid,
+			bool				critical,
+			byte[]				extValue)
+		{
+			extensions.Add(oid, new X509Extension(critical, new DerOctetString(extValue)));
+			extOrdering.Add(oid);
+		}
+
+		public TimeStampRequest Generate(
+			string	digestAlgorithm,
+			byte[]	digest)
+		{
+			return this.Generate(digestAlgorithm, digest, null);
+		}
+
+		public TimeStampRequest Generate(
+			string		digestAlgorithmOid,
+			byte[]		digest,
+			BigInteger	nonce)
+		{
+			if (digestAlgorithmOid == null)
+			{
+				throw new ArgumentException("No digest algorithm specified");
+			}
+
+			DerObjectIdentifier digestAlgOid = new DerObjectIdentifier(digestAlgorithmOid);
+
+			AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOid, DerNull.Instance);
+			MessageImprint messageImprint = new MessageImprint(algID, digest);
+
+			X509Extensions  ext = null;
+
+			if (extOrdering.Count != 0)
+			{
+				ext = new X509Extensions(extOrdering, extensions);
+			}
+
+			DerInteger derNonce = nonce == null
+				?	null
+				:	new DerInteger(nonce);
+
+			return new TimeStampRequest(
+				new TimeStampReq(messageImprint, reqPolicy, derNonce, certReq, ext));
+		}
+
+		public virtual TimeStampRequest Generate(DerObjectIdentifier digestAlgorithm, byte[] digest)
+		{
+			return Generate(digestAlgorithm.Id, digest);
+		}
+
+		public virtual TimeStampRequest Generate(DerObjectIdentifier digestAlgorithm, byte[] digest, BigInteger nonce)
+		{
+			return Generate(digestAlgorithm.Id, digest, nonce);
+		}
+	}
+}
diff --git a/crypto/src/tsp/TimeStampResponse.cs b/crypto/src/tsp/TimeStampResponse.cs
new file mode 100644
index 000000000..069521111
--- /dev/null
+++ b/crypto/src/tsp/TimeStampResponse.cs
@@ -0,0 +1,184 @@
+using System;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cmp;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.Tsp;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tsp
+{
+	/**
+	 * Base class for an RFC 3161 Time Stamp Response object.
+	 */
+	public class TimeStampResponse
+	{
+		private TimeStampResp	resp;
+		private TimeStampToken	timeStampToken;
+
+		public TimeStampResponse(
+			TimeStampResp resp)
+		{
+			this.resp = resp;
+
+			if (resp.TimeStampToken != null)
+			{
+				timeStampToken = new TimeStampToken(resp.TimeStampToken);
+			}
+		}
+
+		/**
+		* 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)
+			: this(readTimeStampResp(new Asn1InputStream(resp)))
+		{
+		}
+
+		/**
+		 * Create a TimeStampResponse from an input stream containing an ASN.1 encoding.
+		 *
+		 * @param input 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(
+			Stream input)
+			: this(readTimeStampResp(new Asn1InputStream(input)))
+		{
+		}
+
+		private static TimeStampResp readTimeStampResp(
+			Asn1InputStream input)
+		{
+			try
+			{
+				return TimeStampResp.GetInstance(input.ReadObject());
+			}
+			catch (ArgumentException e)
+			{
+				throw new TspException("malformed timestamp response: " + e, e);
+			}
+			catch (InvalidCastException e)
+			{
+				throw new TspException("malformed timestamp response: " + e, e);
+			}
+		}
+
+		public int Status
+		{
+			get { return resp.Status.Status.IntValue; }
+		}
+
+		public string GetStatusString()
+		{
+			if (resp.Status.StatusString == null)
+			{
+				return null;
+			}
+
+			StringBuilder statusStringBuf = new StringBuilder();
+			PkiFreeText text = resp.Status.StatusString;
+			for (int i = 0; i != text.Count; i++)
+			{
+				statusStringBuf.Append(text[i].GetString());
+			}
+
+			return statusStringBuf.ToString();
+		}
+
+		public PkiFailureInfo GetFailInfo()
+		{
+			if (resp.Status.FailInfo == null)
+			{
+				return null;
+			}
+
+			return new PkiFailureInfo(resp.Status.FailInfo);
+		}
+
+		public TimeStampToken TimeStampToken
+		{
+			get { 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)
+		{
+			TimeStampToken tok = this.TimeStampToken;
+
+			if (tok != null)
+			{
+				TimeStampTokenInfo tstInfo = tok.TimeStampInfo;
+
+				if (request.Nonce != null && !request.Nonce.Equals(tstInfo.Nonce))
+				{
+					throw new TspValidationException("response contains wrong nonce value.");
+				}
+
+				if (this.Status != (int) PkiStatus.Granted && this.Status != (int) PkiStatus.GrantedWithMods)
+				{
+					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.MessageImprintAlgOid.Equals(request.MessageImprintAlgOid))
+				{
+					throw new TspValidationException("response for different message imprint algorithm.");
+				}
+
+				Asn1.Cms.Attribute scV1 = tok.SignedAttributes[PkcsObjectIdentifiers.IdAASigningCertificate];
+				Asn1.Cms.Attribute scV2 = tok.SignedAttributes[PkcsObjectIdentifiers.IdAASigningCertificateV2];
+
+				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.ReqPolicy != null && !request.ReqPolicy.Equals(tstInfo.Policy))
+				{
+					throw new TspValidationException("TSA policy wrong for request.");
+				}
+			}
+			else if (this.Status == (int) PkiStatus.Granted || this.Status == (int) PkiStatus.GrantedWithMods)
+			{
+				throw new TspValidationException("no time stamp token found and one expected.");
+			}
+		}
+
+		/**
+		 * return the ASN.1 encoded representation of this object.
+		 */
+		public byte[] GetEncoded()
+		{
+			return resp.GetEncoded();
+		}
+	}
+}
diff --git a/crypto/src/tsp/TimeStampResponseGenerator.cs b/crypto/src/tsp/TimeStampResponseGenerator.cs
new file mode 100644
index 000000000..8d798de67
--- /dev/null
+++ b/crypto/src/tsp/TimeStampResponseGenerator.cs
@@ -0,0 +1,210 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cmp;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.Tsp;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Tsp
+{
+    /**
+     * Generator for RFC 3161 Time Stamp Responses.
+     */
+    public class TimeStampResponseGenerator
+    {
+        private PkiStatus status;
+
+        private Asn1EncodableVector statusStrings;
+
+        private int failInfo;
+        private TimeStampTokenGenerator tokenGenerator;
+        private IList acceptedAlgorithms;
+        private IList acceptedPolicies;
+        private IList acceptedExtensions;
+
+        public TimeStampResponseGenerator(
+            TimeStampTokenGenerator tokenGenerator,
+            IList acceptedAlgorithms)
+            : this(tokenGenerator, acceptedAlgorithms, null, null)
+        {
+        }
+
+        public TimeStampResponseGenerator(
+            TimeStampTokenGenerator tokenGenerator,
+            IList acceptedAlgorithms,
+            IList acceptedPolicy)
+            : this(tokenGenerator, acceptedAlgorithms, acceptedPolicy, null)
+        {
+        }
+
+        public TimeStampResponseGenerator(
+            TimeStampTokenGenerator tokenGenerator,
+            IList acceptedAlgorithms,
+            IList acceptedPolicies,
+            IList acceptedExtensions)
+        {
+            this.tokenGenerator = tokenGenerator;
+            this.acceptedAlgorithms = acceptedAlgorithms;
+            this.acceptedPolicies = acceptedPolicies;
+            this.acceptedExtensions = acceptedExtensions;
+
+            statusStrings = new Asn1EncodableVector();
+        }
+
+        private void AddStatusString(string statusString)
+        {
+            statusStrings.Add(new DerUtf8String(statusString));
+        }
+
+        private void SetFailInfoField(int field)
+        {
+            failInfo |= field;
+        }
+
+        private PkiStatusInfo GetPkiStatusInfo()
+        {
+            Asn1EncodableVector v = new Asn1EncodableVector(
+                new DerInteger((int)status));
+
+            if (statusStrings.Count > 0)
+            {
+                v.Add(new PkiFreeText(new DerSequence(statusStrings)));
+            }
+
+            if (failInfo != 0)
+            {
+                v.Add(new FailInfo(failInfo));
+            }
+
+            return new PkiStatusInfo(new DerSequence(v));
+        }
+
+        public TimeStampResponse Generate(
+            TimeStampRequest request,
+            BigInteger serialNumber,
+            DateTime genTime)
+        {
+            return Generate(request, serialNumber, new DateTimeObject(genTime));
+        }
+
+        /**
+         * Return an appropriate TimeStampResponse.
+         * <p>
+         * If genTime is null a timeNotAvailable error response will be returned.
+         *
+         * @param request the request this response is for.
+         * @param serialNumber serial number for the response token.
+         * @param genTime generation time for the response token.
+         * @param provider provider to use for signature calculation.
+         * @return
+         * @throws NoSuchAlgorithmException
+         * @throws NoSuchProviderException
+         * @throws TSPException
+         * </p>
+         */
+        public TimeStampResponse Generate(
+            TimeStampRequest request,
+            BigInteger serialNumber,
+            DateTimeObject genTime)
+        {
+            TimeStampResp resp;
+
+            try
+            {
+                if (genTime == null)
+                    throw new TspValidationException("The time source is not available.",
+                        PkiFailureInfo.TimeNotAvailable);
+
+                request.Validate(acceptedAlgorithms, acceptedPolicies, acceptedExtensions);
+
+                this.status = PkiStatus.Granted;
+                this.AddStatusString("Operation Okay");
+
+                PkiStatusInfo pkiStatusInfo = GetPkiStatusInfo();
+
+                ContentInfo tstTokenContentInfo;
+                try
+                {
+                    TimeStampToken token = tokenGenerator.Generate(request, serialNumber, genTime.Value);
+                    byte[] encoded = token.ToCmsSignedData().GetEncoded();
+
+                    tstTokenContentInfo = ContentInfo.GetInstance(Asn1Object.FromByteArray(encoded));
+                }
+                catch (IOException e)
+                {
+                    throw new TspException("Timestamp token received cannot be converted to ContentInfo", e);
+                }
+
+                resp = new TimeStampResp(pkiStatusInfo, tstTokenContentInfo);
+            }
+            catch (TspValidationException e)
+            {
+                status = PkiStatus.Rejection;
+
+                this.SetFailInfoField(e.FailureCode);
+                this.AddStatusString(e.Message);
+
+                PkiStatusInfo pkiStatusInfo = GetPkiStatusInfo();
+
+                resp = new TimeStampResp(pkiStatusInfo, null);
+            }
+
+            try
+            {
+                return new TimeStampResponse(resp);
+            }
+            catch (IOException e)
+            {
+                throw new TspException("created badly formatted response!", e);
+            }
+        }
+
+        class FailInfo
+            : DerBitString
+        {
+            internal FailInfo(
+                int failInfoValue)
+                : base(GetBytes(failInfoValue), GetPadBits(failInfoValue))
+            {
+            }
+        }
+
+        /**
+         * Generate a 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(PkiStatus status, int failInfoField, string statusString)
+        {
+            this.status = status;
+
+            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!", e);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tsp/TimeStampToken.cs b/crypto/src/tsp/TimeStampToken.cs
new file mode 100644
index 000000000..51a9592dc
--- /dev/null
+++ b/crypto/src/tsp/TimeStampToken.cs
@@ -0,0 +1,305 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Ess;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Oiw;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.Tsp;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Cms;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Security.Certificates;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.X509;
+using Org.BouncyCastle.X509.Store;
+
+namespace Org.BouncyCastle.Tsp
+{
+	public class TimeStampToken
+	{
+		private readonly CmsSignedData		tsToken;
+		private readonly SignerInformation	tsaSignerInfo;
+//		private readonly DateTime			genTime;
+		private readonly TimeStampTokenInfo	tstInfo;
+		private readonly CertID				certID;
+
+		public TimeStampToken(
+			Asn1.Cms.ContentInfo contentInfo)
+			: this(new CmsSignedData(contentInfo))
+		{
+		}
+
+		public TimeStampToken(
+			CmsSignedData signedData)
+		{
+			this.tsToken = signedData;
+
+			if (!this.tsToken.SignedContentType.Equals(PkcsObjectIdentifiers.IdCTTstInfo))
+			{
+				throw new TspValidationException("ContentInfo object not for a time stamp.");
+			}
+
+			ICollection signers = tsToken.GetSignerInfos().GetSigners();
+
+			if (signers.Count != 1)
+			{
+				throw new ArgumentException("Time-stamp token signed by "
+					+ signers.Count
+					+ " signers, but it must contain just the TSA signature.");
+			}
+
+
+			IEnumerator signerEnum = signers.GetEnumerator();
+
+			signerEnum.MoveNext();
+			tsaSignerInfo = (SignerInformation) signerEnum.Current;
+
+			try
+			{
+				CmsProcessable content = tsToken.SignedContent;
+				MemoryStream bOut = new MemoryStream();
+
+				content.Write(bOut);
+
+				this.tstInfo = new TimeStampTokenInfo(
+					TstInfo.GetInstance(
+						Asn1Object.FromByteArray(bOut.ToArray())));
+
+				Asn1.Cms.Attribute attr = tsaSignerInfo.SignedAttributes[
+					PkcsObjectIdentifiers.IdAASigningCertificate];
+
+//				if (attr == null)
+//				{
+//					throw new TspValidationException(
+//						"no signing certificate attribute found, time stamp invalid.");
+//				}
+//
+//				SigningCertificate signCert = SigningCertificate.GetInstance(
+//					attr.AttrValues[0]);
+//
+//				this.certID = EssCertID.GetInstance(signCert.GetCerts()[0]);
+
+				if (attr != null)
+				{
+					SigningCertificate signCert = SigningCertificate.GetInstance(attr.AttrValues[0]);
+
+					this.certID = new CertID(EssCertID.GetInstance(signCert.GetCerts()[0]));
+				}
+				else
+				{
+					attr = tsaSignerInfo.SignedAttributes[PkcsObjectIdentifiers.IdAASigningCertificateV2];
+
+					if (attr == null)
+						throw new TspValidationException("no signing certificate attribute found, time stamp invalid.");
+
+					SigningCertificateV2 signCertV2 = SigningCertificateV2.GetInstance(attr.AttrValues[0]);
+
+					this.certID = new CertID(EssCertIDv2.GetInstance(signCertV2.GetCerts()[0]));
+				}
+			}
+			catch (CmsException e)
+			{
+				throw new TspException(e.Message, e.InnerException);
+			}
+		}
+
+		public TimeStampTokenInfo TimeStampInfo
+		{
+			get { return tstInfo; }
+		}
+
+		public SignerID SignerID
+		{
+			get { return tsaSignerInfo.SignerID; }
+		}
+
+		public Asn1.Cms.AttributeTable SignedAttributes
+		{
+			get { return tsaSignerInfo.SignedAttributes; }
+		}
+
+		public Asn1.Cms.AttributeTable UnsignedAttributes
+		{
+			get { return tsaSignerInfo.UnsignedAttributes; }
+		}
+
+		public IX509Store GetCertificates(
+			string type)
+		{
+			return tsToken.GetCertificates(type);
+		}
+
+		public IX509Store GetCrls(
+			string type)
+		{
+			return tsToken.GetCrls(type);
+		}
+
+	    public IX509Store GetAttributeCertificates(
+			string type)
+	    {
+	        return tsToken.GetAttributeCertificates(type);
+	    }
+
+		/**
+		 * 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.IdKPTimeStamping 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>
+		 */
+		public void Validate(
+			X509Certificate cert)
+		{
+			try
+			{
+				byte[] hash = DigestUtilities.CalculateDigest(
+					certID.GetHashAlgorithmName(), cert.GetEncoded());
+
+				if (!Arrays.ConstantTimeAreEqual(certID.GetCertHash(), hash))
+				{
+					throw new TspValidationException("certificate hash does not match certID hash.");
+				}
+
+				if (certID.IssuerSerial != null)
+				{
+					if (!certID.IssuerSerial.Serial.Value.Equals(cert.SerialNumber))
+					{
+						throw new TspValidationException("certificate serial number does not match certID for signature.");
+					}
+
+					GeneralName[] names = certID.IssuerSerial.Issuer.GetNames();
+					X509Name principal = PrincipalUtilities.GetIssuerX509Principal(cert);
+					bool found = false;
+
+					for (int i = 0; i != names.Length; i++)
+					{
+						if (names[i].TagNo == 4
+							&& X509Name.GetInstance(names[i].Name).Equivalent(principal))
+						{
+							found = true;
+							break;
+						}
+					}
+
+					if (!found)
+					{
+						throw new TspValidationException("certificate name does not match certID for signature. ");
+					}
+				}
+
+				TspUtil.ValidateCertificate(cert);
+
+				cert.CheckValidity(tstInfo.GenTime);
+
+				if (!tsaSignerInfo.Verify(cert))
+				{
+					throw new TspValidationException("signature not created by certificate.");
+				}
+			}
+			catch (CmsException e)
+			{
+				if (e.InnerException != null)
+				{
+					throw new TspException(e.Message, e.InnerException);
+				}
+
+				throw new TspException("CMS exception: " + e, e);
+			}
+			catch (CertificateEncodingException e)
+			{
+				throw new TspException("problem processing certificate: " + e, e);
+			}
+			catch (SecurityUtilityException e)
+			{
+				throw new TspException("cannot find algorithm: " + e.Message, 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()
+		{
+			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;
+
+			internal CertID(EssCertID certID)
+			{
+				this.certID = certID;
+				this.certIDv2 = null;
+			}
+
+			internal CertID(EssCertIDv2 certID)
+			{
+				this.certIDv2 = certID;
+				this.certID = null;
+			}
+
+			public string GetHashAlgorithmName()
+			{
+				if (certID != null)
+					return "SHA-1";
+
+				if (NistObjectIdentifiers.IdSha256.Equals(certIDv2.HashAlgorithm.ObjectID))
+					return "SHA-256";
+
+				return certIDv2.HashAlgorithm.ObjectID.Id;
+			}
+
+			public AlgorithmIdentifier GetHashAlgorithm()
+			{
+				return (certID != null)
+					?	new AlgorithmIdentifier(OiwObjectIdentifiers.IdSha1)
+					:	certIDv2.HashAlgorithm;
+			}
+
+			public byte[] GetCertHash()
+			{
+				return certID != null
+					?	certID.GetCertHash()
+					:	certIDv2.GetCertHash();
+			}
+
+			public IssuerSerial IssuerSerial
+			{
+				get
+				{
+					return certID != null
+						?	certID.IssuerSerial
+						:	certIDv2.IssuerSerial;
+				}
+			}
+		}
+	}
+}
diff --git a/crypto/src/tsp/TimeStampTokenGenerator.cs b/crypto/src/tsp/TimeStampTokenGenerator.cs
new file mode 100644
index 000000000..07eddd4b9
--- /dev/null
+++ b/crypto/src/tsp/TimeStampTokenGenerator.cs
@@ -0,0 +1,245 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Ess;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.Tsp;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Cms;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Security.Certificates;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.X509;
+using Org.BouncyCastle.X509.Store;
+
+namespace Org.BouncyCastle.Tsp
+{
+	public class TimeStampTokenGenerator
+	{
+		private int accuracySeconds = -1;
+		private int accuracyMillis = -1;
+		private int accuracyMicros = -1;
+		private bool ordering = false;
+		private GeneralName tsa = null;
+		private string tsaPolicyOID;
+
+		private AsymmetricKeyParameter	key;
+		private X509Certificate			cert;
+		private string					digestOID;
+		private Asn1.Cms.AttributeTable	signedAttr;
+		private Asn1.Cms.AttributeTable	unsignedAttr;
+		private IX509Store				x509Certs;
+		private IX509Store				x509Crls;
+
+		/**
+		 * basic creation - only the default attributes will be included here.
+		 */
+		public TimeStampTokenGenerator(
+			AsymmetricKeyParameter	key,
+			X509Certificate			cert,
+			string					digestOID,
+			string					tsaPolicyOID)
+			: this(key, cert, digestOID, tsaPolicyOID, null, null)
+		{
+		}
+
+		/**
+		 * create with a signer with extra signed/unsigned attributes.
+		 */
+		public TimeStampTokenGenerator(
+			AsymmetricKeyParameter	key,
+			X509Certificate			cert,
+			string					digestOID,
+			string					tsaPolicyOID,
+			Asn1.Cms.AttributeTable	signedAttr,
+			Asn1.Cms.AttributeTable	unsignedAttr)
+		{
+			this.key = key;
+			this.cert = cert;
+			this.digestOID = digestOID;
+			this.tsaPolicyOID = tsaPolicyOID;
+			this.unsignedAttr = unsignedAttr;
+
+			TspUtil.ValidateCertificate(cert);
+
+			//
+			// Add the ESSCertID attribute
+			//
+			IDictionary signedAttrs;
+			if (signedAttr != null)
+			{
+				signedAttrs = signedAttr.ToDictionary();
+			}
+			else
+			{
+				signedAttrs = Platform.CreateHashtable();
+			}
+
+			try
+			{
+				byte[] hash = DigestUtilities.CalculateDigest("SHA-1", cert.GetEncoded());
+
+				EssCertID essCertid = new EssCertID(hash);
+
+				Asn1.Cms.Attribute attr = new Asn1.Cms.Attribute(
+					PkcsObjectIdentifiers.IdAASigningCertificate,
+					new DerSet(new SigningCertificate(essCertid)));
+
+				signedAttrs[attr.AttrType] = attr;
+			}
+			catch (CertificateEncodingException e)
+			{
+				throw new TspException("Exception processing certificate.", e);
+			}
+			catch (SecurityUtilityException e)
+			{
+				throw new TspException("Can't find a SHA-1 implementation.", e);
+			}
+
+			this.signedAttr = new Asn1.Cms.AttributeTable(signedAttrs);
+		}
+
+		public void SetCertificates(
+			IX509Store certificates)
+		{
+			this.x509Certs = certificates;
+		}
+
+		public void SetCrls(
+			IX509Store crls)
+		{
+			this.x509Crls = crls;
+		}
+
+		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(
+			bool ordering)
+		{
+			this.ordering = ordering;
+		}
+
+		public void SetTsa(
+			GeneralName tsa)
+		{
+			this.tsa = tsa;
+		}
+
+		//------------------------------------------------------------------------------
+
+		public TimeStampToken Generate(
+			TimeStampRequest	request,
+			BigInteger			serialNumber,
+			DateTime			genTime)
+		{
+			DerObjectIdentifier digestAlgOID = new DerObjectIdentifier(request.MessageImprintAlgOid);
+
+			AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, DerNull.Instance);
+			MessageImprint messageImprint = new MessageImprint(algID, request.GetMessageImprintDigest());
+
+			Accuracy accuracy = null;
+			if (accuracySeconds > 0 || accuracyMillis > 0 || accuracyMicros > 0)
+			{
+				DerInteger seconds = null;
+				if (accuracySeconds > 0)
+				{
+					seconds = new DerInteger(accuracySeconds);
+				}
+
+				DerInteger millis = null;
+				if (accuracyMillis > 0)
+				{
+					millis = new DerInteger(accuracyMillis);
+				}
+
+				DerInteger micros = null;
+				if (accuracyMicros > 0)
+				{
+					micros = new DerInteger(accuracyMicros);
+				}
+
+				accuracy = new Accuracy(seconds, millis, micros);
+			}
+
+			DerBoolean derOrdering = null;
+			if (ordering)
+			{
+				derOrdering = DerBoolean.GetInstance(ordering);
+			}
+
+			DerInteger nonce = null;
+			if (request.Nonce != null)
+			{
+				nonce = new DerInteger(request.Nonce);
+			}
+
+			DerObjectIdentifier tsaPolicy = new DerObjectIdentifier(tsaPolicyOID);
+			if (request.ReqPolicy != null)
+			{
+				tsaPolicy = new DerObjectIdentifier(request.ReqPolicy);
+			}
+
+			TstInfo tstInfo = new TstInfo(tsaPolicy, messageImprint,
+				new DerInteger(serialNumber), new DerGeneralizedTime(genTime), accuracy,
+				derOrdering, nonce, tsa, request.Extensions);
+
+			try
+			{
+				CmsSignedDataGenerator signedDataGenerator = new CmsSignedDataGenerator();
+
+				byte[] derEncodedTstInfo = tstInfo.GetDerEncoded();
+
+				if (request.CertReq)
+				{
+					signedDataGenerator.AddCertificates(x509Certs);
+				}
+
+				signedDataGenerator.AddCrls(x509Crls);
+				signedDataGenerator.AddSigner(key, cert, digestOID, signedAttr, unsignedAttr);
+
+				CmsSignedData signedData = signedDataGenerator.Generate(
+					PkcsObjectIdentifiers.IdCTTstInfo.Id,
+					new CmsProcessableByteArray(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);
+			}
+			catch (X509StoreException e)
+			{
+				throw new TspException("Exception handling CertStore", e);
+			}
+//			catch (InvalidAlgorithmParameterException e)
+//			{
+//				throw new TspException("Exception handling CertStore CRLs", e);
+//			}
+		}
+	}
+}
diff --git a/crypto/src/tsp/TimeStampTokenInfo.cs b/crypto/src/tsp/TimeStampTokenInfo.cs
new file mode 100644
index 000000000..5027a87c4
--- /dev/null
+++ b/crypto/src/tsp/TimeStampTokenInfo.cs
@@ -0,0 +1,107 @@
+using System;
+
+using Org.BouncyCastle.Asn1.Tsp;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Tsp
+{
+	public class TimeStampTokenInfo
+	{
+		private TstInfo		tstInfo;
+		private DateTime	genTime;
+
+		public TimeStampTokenInfo(
+			TstInfo tstInfo)
+		{
+			this.tstInfo = tstInfo;
+
+			try
+			{
+				this.genTime = tstInfo.GenTime.ToDateTime();
+			}
+			catch (Exception e)
+			{
+				throw new TspException("unable to parse genTime field: " + e.Message);
+			}
+		}
+
+		public bool IsOrdered
+		{
+			get { return tstInfo.Ordering.IsTrue; }
+		}
+
+		public Accuracy Accuracy
+		{
+			get { return tstInfo.Accuracy; }
+		}
+
+		public DateTime GenTime
+		{
+			get { return genTime; }
+		}
+
+		public GenTimeAccuracy GenTimeAccuracy
+		{
+			get
+			{
+				return this.Accuracy == null
+					?	null
+					:	new GenTimeAccuracy(this.Accuracy);
+			}
+		}
+
+		public string Policy
+		{
+			get { return tstInfo.Policy.Id; }
+		}
+
+		public BigInteger SerialNumber
+		{
+			get { return tstInfo.SerialNumber.Value; }
+		}
+
+		public GeneralName Tsa
+		{
+			get { return tstInfo.Tsa; }
+		}
+
+		/**
+		 * @return the nonce value, null if there isn't one.
+		 */
+		public BigInteger Nonce
+		{
+			get
+			{
+				return tstInfo.Nonce == null
+					?	null
+					:	tstInfo.Nonce.Value;
+			}
+		}
+
+		public AlgorithmIdentifier HashAlgorithm
+		{
+			get { return tstInfo.MessageImprint.HashAlgorithm; }
+		}
+
+		public string MessageImprintAlgOid
+		{
+			get { return tstInfo.MessageImprint.HashAlgorithm.ObjectID.Id; }
+		}
+
+		public byte[] GetMessageImprintDigest()
+		{
+			return tstInfo.MessageImprint.GetHashedMessage();
+		}
+
+		public byte[] GetEncoded()
+		{
+			return tstInfo.GetEncoded();
+		}
+
+		public TstInfo TstInfo
+		{
+			get { return tstInfo; }
+		}
+	}
+}