package com.opentrust.spi.cms; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Security; import java.security.SignatureException; import java.security.cert.CRL; import java.security.cert.CertStore; import java.security.cert.CertStoreException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.CollectionCertStoreParameters; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Set; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.DEREncodable; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.asn1.cms.ContentInfo; import org.bouncycastle.asn1.cms.SignedData; import org.bouncycastle.asn1.esf.CommitmentTypeIndication; import org.bouncycastle.asn1.esf.SignaturePolicyIdentifier; import org.bouncycastle.asn1.esf.SignerAttribute; import org.bouncycastle.asn1.esf.SignerLocation; import org.bouncycastle.asn1.ess.ContentHints; import org.bouncycastle.asn1.ess.ContentIdentifier; import org.bouncycastle.asn1.ess.ESSCertID; import org.bouncycastle.asn1.ess.ESSCertIDv2; import org.bouncycastle.asn1.x509.CertificateList; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaCRLStore; import org.bouncycastle.cert.jcajce.JcaCertStore; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSProcessable; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedDataParser; import org.bouncycastle.cms.CMSTypedStream; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.SignerInformationStore; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.util.Store; import com.opentrust.spi.cms.helpers.OCSPResponse; import com.opentrust.spi.cms.helpers.SignatureHelper; import com.opentrust.spi.cms.helpers.SignedAttributesHelper; import com.opentrust.spi.cms.helpers.UnsignedAttributesHelper; import com.opentrust.spi.logger.Channel; import com.opentrust.spi.logger.SPILogger; import com.opentrust.spi.tsp.TimestampToken; public class CMSSignedDataWrapper { private static SPILogger log = SPILogger.getLogger("CMS"); static { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); } protected CMSSignedData cmsSignedData = null; protected SignerInformation firstSignerInfo = null; protected List<TimestampToken> signatureTimeStamps = null; protected boolean hasMultipleSignerInfos = false; public CMSSignedDataWrapper(InputStream inputStream) throws SignatureException, CMSException { this(new CMSSignedData(inputStream)); } public CMSSignedDataWrapper(byte[] data) throws SignatureException, CMSException { this(new CMSSignedData(data)); } public CMSSignedDataWrapper(CMSSignedData cmsSignedData) { try { this.cmsSignedData = cmsSignedData; parseCms(); } catch (Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } } private void parseCms() { Collection signers = cmsSignedData.getSignerInfos().getSigners(); if(signers.size()!=1) hasMultipleSignerInfos = true; Iterator iterator = signers.iterator(); firstSignerInfo = (SignerInformation) iterator.next(); } public boolean hasMultipleSignerInfos() { return hasMultipleSignerInfos; } public ASN1ObjectIdentifier getContentType() { ContentInfo ci = cmsSignedData.getContentInfo(); if(ci==null) return null; return ci.getContentType(); } public int getVersion() { return cmsSignedData.getVersion(); } public int getSignerVersion() { return firstSignerInfo.getVersion(); } public void appendSignatureTimeStamp(byte[] timeStampTokenBytes) { try { AttributeTable at = firstSignerInfo.getUnsignedAttributes(); firstSignerInfo = SignerInformation.replaceUnsignedAttributes(firstSignerInfo, appendTimestampAttribute(timeStampTokenBytes, at)); Collection<SignerInformation> signers = new ArrayList<SignerInformation>(1); signers.add(firstSignerInfo); SignerInformationStore sis = new SignerInformationStore(signers); cmsSignedData = CMSSignedData.replaceSigners(cmsSignedData, sis); } catch (Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } } protected static AttributeTable appendTimestampAttribute( byte[] timeStampTokenBytes, AttributeTable attributeTable) { Hashtable unsignedAttributesHT; if (attributeTable == null) { unsignedAttributesHT = new Hashtable(); } else { unsignedAttributesHT = attributeTable.toHashtable(); } try { UnsignedAttributesHelper.addTimestampAttribute(unsignedAttributesHT, timeStampTokenBytes); } catch(Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } attributeTable = new AttributeTable(unsignedAttributesHT); return attributeTable; } // adds unsigned certs & revocation infos (CRL or OCSP) to existing certs & revocation info list ('certificates' and 'crls' CMS fields) public void appendValidationValues(Collection certificateValues, Collection revocationValues) { try { Store certStore = cmsSignedData.getCertificates(); Store crlStore = cmsSignedData.getCRLs(); if (certificateValues != null && !certificateValues.isEmpty()) { Collection<Certificate> existingCerts = getSignatureCertificateInfo(); Set<Certificate> newCerts = new HashSet<Certificate>(existingCerts); // 'Set' to avoid duplicates newCerts.addAll(certificateValues); certStore = new JcaCertStore(newCerts); } if (revocationValues != null && !revocationValues.isEmpty()) { Collection<CRL> existingCrls = getUnsignedCRLs(); Set<CRL> newCrls = new HashSet<CRL>(existingCrls); // 'Set' to avoid duplicates //FIXME : also add OCSP info (use OtherRevocationInfoFormat of RevocationInfoChoices, see RFC 3852) for(Object o : revocationValues) { if(o instanceof CRL) newCrls.add((CRL)o); } crlStore = new JcaCRLStore(newCrls); } cmsSignedData = CMSSignedData.replaceCertificatesAndCRLs(cmsSignedData, certStore, cmsSignedData.getAttributeCertificates(), crlStore); } catch (Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } } public byte[] getEncoded() { try { return cmsSignedData.getEncoded(); } catch (IOException e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } return null; } public String getSignatureAlgorithm() { return SignatureHelper.getSignatureAlgoFromDigestAndKeyAlgo(getDataDigestAlgorithm(), getEncryptionAlgorithm()); } public String getDataDigestAlgorithm() { return firstSignerInfo.getDigestAlgOID(); } public String getEncryptionAlgorithm() { return firstSignerInfo.getEncryptionAlgOID(); } public List<Certificate> getSignatureCertificateInfo() { try { Store certificateStore = cmsSignedData.getCertificates(); Collection<X509CertificateHolder> certificateCollection = certificateStore.getMatches(null); List<Certificate> x509CertsCollection = new ArrayList<Certificate>(certificateCollection.size()); for(X509CertificateHolder certHolder : certificateCollection) { x509CertsCollection.add(CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME).generateCertificate(new ByteArrayInputStream(certHolder.getEncoded()))); } return x509CertsCollection; } catch (Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } return null; } public Collection<CRL> getCRLs() { Collection<CRL> crls = new HashSet<CRL>(); try { crls.addAll(getUnsignedCRLs()); crls.addAll(getSignedCRLs()); return crls; } catch (Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } return crls; } // unsigned CRLs at the root of CMS structure (outside signerInfos) public Collection<CRL> getUnsignedCRLs() { try { Collection<CertificateList> crlCollection = cmsSignedData.getCRLs().getMatches(null); // Then we need to "cast" from bouncycastle.CertificateList to java.CRL Collection<CRL> x509CrlsCollection = new HashSet<CRL>(crlCollection.size()); for(CertificateList certList : crlCollection) { x509CrlsCollection.add(CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME).generateCRL(new ByteArrayInputStream(certList.getEncoded()))); } return x509CrlsCollection; } catch (Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } return null; } // CRLS found as signed ID_ADBE_REVOCATION attribute public Collection<CRL> getSignedCRLs() { try { AttributeTable table = firstSignerInfo.getSignedAttributes(); return SignedAttributesHelper.getSignedCRLs(table); } catch (Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } return null; } public CertStore getSignatureCRLStore() { try { CertStore crlStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(getCRLs()), BouncyCastleProvider.PROVIDER_NAME); return crlStore; } catch (Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } return null; } public Set<OCSPResponse> getOCSPResponses() { try { Set<OCSPResponse> ocspResponses = getUnsignedOCSPResponses(); ocspResponses.addAll(getSignedOCSPResponses()); return ocspResponses; } catch (Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } return null; } // OCSP responses found as signed ID_ADBE_REVOCATION attribute public Set<OCSPResponse> getSignedOCSPResponses() { try { AttributeTable table = firstSignerInfo.getSignedAttributes(); return SignedAttributesHelper.getSignedOCSPResponses(table); } catch (Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } return null; } // unsigned OCSPs at the root of CMS structure (outside signerInfos) public Set<OCSPResponse> getUnsignedOCSPResponses() { try { //FIXME : really fetch those values ! Set<OCSPResponse> ocspResponses = new HashSet<OCSPResponse>(); return ocspResponses; } catch (Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } return null; } public List<TimestampToken> getSignatureTimestamps() { if (signatureTimeStamps == null) { try { signatureTimeStamps = UnsignedAttributesHelper.getSignatureTimestamps(firstSignerInfo.getUnsignedAttributes()); } catch (Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } } return signatureTimeStamps; } public TimestampToken getContentTimestamp() { try { return SignedAttributesHelper.getContentTimestamp(firstSignerInfo.getSignedAttributes()); } catch (Exception e) { ExceptionHandlerTyped.<SPISignatureException>handle(SPISignatureException.class, e); } return null; } public byte[] getSignatureValue() { return firstSignerInfo.getSignature(); } public Certificate getSignerCertificate() { try { Collection certificateCollection = cmsSignedData.getCertificates().getMatches(firstSignerInfo.getSID()); Iterator iterator = certificateCollection.iterator(); X509CertificateHolder certHolder = (X509CertificateHolder) iterator.next(); return CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME).generateCertificate(new ByteArrayInputStream(certHolder.getEncoded())); } catch (Exception e) { log.error(Channel.TECH, "Could not extract signer certificate from CMS signature : %1$s", e); } return null; } public Date getSigningTime() { return SignedAttributesHelper.getSigningTime(firstSignerInfo.getSignedAttributes()); } public byte[] getDigestAttribute() { return SignedAttributesHelper.getDigestAttribute(firstSignerInfo.getSignedAttributes()); } public ASN1ObjectIdentifier getContentTypeAttribute() { return SignedAttributesHelper.getContentTypeAttribute(firstSignerInfo.getSignedAttributes()); } public ESSCertID getSigningCertificateAttribute() { return SignedAttributesHelper.getSigningCertificateAttribute(firstSignerInfo.getSignedAttributes()); } public ESSCertIDv2 getSigningCertificateV2Attribute() { return SignedAttributesHelper.getSigningCertificateV2Attribute(firstSignerInfo.getSignedAttributes()); } public SignaturePolicyIdentifier getSignaturePolicyIdentifierAttribute() { return SignedAttributesHelper.getSignaturePolicyIdentifierAttribute(firstSignerInfo.getSignedAttributes()); } public DEREncodable getContentReferenceAttribute() { return SignedAttributesHelper.getContentReferenceAttribute(firstSignerInfo.getSignedAttributes()); } public ContentIdentifier getContentIdentifierAttribute() { return SignedAttributesHelper.getContentIdentifierAttribute(firstSignerInfo.getSignedAttributes()); } public ContentHints getContentHintsAttribute() { return SignedAttributesHelper.getContentHintsAttribute(firstSignerInfo.getSignedAttributes()); } public CommitmentTypeIndication getCommitmentTypeIndicationAttribute() { return SignedAttributesHelper.getCommitmentTypeIndicationAttribute(firstSignerInfo.getSignedAttributes()); } public SignerLocation getSignerLocationAttribute() { return SignedAttributesHelper.getSignerLocationAttribute(firstSignerInfo.getSignedAttributes()); } public SignerAttribute getSignerAttributesAttribute() { return SignedAttributesHelper.getSignerAttributesAttribute(firstSignerInfo.getSignedAttributes()); } public SignerInformationStore getCounterSignatures() { return firstSignerInfo.getCounterSignatures(); } // For external signatures, inputStream parameter is the data being signed (external eContent) public boolean verifyExternalWithContent(InputStream inputStream) throws CMSException, IOException, NoSuchAlgorithmException, NoSuchProviderException, CertStoreException, OperatorCreationException, CertificateException { CMSSignedDataParser sp = new CMSSignedDataParser(new CMSTypedStream(inputStream), cmsSignedData.getEncoded()); sp.getSignedContent().drain(); //here digests are computed and passed to newly created SignerInformation objects Collection signers = sp.getSignerInfos().getSigners(); if(signers.size()!=1) hasMultipleSignerInfos = true; Iterator iterator = signers.iterator(); firstSignerInfo = (SignerInformation) iterator.next(); return CMSVerifier.verify(firstSignerInfo, cmsSignedData.getCertificates()); } // For encapsulated signatures, data being signed is inside the CMS structure, under the EncapsulatedContentInfo field public boolean verifyEncapsulated() throws InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException, IOException, CertStoreException, OperatorCreationException, CMSException { return CMSVerifier.verify(firstSignerInfo, cmsSignedData.getCertificates()); } // For external signatures, digest parameter is the digest of the data being signed (external eContent pre-digested with digestAlgo specified in signerInfo) public boolean verifyExternalWithReference(byte[] digest) throws InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException, IOException { return CMSVerifier.verifyReference(digest, firstSignerInfo, cmsSignedData.getCertificates()); } public byte[] getEncodedSignedAttrs() throws IOException { return firstSignerInfo.getEncodedSignedAttributes(); } public byte[] getEncodedEncapsulatedData() throws IOException { CMSProcessable content = cmsSignedData.getSignedContent(); if(content==null) return null; return (byte[])content.getContent(); } }