Search code examples
javabouncycastle

BouncyCastle attribute messageDigest fail verification


I have a problem with signing/veryfing CADES signature usign BouncyCastle 1.59. I have to add messageDigest attribute but when I added it then my program fails.

Here is my code:

Signature signature = Signature.getInstance("SHA256withRSA", new BouncyCastleProvider());
byte[] test_data = "test".getBytes();
MessageDigest dig = MessageDigest.getInstance("SHA256", new BouncyCastleProvider());
byte[] digest = dig.digest(test_data);

byte[] privateKeyContent = archivePrivateKey.getBytes();
BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(privateKeyContent)));
Security.addProvider(new BouncyCastleProvider());
PEMParser pp = new PEMParser(br);
PEMKeyPair pemKeyPair = (PEMKeyPair) pp.readObject();
KeyPair kp = new JcaPEMKeyConverter().getKeyPair(pemKeyPair);
pp.close();

signature.initSign(kp.getPrivate());
signature.update(test_data);

byte[] certContent = archiveCertificateContent.getBytes();
PemFile pemFile = new PemFile(certContent);
X509CertificateHolder cert = new X509CertificateHolder(pemFile.getPemObject().getContent());
X509Certificate certificate = new JcaX509CertificateConverter().getCertificate(cert);

// Build CMS
List certList = new ArrayList();
CMSTypedData data = new CMSProcessableByteArray(signature.sign());
certList.add(certificate);
Store certs = new JcaCertStore(certList);


byte[] certHash = dig.digest(certificate.getEncoded());
AlgorithmIdentifier algId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256);
ESSCertIDv2 essCert = new ESSCertIDv2(algId, certHash);
SigningCertificateV2 scv2 = new SigningCertificateV2(new ESSCertIDv2[] { essCert });

ASN1EncodableVector signedAttributes = new ASN1EncodableVector();
signedAttributes.add(new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new DERSet(scv2)));
//signedAttributes.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(digest))));
signedAttributes.add(new Attribute(CMSAttributes.contentType, new DERSet(PKCSObjectIdentifiers.data)));

AttributeTable signedAttributesTable = new AttributeTable(signedAttributes);
signedAttributesTable.toASN1EncodableVector();

CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
JcaContentSignerBuilder contentSigner = new JcaContentSignerBuilder("SHA256withRSA");
contentSigner.setProvider(BC_PROVIDER);
SignerInfoGeneratorBuilder signerInfoBuilder = new SignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC_PROVIDER).build());
signerInfoBuilder.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(signedAttributesTable));

gen.addSignerInfoGenerator(signerInfoBuilder.build(contentSigner.build(kp.getPrivate()), cert));
gen.addCertificates(certs);
CMSSignedData signedData = gen.generate(data, true);

// Verify signature
Store store = signedData.getCertificates();
SignerInformationStore signers = signedData.getSignerInfos();
Collection c = signers.getSigners();
Iterator it = c.iterator();
while (it.hasNext()) {
  SignerInformation signer = (SignerInformation) it.next();
  Collection certCollection = store.getMatches(signer.getSID());
  Iterator certIt = certCollection.iterator();
  X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
  X509Certificate certFromSignedData = new JcaX509CertificateConverter().setProvider(BC_PROVIDER).getCertificate(certHolder);
  if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC_PROVIDER).build(certFromSignedData))) {
    System.out.println("Signature verified");
  } else {
    System.out.println("Signature verification failed");
  }
}

When I uncomment line 35: signedAttributes.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(digest)))); then I get error:

org.bouncycastle.cms.CMSSignerDigestMismatchException: message-digest attribute value does not match calculated value

I was trying a lot of options, configurations but the problem still remains. Any help will be very useful. Thanks


Solution

  • I haven't tried your code myself but I think the problem is with the message-digest your are adding as MessageDigest attribute is not what you are really putting in the SignedData. I see that you are signing the data first using JCA.

    Signature signature = Signature.getInstance("SHA256withRSA", new BouncyCastleProvider());
    byte[] test_data = "test".getBytes();
        signature.initSign(kp.getPrivate());
    signature.update(test_data);
    CMSTypedData data = new CMSProcessableByteArray(signature.sign());
    ...
    CMSSignedData signedData = gen.generate(data, true);
    

    Here you are putting the signature (and not the data, which is weird) to generate the CMSSignedData. So for the contentSigner, your signature is the actual data to be signed. However, in the MessageDigest attribute, you are putting the digest of the actual data. This, most probably, is causing the problem.

    Now coming to the more important question. Why are you signing the data using the above code in the first place? This seems wrong to me and probably not what you want. If your purpose is to generate a CMS signedData for the "test" data, you should use that directly in the signedDataGenerator and this will take care of the signing part.

    Please go through the RFC if you have not already done so.

    Hope this helps.