package org.spongycastle.tsp;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.cert.CRLException;
import java.security.cert.CertStore;
import java.security.cert.CertStoreException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.RSAPrivateKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Hashtable;
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.DERSet;
import org.spongycastle.asn1.cms.Attribute;
import org.spongycastle.asn1.cms.AttributeTable;
import org.spongycastle.asn1.ess.ESSCertID;
import org.spongycastle.asn1.ess.SigningCertificate;
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.cert.jcajce.JcaX509CRLHolder;
import org.spongycastle.cert.jcajce.JcaX509CertificateHolder;
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.CMSSignedGenerator;
import org.spongycastle.cms.DefaultSignedAttributeTableGenerator;
import org.spongycastle.cms.SignerInfoGenerator;
import org.spongycastle.cms.SimpleAttributeTableGenerator;
import org.spongycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.spongycastle.jce.interfaces.GOST3410PrivateKey;
import org.spongycastle.operator.DigestCalculator;
import org.spongycastle.operator.OperatorCreationException;
import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
import org.spongycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.spongycastle.util.CollectionStore;
import org.spongycastle.util.Store;
public class TimeStampTokenGenerator
{
int accuracySeconds = -1;
int accuracyMillis = -1;
int accuracyMicros = -1;
boolean ordering = false;
GeneralName tsa = null;
private ASN1ObjectIdentifier tsaPolicyOID;
PrivateKey key;
X509Certificate cert;
String digestOID;
AttributeTable signedAttr;
AttributeTable unsignedAttr;
private List certs = new ArrayList();
private List crls = new ArrayList();
private List attrCerts = new ArrayList();
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.
*
* @param sha1DigestCalculator calculator for SHA-1 of certificate.
* @param signerInfoGen the generator for the signer we are using.
* @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(
DigestCalculator sha1DigestCalculator,
final SignerInfoGenerator signerInfoGen,
ASN1ObjectIdentifier tsaPolicy)
throws IllegalArgumentException, TSPException
{
this.signerInfoGen = signerInfoGen;
this.tsaPolicyOID = tsaPolicy;
if (!sha1DigestCalculator.getAlgorithmIdentifier().getAlgorithm().equals(OIWObjectIdentifiers.idSHA1))
{
throw new IllegalArgumentException("Digest calculator must be for SHA-1");
}
if (!signerInfoGen.hasAssociatedCertificate())
{
throw new IllegalArgumentException("SignerInfoGenerator must have an associated certificate");
}
TSPUtil.validateCertificate(signerInfoGen.getAssociatedCertificate());
try
{
OutputStream dOut = sha1DigestCalculator.getOutputStream();
dOut.write(signerInfoGen.getAssociatedCertificate().getEncoded());
dOut.close();
final ESSCertID essCertid = new ESSCertID(sha1DigestCalculator.getDigest());
this.signerInfoGen = new SignerInfoGenerator(signerInfoGen, new CMSAttributeTableGenerator()
{
public AttributeTable getAttributes(Map parameters)
throws CMSAttributeTableGenerationException
{
AttributeTable table = signerInfoGen.getSignedAttributeTableGenerator().getAttributes(parameters);
return table.add(PKCSObjectIdentifiers.id_aa_signingCertificate, new SigningCertificate(essCertid));
}
}, signerInfoGen.getUnsignedAttributeTableGenerator());
}
catch (IOException e)
{
throw new TSPException("Exception processing certificate.", e);
}
}
/**
* basic creation - only the default attributes will be included here.
* @deprecated use SignerInfoGenerator constructor that takes a digest calculator
*/
public TimeStampTokenGenerator(
final SignerInfoGenerator signerInfoGen,
ASN1ObjectIdentifier tsaPolicy)
throws IllegalArgumentException, TSPException
{
this(new DigestCalculator()
{
private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
public AlgorithmIdentifier getAlgorithmIdentifier()
{
return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, DERNull.INSTANCE);
}
public OutputStream getOutputStream()
{
return bOut;
}
public byte[] getDigest()
{
try
{
return MessageDigest.getInstance("SHA-1").digest(bOut.toByteArray());
}
catch (NoSuchAlgorithmException e)
{
throw new IllegalStateException("cannot find sha-1: "+ e.getMessage());
}
}
}, signerInfoGen, tsaPolicy);
}
/**
* basic creation - only the default attributes will be included here.
* @deprecated use SignerInfoGenerator constructor that takes a digest calculator.
*/
public TimeStampTokenGenerator(
PrivateKey key,
X509Certificate cert,
String digestOID,
String tsaPolicyOID)
throws IllegalArgumentException, TSPException
{
this(key, cert, digestOID, tsaPolicyOID, null, null);
}
/**
* basic creation - only the default attributes will be included here.
* @deprecated use SignerInfoGenerator constructor that takes a digest calculator.
*/
public TimeStampTokenGenerator(
PrivateKey key,
X509Certificate cert,
ASN1ObjectIdentifier digestOID,
String tsaPolicyOID)
throws IllegalArgumentException, TSPException
{
this(key, cert, digestOID.getId(), tsaPolicyOID, null, null);
}
/**
* create with a signer with extra signed/unsigned attributes.
* @deprecated use SignerInfoGenerator constructor that takes a digest calculator.
*/
public TimeStampTokenGenerator(
PrivateKey key,
X509Certificate cert,
String digestOID,
String tsaPolicyOID,
AttributeTable signedAttr,
AttributeTable unsignedAttr)
throws IllegalArgumentException, TSPException
{
this.key = key;
this.cert = cert;
this.digestOID = digestOID;
this.tsaPolicyOID = new ASN1ObjectIdentifier(tsaPolicyOID);
this.unsignedAttr = unsignedAttr;
//
// add the essCertid
//
Hashtable signedAttrs = null;
if (signedAttr != null)
{
signedAttrs = signedAttr.toHashtable();
}
else
{
signedAttrs = new Hashtable();
}
TSPUtil.validateCertificate(cert);
try
{
ESSCertID essCertid = new ESSCertID(MessageDigest.getInstance("SHA-1").digest(cert.getEncoded()));
signedAttrs.put(PKCSObjectIdentifiers.id_aa_signingCertificate,
new Attribute(
PKCSObjectIdentifiers.id_aa_signingCertificate,
new DERSet(new SigningCertificate(essCertid))));
}
catch (NoSuchAlgorithmException e)
{
throw new TSPException("Can't find a SHA-1 implementation.", e);
}
catch (CertificateEncodingException e)
{
throw new TSPException("Exception processing certificate.", e);
}
this.signedAttr = new AttributeTable(signedAttrs);
}
/**
* @deprecated use addCertificates and addCRLs
* @param certificates
* @throws CertStoreException
* @throws TSPException
*/
public void setCertificatesAndCRLs(CertStore certificates)
throws CertStoreException, TSPException
{
Collection c1 = certificates.getCertificates(null);
for (Iterator it = c1.iterator(); it.hasNext();)
{
try
{
certs.add(new JcaX509CertificateHolder((X509Certificate)it.next()));
}
catch (CertificateEncodingException e)
{
throw new TSPException("cannot encode certificate: " + e.getMessage(), e);
}
}
c1 = certificates.getCRLs(null);
for (Iterator it = c1.iterator(); it.hasNext();)
{
try
{
crls.add(new JcaX509CRLHolder((X509CRL)it.next()));
}
catch (CRLException e)
{
throw new TSPException("cannot encode CRL: " + e.getMessage(), 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));
}
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;
}
//------------------------------------------------------------------------------
public TimeStampToken generate(
TimeStampRequest request,
BigInteger serialNumber,
Date genTime,
String provider)
throws NoSuchAlgorithmException, NoSuchProviderException, TSPException
{
if (signerInfoGen == null)
{
try
{
JcaSignerInfoGeneratorBuilder sigBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(provider).build());
sigBuilder.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(signedAttr));
if (unsignedAttr != null)
{
sigBuilder.setUnsignedAttributeGenerator(new SimpleAttributeTableGenerator(unsignedAttr));
}
signerInfoGen = sigBuilder.build(new JcaContentSignerBuilder(getSigAlgorithm(key, digestOID)).setProvider(provider).build(key), cert);
}
catch (OperatorCreationException e)
{
throw new TSPException("Error generating signing operator", e);
}
catch (CertificateEncodingException e)
{
throw new TSPException("Error encoding certificate", e);
}
}
return generate(request, serialNumber, genTime);
}
public TimeStampToken generate(
TimeStampRequest request,
BigInteger serialNumber,
Date genTime)
throws TSPException
{
if (signerInfoGen == null)
{
throw new IllegalStateException("can only use this method with SignerInfoGenerator constructor");
}
ASN1ObjectIdentifier digestAlgOID = request.getMessageImprintAlgOID();
AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, new DERNull());
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.addCRLs(new CollectionStore(crls));
signedDataGenerator.addAttributeCertificates(new CollectionStore(attrCerts));
}
else
{
signedDataGenerator.addCRLs(new CollectionStore(crls));
}
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);
}
}
private String getSigAlgorithm(
PrivateKey key,
String digestOID)
{
String enc = null;
if (key instanceof RSAPrivateKey || "RSA".equalsIgnoreCase(key.getAlgorithm()))
{
enc = "RSA";
}
else if (key instanceof DSAPrivateKey || "DSA".equalsIgnoreCase(key.getAlgorithm()))
{
enc = "DSA";
}
else if ("ECDSA".equalsIgnoreCase(key.getAlgorithm()) || "EC".equalsIgnoreCase(key.getAlgorithm()))
{
enc = "ECDSA";
}
else if (key instanceof GOST3410PrivateKey || "GOST3410".equalsIgnoreCase(key.getAlgorithm()))
{
enc = "GOST3410";
}
else if ("ECGOST3410".equalsIgnoreCase(key.getAlgorithm()))
{
enc = CMSSignedGenerator.ENCRYPTION_ECGOST3410;
}
return TSPUtil.getDigestAlgName(digestOID) + "with" + enc;
}
}