What exactly does signer.verify(signerInformationVerifier) verify?

J S :

What exactly does signer.verify(signerInformationVerifier) in the code below check?

Is it correct to say it compares the hash of the message against the signature (which I understand is also the hash of the message but encrypted with the private key of the sender). Before comparing it uses the public key from the certificate to "decrypt" the signature. Both should give the same result if the signature was using the certificate in question. In that case the objective of "verify" is to see the text did not change/the signature corresponds to the certificate. "verify" could also check if the certificate was not expired at time of signing...
I learned so far that "verify" like coded below does not check if the certificate is trusted.

I tried javadoc, google but could not find an answer.

SignerInformationStore signers = cmsSignedData.getSignerInfos();
// variable "it" iterates all signers
Iterator<?> it = signers.getSigners().iterator();
while (it.hasNext){
    SignerInformation signer = (SignerInformation) it.next();
    // get all 
    CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    InputStream in = new  ByteArrayInputStream(certificateHolder.getEncoded());
    X509Certificate cert2 = (X509Certificate) certFactory.generateCertificate(in);

    SignerInformationVerifier signerInformationVerifier = new JcaSimpleSignerInfoVerifierBuilder().build(cert2);
    if (signer.verify(signerInformationVerifier)){
        System.out.println("PDF signature verification is correct");
    } else { System.out.println ("PDF signature verification failed");}

it returns true for my signed pdf documents. I have tested with a pdf that had a trusted signature from a trusted CA 1 (registered as such in keystore) and one from a CA 2 which was not registered as trusted in keystore.

Any explanation/help is much appreciated.

dave_thompson_085 :

The SignerInformationVerifier produced from JcaSimpleSignerInfoVerifierBuilder...build(cert) (which mostly wraps a ContentVerifierProvider) is driven by SignerInformation.verify as follows:

  • since the build(cert) overload and not the build(publickey) overload was used, if the (must-be-)authenticated/signed attribute signingTime is present, it checks that signingTime value is within the certificate's validity period

  • if authenticated/signed attributes are present, it checks them, including checking that the digest of the (received) content matches the digest attribute, and checks that the signature verifies as the signature of the signed attributes under the publickey in the cert (and the algorithms specified in the message and SignerInfo)

  • otherwise (no signed attributes) it checks that the signature verifies as the signature of the (received) content under the publickey in the cert (and the algorithms specified in the message and SignerInfo)

If you use the JcaSimpleSignerInfoVerifierBuilder...build(cert.getPublicKey()) overload instead, the only difference is that it doesn't check the signingTime, if present, against the cert validity period.

It could check the attributes of the signer against the attributes of the certificate (Subject only? Or also serial number, Issuer?)

It does not. Your code should; see org.bouncycastle.cms.CMSSignedDataParser javadoc for a minimal example, which uses the Store of certs in the message to find a cert matching the 'SID' (SignerIdentification) in the SignerInfo. This 'SID' is usually the pair of Issuer and Serial, but in CMS can be the SubjectKeyIdentifier. The standard PKCS7/CMS format does not specify the Subject name for a signer (or SubjectAltName, for that matter) so any method of determining that and using it to find or check the cert would be nonstandard. It is quite possible however that once you find the cert by other means, you might want to use its Subject (or SAN) as useful information in processing the signed content.

It does not check at all that the cert is valid/trusted. The example notes this as an issue.

If you want to take the simple approach you can simply check if the cert is in your truststore or otherwise directly configured, and that it has not expired, possibly skipping the latter if the SignerInformation contains a signed attribute signingTime which was checked as above. This leaves you at risk if the signer cert is revoked because of key compromise, or the cert was determined fraudulent; you will continue to accept signatures from the thief or fraudster. Also you are dependent on yourself or someone updating the local truststore or config after manually verifying every new signing cert, and if you can tricked into doing so wrongly you will again accept bogus signatures.

If you want to really validate the cert, see the Java PKI Programmer's Guide (or CertPathProgGuide in some older versions) and javadoc for related classes primarily java.security.cert.CertPathValidator.

In my understanding the signature is a private key encryption of the hash (message digest) of the pdf document. The reason seems to be that this way signing is much faster. The function signer.verify(signerInformationVerifier) then most likely decrypts the signed hashed message digest using the public key of the certificate and compares it against the hashed message digest (using the hash function defined in the certificate) - it should give the same result.

Mostly not. Signing is not encryption. For one algorithm, RSA, there is a mathematical symmetry between the operations at the core of encryption and signing, which initially seduced people into describing signing as 'encrypting with the privatekey', but treating it this way was soon found to lead to vulnerabilities, and actual encryption and signature schemes now used are significantly different and cannot be interchanged, although a lot of people who just copy things they found on the web that are copies of things someone found 10 years ago that were copies of things someone else found 20 years ago keep repeating the error. Java crypto doesn't help, because it was specified back in the 1990s, and included the misfeature that a Cipher.init for RSA accepts arguments to 'encrypt' with privatekey or 'decrypt' with publickey and actually, silently, does PKCS1 signing or recovery instead of encrypting and decrypting. For other algorithms like DSA, ECDSA, and (now) EdDSA, there is no similarity or relationship of any kind between signing and encrypting. This is discussed in dozens of Qs on crypto.SX and security.SX if you are interested.

It is true that most signature schemes now, and all supported by PKCS7/CMS, are hybrid: the 'bulk' data is first securely hashed (also called digested), and then the signature is computed using the (small) hash/digest instead of the bulk data. If you meant speeds of signing and verifying, that varies; for RSA as implemented in the modern era (since about 1980) signing is much slower than verifying; for DSA and ECDSA verifying is slightly slower than signing.

For PKCS7/CMS, as implied by the description above, the signature isn't normally taken of the 'content' (your PDF file). Instead the hash of the content, plus some other metadata, is included in a data structure called authenticatedAttributes in PKCS7 and renamed signedAttr[ibute]s in CMS, and that is what is covered by the publickey signature (including hash). But there is a backward-compatibility option where signedAttributes is not used and the publickey signature is applied to the content hash. Exactly what the signer and verifier do in both cases is described in RFC 5652 et pred sections 5.4-5.6 although you may need all of section 5 for context. Also the hash algorithm used is not defined in the certificate, but in the PKCS7/CMS message; however, the publickey algorithm and size (and thus strength) are defined in the certificate if used (which is preferred but not actually required), and good practice is to choose the hash to match, reasonably closely, the publickey strength.

For RSA the verifier does 'recover' (not decrypt) the hash from the signature and compare it to the newly computed hash of the received data -- either the content, or the signedAttributes, as above. Technically this is done in a JCA provider class for RSA called (indirectly) by the BouncyCastle classes, just as the quite different verify operations for DSA and ECDSA are done in their provider classes, but the result is the same as far as you're concerned.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=133878&siteId=1