package com.opentrust.spi.cms;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.SignatureException;
import java.security.cert.CertStoreException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.CMSAttributes;
import org.bouncycastle.asn1.ess.ESSCertID;
import org.bouncycastle.asn1.ess.ESSCertIDv2;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.DigestInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
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.cms.SignerInformationVerifier;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.util.Store;
import com.opentrust.spi.cms.helpers.SignatureHelper;
import com.opentrust.spi.cms.helpers.SignedAttributesHelper;
import com.opentrust.spi.crypto.DigestHelper;
import com.opentrust.spi.logger.Channel;
import com.opentrust.spi.logger.SPILogger;
public class CMSVerifier {
private static SPILogger log = SPILogger.getLogger("CMS");
static {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}
public static boolean verify(String documentFileName, byte[] signatureBytes)
throws CMSException, IOException, NoSuchAlgorithmException,
NoSuchProviderException, CertStoreException,
OperatorCreationException, CertificateException {
CMSSignedDataParser sp = new CMSSignedDataParser(new CMSTypedStream(new FileInputStream(
documentFileName)), signatureBytes);
sp.getSignedContent().drain();
Store certStore = sp.getCertificates();
SignerInformationStore signers = sp.getSignerInfos();
Collection c = signers.getSigners();
Iterator it = c.iterator();
while (it.hasNext()) {
boolean verifResult = verify((SignerInformation) it.next(), certStore);
if(!verifResult) return false;
}
return true;
}
public static boolean verify(byte[] contentBytes, byte[] signatureBytes)
throws CMSException, IOException, NoSuchAlgorithmException,
NoSuchProviderException, CertStoreException,
OperatorCreationException, CertificateException {
CMSSignedData sp = new CMSSignedData(new CMSProcessableByteArray(contentBytes), signatureBytes);
Store certStore = sp.getCertificates();
SignerInformationStore signers = sp.getSignerInfos();
Collection c = signers.getSigners();
Iterator it = c.iterator();
while (it.hasNext()) {
boolean verifResult = verify((SignerInformation) it.next(), certStore);
if(!verifResult) return false;
}
return true;
}
public static boolean verify(CMSSignedDataWrapper signature)
throws CMSException, IOException, NoSuchAlgorithmException,
NoSuchProviderException, CertStoreException,
OperatorCreationException, CertificateException {
return verify(signature.firstSignerInfo, signature.cmsSignedData.getCertificates());
}
public static boolean verify(SignerInformation signer, Store certStore)
throws CMSException, IOException, NoSuchAlgorithmException,
NoSuchProviderException, CertStoreException,
OperatorCreationException, CertificateException {
Collection certCollection = certStore.getMatches(signer.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder cert = (X509CertificateHolder) certIt.next();
Certificate x509Cert = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(cert.getEncoded()));
JcaSimpleSignerInfoVerifierBuilder sigVerifBuilder = new JcaSimpleSignerInfoVerifierBuilder();
SignerInformationVerifier signerInfoVerif = sigVerifBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(x509Cert.getPublicKey());
//the digest verification is included // Verif on public key so that cert verifications are not performed (done in dssl layer)
boolean rawVerif = signer.verify(signerInfoVerif);
// If RFC 3852 non-conformity -> CMSException
log.debug(Channel.TECH, "Raw signature verification results in '%1$s'", rawVerif);
boolean signerCertRefVerif = signingCertificateAttributeVerif(signer, x509Cert); // TODO : also done in dssl layer. Should this verification be removed from here ?
log.debug(Channel.TECH, "Signer-cert-ref verification results in '%1$s'", signerCertRefVerif);
return rawVerif && signerCertRefVerif;
}
// Not covered by this function : caller should check that the algo he used for contentDigest is the same as the algo in the signatureBytes CMS
public static boolean verifyReference(byte[] contentDigest,
byte[] signatureBytes) throws CMSException, IOException,
NoSuchAlgorithmException, NoSuchProviderException,
CertStoreException, InvalidKeyException,
SignatureException, OperatorCreationException, CertificateException {
CMSSignedData signedData = new CMSSignedData(signatureBytes);
Store certStore = signedData.getCertificates();
SignerInformationStore signers = signedData.getSignerInfos();
Collection c = signers.getSigners();
Iterator it = c.iterator();
while (it.hasNext()) {
boolean verifResult = verifyReference(contentDigest, (SignerInformation) it.next(), certStore);
if(!verifResult) return false;
}
return true;
}
// Not covered by this function : caller should check that the algo he used for contentDigest is the same as the algo in the SignerInformation object
public static boolean verifyReference(byte[] contentDigest, SignerInformation signer, Store certStore) throws CertificateException, IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException{
// verifying reference : only if message digest is there as a signed attribute
AttributeTable table = signer.getSignedAttributes();
if(table!=null) {
Attribute hash = table.get(CMSAttributes.messageDigest);
if(hash!=null) {
// FIXME return more details
if (!MessageDigest.isEqual(contentDigest, ((ASN1OctetString) hash.getAttrValues().getObjectAt(0)).getOctets()))
throw new SignatureException("Digest mismatch.");
}
}
// verifying signature
Collection certCollection = certStore.getMatches(signer.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder cert = (X509CertificateHolder) certIt.next();
Certificate x509Cert = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(cert.getEncoded()));
java.security.Signature signature = null;
if (signer.getEncodedSignedAttributes() != null) {
String signatureAlgorithm = getSignatureAlgorithm(signer);
signature = java.security.Signature.getInstance(signatureAlgorithm,
BouncyCastleProvider.PROVIDER_NAME);
signature.initVerify(x509Cert.getPublicKey());
signature.update(signer.getEncodedSignedAttributes());
} else {
// TODO: Support something else than RSA
signature = java.security.Signature.getInstance("NONEwithRSA", BouncyCastleProvider.PROVIDER_NAME);
signature.initVerify(x509Cert.getPublicKey());
AlgorithmIdentifier digestAlgOID = signer.getDigestAlgorithmID();
if (digestAlgOID.getParameters() == null)
digestAlgOID = new AlgorithmIdentifier(digestAlgOID.getObjectId(), new DERNull());
DigestInfo di = new DigestInfo(digestAlgOID, contentDigest);
signature.update(di.getDEREncoded());
}
boolean rawVerif = signature.verify(signer.getSignature());
// If RFC 3852 non-conformity -> CMSException
log.debug(Channel.TECH, "Raw signature verification results in '%1$s'", rawVerif);
boolean signerCertRefVerif = signingCertificateAttributeVerif(signer, x509Cert); // TODO : also done in dssl layer. Should this verification be removed from here ?
log.debug(Channel.TECH, "Signer-cert-ref verification results in '%1$s'", signerCertRefVerif);
return rawVerif && signerCertRefVerif;
}
// Not covered by this function : caller should check that the algo he used for contentDigest is the same as the algo in the SignerInformation object
public static boolean verifyReference(byte[] contentDigest, CMSSignedDataWrapper signature)
throws CertificateException, IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException
{
return verifyReference(contentDigest, signature.firstSignerInfo, signature.cmsSignedData.getCertificates());
}
private static String getSignatureAlgorithm(SignerInformation signer) {
String encOid = signer.getEncryptionAlgOID();
String digestOid = signer.getDigestAlgOID();
return SignatureHelper.getSignatureAlgoFromDigestAndKeyAlgo(digestOid, encOid);
}
private static boolean signingCertificateAttributeVerif(SignerInformation signer, Certificate x509Cert) throws CertificateException, NoSuchAlgorithmException {
boolean signerCertRefVerif = true;
ESSCertID signingCertRef = SignedAttributesHelper.getSigningCertificateAttribute(signer.getSignedAttributes());
if(signingCertRef!=null) {
log.debug(Channel.TECH, "signer-cert-ref attribute found");
byte[] certHash = DigestHelper.getDigest(x509Cert.getEncoded(), "SHA1");
signerCertRefVerif = Arrays.equals(certHash, signingCertRef.getCertHash());
} else {
ESSCertIDv2 signingCertRefV2 = SignedAttributesHelper.getSigningCertificateV2Attribute(signer.getSignedAttributes());
if(signingCertRefV2!=null) {
log.debug(Channel.TECH, "signer-cert-ref-v2 attribute found");
String hashAlgorithm = signingCertRefV2.getHashAlgorithm().getObjectId().getId();
byte[] certHash = DigestHelper.getDigest(x509Cert.getEncoded(), hashAlgorithm);
signerCertRefVerif = Arrays.equals(certHash, signingCertRefV2.getCertHash());
}
}
return signerCertRefVerif;
}
}