简体   繁体   中英

Detached signature in CMSSignedData verifies using Bouncy Castle but NOT using java.security.Signature

I have been traversing the web for some days now to find the cause of this but without success.

I am doing this :

  • generating fresh certificates (signer cert and his parent)
  • creating signature using CMSSignedDataGenerator ( bouncycastle 1.45 )
  • verifying it by java.security.Signature -> FAILS
  • verifying it by BC classes -> SUCCESS

Facts I found out :

  • Creation and verification of signature uses same algorithm - SHA1withRSA.
  • No exception is thrown anywhere in the process.

Questions :

  • Any idea what I am doing wrong?

Ideas to consider :

I think that the cause lies in the generation of the signature but I suppose I am doing it according to docs. Or - the validation process may expect some kind of different structure like PKCS1 while the generator provides PKCS7. Nothing actually tells me what to search next.

Here is a piece of code which should illustrate the problem and output (after providing your own certificates and a private key):

import org.bouncycastle.cms.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.CertStore;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import static org.bouncycastle.cms.CMSSignedGenerator.DIGEST_SHA1;
import static org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;


public final class VerifyCMSSignedData {

    private static final byte[] DATA_TO_BE_SIGNED = "data".getBytes();
    private static final String SHA_1_WITH_RSA = "SHA1WithRSA";
    private static final boolean DATA_NOT_ATTACHED = false;
    private static final String COLLECTION_STORE_TYPE = "Collection";

    private static X509Certificate signer;
    private static PrivateKey signerPrivateKey;
    private static X509Certificate parent;

    private VerifyCMSSignedData() {
    }

    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        setUpCertificates();

        shouldVerifySignature();
    }

    public static void shouldVerifySignature() throws Exception {
        CMSSignedData signature = createSignature();

        shouldVerifyBySignature(signature.getEncoded());

        shouldVerifyByBC(signature.getEncoded());
    }

    private static void shouldVerifyByBC(byte[] signatureBytes) {
        boolean verified = false;

        try {
            CMSSignedData cms = new CMSSignedData(new CMSProcessableByteArray(DATA_TO_BE_SIGNED), signatureBytes);
            CertStore certStore = cms.getCertificatesAndCRLs(COLLECTION_STORE_TYPE, PROVIDER_NAME);
            SignerInformationStore signers = cms.getSignerInfos();
            Collection c = signers.getSigners();
            for (Object aC : c) {
                SignerInformation signer = (SignerInformation) aC;
                Collection certCollection = certStore.getCertificates(signer.getSID());
                Iterator certIt = certCollection.iterator();
                X509Certificate cert = (X509Certificate) certIt.next();
                verified = signer.verify(cert, PROVIDER_NAME);
            }
        } catch (Exception e) {
            e.printStackTrace();
            verified = false;
        }

        System.out.println(verified ? "VERIFIED BY BC" : "! Not verified through BC !");
    }

    private static void shouldVerifyBySignature(byte[] signatureBytes) throws Exception {
        Signature signatureVerifier = Signature.getInstance(SHA_1_WITH_RSA, PROVIDER_NAME);
        signatureVerifier.initVerify(signer.getPublicKey());
        signatureVerifier.update(DATA_TO_BE_SIGNED);
        boolean verified = signatureVerifier.verify(signatureBytes);

        System.out.println(verified ? "VERIFIED BY SIGNATURE CLASS" : "! Not verified by Signature class !");
    }

    private static CMSSignedData createSignature() throws Exception {
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

        gen.addSigner(signerPrivateKey, signer, DIGEST_SHA1);

        List<X509Certificate> allCerts = new ArrayList<>();
        if (parent != null) {
            allCerts.add(parent);
        }
        allCerts.add(signer);

        CertStore store = CertStore.getInstance(
                COLLECTION_STORE_TYPE,
                new CollectionCertStoreParameters(allCerts),
                PROVIDER_NAME
        );

        gen.addCertificatesAndCRLs(store);

        return gen.generate(new CMSProcessableByteArray(DATA_TO_BE_SIGNED), DATA_NOT_ATTACHED, PROVIDER_NAME);
    }

    private static void setUpCertificates() throws Exception {
        // TODO setup your certificates here
    }
}

Output :

! Not verified by Signature class !

VERIFIED BY BC

Process finished with exit code 0

You are verifying different things. The verification can never be succesful because Java SHA1withRSA verification requires a RSA PKCS#1_v15 signature, but you have generated a CMS signature

CMS encapsulates digital signatures or encrypted messages and some additional elements such as certificates. Your CMS message includes a PCKS#1 signature, but if if you want to verify it directly using the Java Api, note that the signed hash is not computed on the data to be signed. It contains some additional elements such as the reference to signing certificate in ASN.1 syntax, so you would need to compute the hash in the same way

You was wrong here:

signatureVerifier.update(DATA_TO_BE_SIGNED);

and here:

boolean verified = signatureVerifier.verify(signatureBytes);

See pedrofb's answer for more details.

Modify your shouldVerifyBySignature method to work:

private static void shouldVerifyBySignature(byte[] signatureBytes) throws Exception {
    boolean verified = false;
    CMSSignedData cms = new CMSSignedData(new CMSProcessableByteArray(DATA_TO_BE_SIGNED), signatureBytes);
    CertStore certStore = cms.getCertificatesAndCRLs(COLLECTION_STORE_TYPE, PROVIDER_NAME);
    SignerInformationStore signers = cms.getSignerInfos();
    Collection c = signers.getSigners();
    for (Object aC : c) {
        SignerInformation si = (SignerInformation) aC;
        Signature signatureVerifier = Signature.getInstance(SHA_1_WITH_RSA, PROVIDER_NAME);
        signatureVerifier.initVerify(signer.getPublicKey());


        signatureVerifier.update(si.getEncodedSignedAttributes());
        verified = signatureVerifier.verify(si.getSignature());
    }

    System.out.println(verified ? "VERIFIED BY SIGNATURE CLASS" : "! Not verified by Signature class !");
}

I hope this will help you!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM