Android_dep
Android_dep

Reputation: 101

How to generate PKCS#7 signature from digest?

I want to sign the pdf using pdf digest. I have created the hash using below code,

byte[] buffer = new byte[1024];
int numOfBytesRead =0;
MessageDigest md = null;
md = MessageDigest.getInstance("SHA256","BC");
while((numOfBytesRead = content.read(buffer)) != -1 ){
     md.update(buffer, 0, numOfBytesRead);
}
byte[] digest = md.digest();

At the end I need to attach this signature to my pdf. I have found one solution Create pkcs7 signature from file digest, but the algorithm used in the link is SHA256withRSA. My privatekey is genearted using EC algorithm and I need to use SHA256withECDSA.Is it possible to just sign the Hash using SHA256withECDSA and attach the signature to the pdf using PDFBox ExternalSigning Interface.

Upvotes: 1

Views: 4739

Answers (2)

mkl
mkl

Reputation: 96039

There are several situations in which Adobe calls a signer's certificate invalid even though apparently it is valid; in the case at hand in particular:

  • Key usage or Extended key usage values not appropriate
  • PAdES signature misses an ESS signing-certificate-v2 attribute

Key usage or Extended key usage values not appropriate

This is based on the information the OP first published as an answer

I tried below code still the pdf says Signature is invalid. Can you please check the code,

[...]

I have attached the pdf . Pdf file created

Indeed, Adobe Reader says the signature is invalid, but look more closely:

Signature panel

It says "Document has not been modified since this signature was applied" - This means that the signature is mathematically correct!

The issue is that the "Signer's certificate is invalid", and the reason for this can be seen when digging into the signature property dialogues:

Certificate Details

Thus, the problem is that your signer certificate is Not valid for usage.

This is due to the highlighted attribute, while the Key Usage Digital Signature is ok, the "Extended key usage" 1.3.6.1.5.5.8.2.2 (OID for IPSEC Protection) is not!

According to the Adobe Digital Signatures Guide for IT, Adobe Acrobat accepts only

  • one or more of the following Key usage values (if any)

    • nonRepudiation
    • signTransaction (11.0.09 only)
    • digitalSignature (11.0.10 and later)
  • and one or more of the following Extended key usage values (if any)

    • emailProtection
    • codeSigning
    • anyExtendedKeyUsage
    • 1.2.840.113583.1.1.5 (Adobe Authentic Documents Trust)

Due to its IPSEC Protection extended key usage value, therefore, your certificate is not considered valid for signing PDF documents.

This probably only is an issue in legacy ISO 32000-1 signatures, probably not in PAdES signatures.

PAdES signature misses an ESS signing-certificate-v2 attribute

This is based on the information the OP first published as an answer

I have created 2 pdf files, PDFA is signed using the digest of the pdf content with below code,

[...]

PDFA

PDFB is created with the same private key and certificate, but instead of digest I am using pdf document content directly which gives me valid signed pdf, PDFB code below,

[...]

PDFB

I think something is missing in the signing part of PDFA which I couldn't figure out.

Here the main difference is not whether you explicitly calculate the hash yourself or allow it to be calculated implicitly, the main difference is that the signature in PDFB includes an ESS signing-certificate-v2 attribute while the one in PDFA does not. This attribute is generated between

//PAdES - PDF Advanced Electronic Signature

and

//PAdES-end

As the comments already hint, this is only necessary for PAdES signatures, not for legacy ISO 32000-1 ones. The answer the OP took his original code from referred to creating a legacy ISO 32000-1 signature (and, therefore, works alright) while the OP creates a PAdES signature.

The presence of an ESS signing certificate attribute is required by the PAdES specification ETSI EN 319 142-1:

e) Generators shall use either the signing certificate or the signing-certificate v2 attribute, depending on the hash function, in accordance with ETSI EN 319 122-1.

(ETSI EN 319 142-1, section 6.3 PAdES baseline signatures)

It references the CAdES specification ETSI EN 319 122-1 which in turn requires

h) Requirement for SPO: ESS signing-certificate. The ESS signing-certificate attribute shall be used if the SHA-1 hash algorithm is used.

i) Requirement for SPO: ESS signing-certificate-v2. The ESS signing-certificate-v2 attribute shall be used when another hash algorithms than SHA-1 is used.

(ETSI EN 319 122-1, section 6.3 Requirements on components and services)

Upvotes: 1

Android_dep
Android_dep

Reputation: 101

I tried below code still the pdf says Signature is invalid. Can you please check the code,

System.out.println("Hash Signing started");
    List<Certificate> certList = getFormatCertificate(strCertificate);
    PrivateKey privateKey;
    CMSSignedData s = null;
    Security.addProvider(new BouncyCastleProvider());
    byte[] signature = null;
    try {
        privateKey = loadPrivateKey(strPrivatekey);
        byte[] buffer = new byte[1024];
        int numOfBytesRead =0;
        MessageDigest md = null;
        md = MessageDigest.getInstance("SHA256","BC");
        while((numOfBytesRead = content.read(buffer)) != -1 ){
            md.update(buffer, 0, numOfBytesRead);
        }
        byte[] digest = md.digest();

        // Separate signature container creation step
        JcaCertStore certs = new JcaCertStore(certList);

        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

        Attribute attr = new Attribute(CMSAttributes.messageDigest,
                new DERSet(new DEROctetString(digest)));

        ASN1EncodableVector v = new ASN1EncodableVector();

        v.add(attr);

        SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
                .setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));

        //AlgorithmIdentifier sha256withECDSA = new DefaultSignatureAlgorithmIdentifierFinder().find(signerAlgorithm);

        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        InputStream in = new ByteArrayInputStream(certList.get(certList.size()-1).getEncoded());
        X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);

        gen.addSignerInfoGenerator(builder.build(
                new JcaContentSignerBuilder(signerAlgorithm).build(privateKey),
                new JcaX509CertificateHolder(cert)));
        //DErse
//      gen.addSignerInfoGenerator(builder.build(
//              new JcaContentSignerBuilder(sha256withRSA,
//                      new DefaultDigestAlgorithmIdentifierFinder().find(sha256withRSA))
//                              .build(PrivateKeyFactory.createKey(privateKey.getEncoded())),
//              new JcaX509CertificateHolder(cert)));

        gen.addCertificates(certs);

        s = gen.generate(new CMSAbsentContent(), false);
        System.out.println("Hash sign completed");
        signature = s.getEncoded();// ConstructEcdsaSigValue(s.getEncoded());
    } catch (GeneralSecurityException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("GeneralSecurityException ::"+e.toString());
    } catch (OperatorCreationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("OperatorCreationException ::"+e.toString());
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("IOException ::"+e.toString());
    } catch (CMSException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("CMSException ::"+e.toString());
    }finally{
        return signature;
    }

I have attached the pdf . Pdf file created

@Mkl/Tilman : I have created 2 pdf files, PDFA is signed using the digest of the pdf content with below code,

System.out.println("Hash Signing started");
    List<Certificate> certList = getFormatCertificate(strCertificate);
    PrivateKey privateKey;
    CMSSignedData s = null;
    Security.addProvider(new BouncyCastleProvider());
    byte[] signature = null;
    try {
        privateKey = loadPrivateKey(strPrivatekey);

        /*byte[] buffer = new byte[1024];
        int numOfBytesRead =0;
        MessageDigest md = null;
        //md = MessageDigest.getInstance("SHA256","BC");
        md = MessageDigest.getInstance("SHA-256");
        while((numOfBytesRead = content.read(buffer)) != -1 ){
            md.update(buffer, 0, numOfBytesRead);
        }
        byte[] digest = md.digest();*/
        MessageDigest md = MessageDigest.getInstance("SHA256", "BC");
        byte[] digest = md.digest(IOUtils.toByteArray(content));

        // Separate signature container creation step
        JcaCertStore certs = new JcaCertStore(certList);

        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

        Attribute attr = new Attribute(CMSAttributes.messageDigest,
                new DERSet(new DEROctetString(digest)));

        ASN1EncodableVector v = new ASN1EncodableVector();

        v.add(attr);

        SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
                .setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));

        //AlgorithmIdentifier sha256withECDSA = new DefaultSignatureAlgorithmIdentifierFinder().find(signerAlgorithm);

        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        InputStream in = new ByteArrayInputStream(certList.get(certList.size()-1).getEncoded());
        X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);

        gen.addSignerInfoGenerator(builder.build(
                new JcaContentSignerBuilder(signerAlgorithm).build(privateKey),
                new JcaX509CertificateHolder(cert)));
        //DErse
//      gen.addSignerInfoGenerator(builder.build(
//              new JcaContentSignerBuilder(sha256withRSA,
//                      new DefaultDigestAlgorithmIdentifierFinder().find(sha256withRSA))
//                              .build(PrivateKeyFactory.createKey(privateKey.getEncoded())),
//              new JcaX509CertificateHolder(cert)));

        gen.addCertificates(certs);

        s = gen.generate(new CMSAbsentContent(), false);
        System.out.println("Hash sign completed");
        signature = s.getEncoded();// ConstructEcdsaSigValue(s.getEncoded());
    } catch (GeneralSecurityException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("GeneralSecurityException ::"+e.toString());
    } catch (OperatorCreationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("OperatorCreationException ::"+e.toString());
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("IOException ::"+e.toString());
    } catch (CMSException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.out.println("CMSException ::"+e.toString());
    }finally{
        return signature;
    }

PDFA

PDFB is created with the same private key and certificate, but instead of digest I am using pdf document content directly which gives me valid signed pdf, PDFB code below,

SignatureInterface signatureInterface = new SignatureInterface() {
                    @SuppressWarnings("rawtypes")
                    @Override
                    public byte[] sign(InputStream content) throws IOException {
                        try {
                            byte[] certificateByte = null;

                            Store certs = new JcaCertStore(certificates);

                            //PAdES - PDF Advanced Electronic Signature
                            //ESS - Enhanced Security Services
                            //ASN1 - Abstract Syntax Notation One-standard interface description language for defining data structures that can be serialized and deserialized in a cross-platform way
                            // Generating certificate hash
                            MessageDigest md = MessageDigest.getInstance("SHA-256");
                            md.update(certificates.get(certificates.size()-1).getEncoded());
                            byte[] certHash = md.digest();
                            // Generating certificate hash ends
                            System.out.println("Cert hash generated");
                            //ESSCertIDv2 identifies the certificate from the hash
                            ESSCertIDv2 essCert1 =
                                    new ESSCertIDv2(new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256), certHash);
                            ESSCertIDv2[] essCert1Arr =
                                    {
                                            essCert1
                                    };
                            SigningCertificateV2 scv2 = new SigningCertificateV2(essCert1Arr);
                            Attribute certHAttribute =
                                    new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new DERSet(scv2));
                            ASN1EncodableVector v = new ASN1EncodableVector();
                            v.add(certHAttribute);

                            AttributeTable at = new AttributeTable(v);

                            //Create a standard attribute table from the passed in parameters - certhash
                            CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(at){
                                protected Hashtable createStandardAttributeTable(Map parameters)
                                {
                                    Hashtable result = super.createStandardAttributeTable(parameters);
                                    result.remove(CMSAttributes.signingTime);
                                    return result;
                                }
                            };
                            //PAdES-end
                            System.out.println("CMSAttributeTableGenerator generated");
                            SignerInfoGeneratorBuilder genBuild =
                                    new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider());
                            genBuild.setSignedAttributeGenerator(attrGen);
                            //Get single certificate
                            org.spongycastle.asn1.x509.Certificate certas1 = org.spongycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(certificates.get(certificates.size()-1).getEncoded()));
                            // ContentSigner interface creates SHA256withECDSA signer using PvtKey
                            ContentSigner sha1Signer = new JcaContentSignerBuilder(signerAlgorithm).build(privateKey);
                            //Creates SignerInfoGenerator using X.509 cert and ContentSigner
                            SignerInfoGenerator sifGen = genBuild.build(sha1Signer, new X509CertificateHolder(certas1));

                            // CMSSignedDataGenerator generates a pkcs7-signature message 
                            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

                            gen.addCertificates(certs);
                            gen.addSignerInfoGenerator(sifGen);
                            //Creates CMS message from PDF
                            CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
                            //Generate a CMS Signed Data object which can be carrying a detached CMS signature
                            //msg - content to be signed
                            CMSSignedData signedData = gen.generate(msg, false);
                            System.out.println("CMSSignedData is done");
                            return signedData.getEncoded();
                        } catch (GeneralSecurityException e) {
                            throw new IOException(e);
                        } catch (CMSException e) {
                            throw new IOException(e);
                        } catch (OperatorCreationException e) {
                            throw new IOException(e);
                        }
                }

            };
            System.out.println("CMSSignedData is done2");
            PDDocument pdDocument =  PDDocument.load(inputfile);

            System.out.println("pdDocument loaded");
            pdDocument.addSignature(signature, signatureInterface);

PDFB

I think something is missing in the signing part of PDFA which I couldn't figure out.

Upvotes: 0

Related Questions