javapdfboxdigital-signaturex509certificatepades

pdfbox - document getting corrupted after adding signed attributes


I am trying to digitally sign document using pdfbox. I am using filter as FILTER_ADOBE_PPKLITE and subfilter as SUBFILTER_ETSI_CADES_DETACHED. For ETSI_CADES_Detached, a signing attribute is needs to be added. I am fetching signed hash and certificates from CSC> But after adding signing attribute, it is making the document corrupted. Sharing the screenshot for the reference

error image

1

Seems like hash is getting chagned. Sharing code for the reference.

PDDocument document = PDDocument.load(inputStream);

            outFile = File.createTempFile("signedFIle", ".pdf");

            Certificate[] certificateChain = retrieveCertificates(requestId, providerId, credentialId, accessToken);//Retrieve certificates from CSC.


            setCertificateChain(certificateChain);

            // sign
            FileOutputStream output = new FileOutputStream(outFile);
            IOUtils.copy(inputStream, output);

            // create signature dictionary
            PDSignature signature = new PDSignature();
//            signature.setType(COSName.SIG);


//        PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(null);

            int accessPermissions = SigUtils.getMDPPermission(document);
            if (accessPermissions == 1)
            {
                throw new IllegalStateException("No changes to the document are permitted due to DocMDP transform parameters dictionary");
            }

            

            signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
            signature.setName("Test Name");
//            signature.setLocation("Bucharest, RO");
//            signature.setReason("PDFBox Signing");
            signature.setSignDate(Calendar.getInstance());
            Rectangle2D humanRect = new Rectangle2D.Float(location.getLeft(), location.getBottom(), location.getRight(), location.getTop());

            PDRectangle rect = createSignatureRectangle(document, humanRect);

            SignatureOptions signatureOptions = new SignatureOptions();
            signatureOptions.setVisualSignature(createVisualSignatureTemplate(document, 0, rect, signature));
            signatureOptions.setPage(0);
            document.addSignature(signature, signatureOptions);


            ExternalSigningSupport externalSigning =
                    document.saveIncrementalForExternalSigning(output);

            InputStream content = externalSigning.getContent();



            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
            X509Certificate cert = (X509Certificate) certificateChain[0];
            gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));

            MessageDigest digest = MessageDigest.getInstance("SHA-256");

            // Use a buffer to read the input stream in chunks
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = content.read(buffer)) != -1) {
                digest.update(buffer, 0, bytesRead);
            }

            byte[] hashBytes = digest.digest();

            ESSCertIDv2 certid = new ESSCertIDv2(
                    new AlgorithmIdentifier(new ASN1ObjectIdentifier("*****")),
                    MessageDigest.getInstance("SHA-256").digest(cert.getEncoded())
            );

            SigningCertificateV2 sigcert = new SigningCertificateV2(certid);

            final DERSet attrValues = new DERSet(sigcert);
            Attribute attr = new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, attrValues);
            ASN1EncodableVector v = new ASN1EncodableVector();
            v.add(attr);
            v.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(hashBytes))));

            AttributeTable atttributeTable = new AttributeTable(v);
            //Create a standard attribute table from the passed in parameters - certhash
            CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(atttributeTable);


            final byte[] signedHash = signHash(requestId, providerId, accessToken, hashBytes); //Retrieve signed hash from CSC.

            ContentSigner contentSigner = new ContentSigner() {

                @Override
                public byte[] getSignature() {
                    return signedHash;
                }

                @Override
                public OutputStream getOutputStream() {
                    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                    byteArrayOutputStream.writeBytes(hashBytes);
                    return byteArrayOutputStream;
                }

                @Override
                public AlgorithmIdentifier getAlgorithmIdentifier() {
                    return new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.113549.1.1.11"));
                }
            };



            org.bouncycastle.asn1.x509.Certificate cert2 = org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
            JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());

//            RevocationInfoResponse revocationInfoResponse = sealingService.getRevocationInfo(requestId, accessToken, revocationInfoRequest);



            sigb.setSignedAttributeGenerator(attrGen);


//            sigb.setDirectSignature( true );
            gen.addSignerInfoGenerator(sigb.build(contentSigner, new X509CertificateHolder(cert2)));
            CMSTypedData msg = new CMSProcessableInputStream( inputStream);

            CMSSignedData signedData = gen.generate((CMSTypedData)msg, false);
            byte[] cmsSignature = signedData.getEncoded();

            inputStream.close();

            externalSigning.setSignature(cmsSignature);
            IOUtils.closeQuietly(signatureOptions);

            return new FileInputStream(outFile);

If I use subfilter as SUBFILTER_ADBE_PKCS7_DETACHED and don't add addtibutesTable, then it works fine. But for SUBFILTER_ETSI_CADES_DETACHED, attributes needs to be added.


Solution

  • In a comment you explained

    we fetch certificates and signedHash from some external provide (CSC). We don't have privateKey. So, after fetching signedHash from CSC, we are directly overriding the method getSignature of ContentSignature and returning that signedHash. ContentSigner cannot actually sign the bytes.

    ContentSigner can actually sign the bytes. You merely have to call CSC signing from within it.

    Simplifying your code a bit you can do that as follows:

    try (
        OutputStream output = new FileOutputStream(outFile);
        PDDocument document = PDDocument.load(resource)   
    ) {
        PDSignature signature = new PDSignature();
    
        signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
        signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
        signature.setName("Test Name");
        signature.setSignDate(Calendar.getInstance());
    
        SignatureOptions signatureOptions = new SignatureOptions();
        signatureOptions.setPage(0);
    
        document.addSignature(signature, signatureOptions);
        ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(output);
    
        // retrieve signer certificate and its chain
        Certificate[] certificateChain = retrieveCertificates(requestId, providerId, credentialId, accessToken);
        X509Certificate cert = (X509Certificate) certificateChain[0];
    
        // build signed attribute table generator and SignerInfo generator builder
        ESSCertIDv2 certid = new ESSCertIDv2(
                new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256),
                MessageDigest.getInstance("SHA-256").digest(cert.getEncoded())
        );
        SigningCertificateV2 sigcert = new SigningCertificateV2(certid);
        Attribute attr = new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new DERSet(sigcert));
    
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(attr);
        AttributeTable atttributeTable = new AttributeTable(v);
        CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(atttributeTable);
    
        org.bouncycastle.asn1.x509.Certificate cert2 = org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
        JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());
        sigb.setSignedAttributeGenerator(attrGen);
    
        // create ContentSigner that signs by calling the CSC endpoint
        ContentSigner contentSigner = new ContentSigner() {
            private MessageDigest digest = MessageDigest.getInstance("SHA-256");
            private OutputStream stream = OutputStreamFactory.createStream(digest);
    
            @Override
            public byte[] getSignature() {
                try {
                    byte[] hash = digest.digest();
                    byte[] signedHash = signHash(requestId, providerId, accessToken, hash);
                    return signedHash;
                } catch (Exception e) {
                    throw new RuntimeException("Exception while signing", e);
                }
            }
    
            @Override
            public OutputStream getOutputStream() {
                return stream;
            }
    
            @Override
            public AlgorithmIdentifier getAlgorithmIdentifier() {
                return new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.113549.1.1.11"));
            }
        };
    
        // create the SignedData generator and execute
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
        gen.addSignerInfoGenerator(sigb.build(contentSigner, new X509CertificateHolder(cert2)));
    
        CMSTypedData msg = new CMSTypedDataInputStream(externalSigning.getContent());
        CMSSignedData signedData = gen.generate(msg, false);
    
        byte[] cmsSignature = signedData.getEncoded();
        externalSigning.setSignature(cmsSignature);
    }
    

    (RemoteSigning test method testSignLikeSkdjksDfkslImproved)