001: package org.bouncycastle.tsp;
002:
003: import java.io.IOException;
004: import java.io.ByteArrayInputStream;
005: import java.io.ByteArrayOutputStream;
006: import java.util.Collection;
007: import java.util.Date;
008: import java.security.cert.CertStore;
009: import java.security.cert.CertificateEncodingException;
010: import java.security.cert.CertificateExpiredException;
011: import java.security.cert.CertificateNotYetValidException;
012: import java.security.cert.X509Certificate;
013: import java.security.MessageDigest;
014: import java.security.NoSuchAlgorithmException;
015: import java.security.NoSuchProviderException;
016:
017: import org.bouncycastle.cms.CMSProcessable;
018: import org.bouncycastle.cms.CMSSignedData;
019: import org.bouncycastle.cms.SignerId;
020: import org.bouncycastle.cms.SignerInformation;
021: import org.bouncycastle.cms.CMSException;
022: import org.bouncycastle.jce.PrincipalUtil;
023: import org.bouncycastle.jce.X509Principal;
024: import org.bouncycastle.asn1.cms.Attribute;
025: import org.bouncycastle.asn1.cms.AttributeTable;
026: import org.bouncycastle.asn1.cms.ContentInfo;
027: import org.bouncycastle.asn1.ess.ESSCertID;
028: import org.bouncycastle.asn1.ess.SigningCertificate;
029: import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
030: import org.bouncycastle.asn1.tsp.TSTInfo;
031: import org.bouncycastle.asn1.x509.GeneralName;
032: import org.bouncycastle.asn1.x509.X509Name;
033: import org.bouncycastle.asn1.ASN1InputStream;
034:
035: public class TimeStampToken {
036: CMSSignedData tsToken;
037:
038: SignerInformation tsaSignerInfo;
039:
040: Date genTime;
041:
042: TimeStampTokenInfo tstInfo;
043:
044: ESSCertID certID;
045:
046: TimeStampToken(ContentInfo contentInfo) throws TSPException,
047: IOException {
048: this (new CMSSignedData(contentInfo));
049: }
050:
051: public TimeStampToken(CMSSignedData signedData)
052: throws TSPException, IOException {
053: this .tsToken = signedData;
054:
055: if (!this .tsToken.getSignedContentTypeOID().equals(
056: PKCSObjectIdentifiers.id_ct_TSTInfo.getId())) {
057: throw new TSPValidationException(
058: "ContentInfo object not for a time stamp.");
059: }
060:
061: Collection signers = tsToken.getSignerInfos().getSigners();
062:
063: if (signers.size() != 1) {
064: throw new IllegalArgumentException(
065: "Time-stamp token signed by "
066: + signers.size()
067: + " signers, but it must contain just the TSA signature.");
068: }
069:
070: tsaSignerInfo = (SignerInformation) signers.iterator().next();
071:
072: try {
073: CMSProcessable content = tsToken.getSignedContent();
074: ByteArrayOutputStream bOut = new ByteArrayOutputStream();
075:
076: content.write(bOut);
077:
078: ASN1InputStream aIn = new ASN1InputStream(
079: new ByteArrayInputStream(bOut.toByteArray()));
080:
081: this .tstInfo = new TimeStampTokenInfo(TSTInfo
082: .getInstance(aIn.readObject()));
083:
084: Attribute attr = tsaSignerInfo.getSignedAttributes().get(
085: PKCSObjectIdentifiers.id_aa_signingCertificate);
086:
087: if (attr == null) {
088: throw new TSPValidationException(
089: "no signing certificate attribute found, time stamp invalid.");
090: }
091:
092: SigningCertificate signCert = SigningCertificate
093: .getInstance(attr.getAttrValues().getObjectAt(0));
094:
095: this .certID = ESSCertID.getInstance(signCert.getCerts()[0]);
096: } catch (CMSException e) {
097: throw new TSPException(e.getMessage(), e
098: .getUnderlyingException());
099: }
100: }
101:
102: public TimeStampTokenInfo getTimeStampInfo() {
103: return tstInfo;
104: }
105:
106: public SignerId getSID() {
107: return tsaSignerInfo.getSID();
108: }
109:
110: public AttributeTable getSignedAttributes() {
111: return tsaSignerInfo.getSignedAttributes();
112: }
113:
114: public AttributeTable getUnsignedAttributes() {
115: return tsaSignerInfo.getUnsignedAttributes();
116: }
117:
118: public CertStore getCertificatesAndCRLs(String type, String provider)
119: throws NoSuchAlgorithmException, NoSuchProviderException,
120: CMSException {
121: return tsToken.getCertificatesAndCRLs(type, provider);
122: }
123:
124: /**
125: * Validate the time stamp token.
126: * <p>
127: * To be valid the token must be signed by the passed in certificate and
128: * the certificate must be the one refered to by the SigningCertificate
129: * attribute included in the hashed attributes of the token. The
130: * certifcate must also have the ExtendedKeyUsageExtension with only
131: * KeyPurposeId.id_kp_timeStamping and have been valid at the time the
132: * timestamp was created.
133: * </p>
134: * <p>
135: * A successful call to validate means all the above are true.
136: * </p>
137: */
138: public void validate(X509Certificate cert, String provider)
139: throws TSPException, TSPValidationException,
140: CertificateExpiredException,
141: CertificateNotYetValidException, NoSuchProviderException {
142: try {
143: if (!MessageDigest.isEqual(certID.getCertHash(),
144: MessageDigest.getInstance("SHA-1").digest(
145: cert.getEncoded()))) {
146: throw new TSPValidationException(
147: "certificate hash does not match certID hash.");
148: }
149:
150: if (certID.getIssuerSerial() != null) {
151: if (!certID.getIssuerSerial().getSerial().getValue()
152: .equals(cert.getSerialNumber())) {
153: throw new TSPValidationException(
154: "certificate serial number does not match certID for signature.");
155: }
156:
157: GeneralName[] names = certID.getIssuerSerial()
158: .getIssuer().getNames();
159: X509Principal principal = PrincipalUtil
160: .getIssuerX509Principal(cert);
161: boolean found = false;
162:
163: for (int i = 0; i != names.length; i++) {
164: if (names[i].getTagNo() == 4
165: && new X509Principal(X509Name
166: .getInstance(names[i].getName()))
167: .equals(principal)) {
168: found = true;
169: break;
170: }
171: }
172:
173: if (!found) {
174: throw new TSPValidationException(
175: "certificate name does not match certID for signature. ");
176: }
177: }
178:
179: TSPUtil.validateCertificate(cert);
180:
181: cert.checkValidity(tstInfo.getGenTime());
182:
183: if (!tsaSignerInfo.verify(cert, provider)) {
184: throw new TSPValidationException(
185: "signature not created by certificate.");
186: }
187: } catch (CMSException e) {
188: if (e.getUnderlyingException() != null) {
189: throw new TSPException(e.getMessage(), e
190: .getUnderlyingException());
191: } else {
192: throw new TSPException("CMS exception: " + e, e);
193: }
194: } catch (NoSuchAlgorithmException e) {
195: throw new TSPException("cannot find algorithm: " + e, e);
196: } catch (CertificateEncodingException e) {
197: throw new TSPException("problem processing certificate: "
198: + e, e);
199: }
200: }
201:
202: /**
203: * Return the underlying CMSSignedData object.
204: *
205: * @return the underlying CMS structure.
206: */
207: public CMSSignedData toCMSSignedData() {
208: return tsToken;
209: }
210:
211: /**
212: * Return a ASN.1 encoded byte stream representing the encoded object.
213: *
214: * @throws IOException if encoding fails.
215: */
216: public byte[] getEncoded() throws IOException {
217: return tsToken.getEncoded();
218: }
219: }
|