blob: 04229985662638fa69a70ed08f9aab5a35464bbe [file] [log] [blame]
package org.bouncycastle.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.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.ess.ESSCertID;
import org.bouncycastle.asn1.ess.ESSCertIDv2;
import org.bouncycastle.asn1.ess.SigningCertificate;
import org.bouncycastle.asn1.ess.SigningCertificateV2;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.tsp.TSTInfo;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.IssuerSerial;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerId;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.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();
}
}
}
}