iText pdf 7 : Digitally sign a pdf

67 Views Asked by At

I am working on implementing a solution for signing a PDF using i text pdf 7. The code is working fine but when the document is opened in adobe it gives "Document has been altered or corrupted since it was signed"

I am not able to figure out on what I am missing

I have attached the code and generated pdf sample as well

public void testPoints() {
        try{
            BouncyCastleProvider providerBC = new BouncyCastleProvider();
            Security.addProvider(providerBC);

            InputStream inStream = new FileInputStream("/Users/abhinav/Work/dev/client-wca.p12");

            /* Reading the certificate from a tempory store */
            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(inStream, "!234Qwer".toCharArray());
            String alias = ks.aliases().nextElement();
            PrivateKey key = (PrivateKey)ks.getKey(alias, "!234Qwer".toCharArray());
            Certificate certificate = (X509Certificate) ks.getCertificate(alias);

            // create PCKS7 for getting attributes
            Certificate[] certificates= Arrays.asList(certificate).toArray(new Certificate[0]);

            //CREATE an empty signature placeholder
            PreSignatureContainerToSign preSignatureContainerToSign = emptySignature("/Users/abhinav/Work/dev/dummy.pdf", "/Users/abhinav/Work/dev/SL_SIGNED.pdf",
                    "sig", certificates, key);

//            byte[] signature = sign(preSignatureContainerToSign.hash, key);

            //Sign the PDF with original has
            createSignature("/Users/abhinav/Work/dev/SL_SIGNED.pdf", "/Users/abhinav/Work/dev/SL_SIGNED_f.pdf",
                    "sig", preSignatureContainerToSign.hash, certificates, key, preSignatureContainerToSign);
        }
        catch (Exception e){
            System.out.println();
            e.printStackTrace();
        }
    }

private PreSignatureContainerToSign emptySignature(String src, String dest, String fieldname, Certificate[] chain, PrivateKey key)
            throws IOException, GeneralSecurityException {
        PdfReader reader = new PdfReader(src);
        PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties());
        PdfSignatureAppearance appearance = signer.getSignatureAppearance();
        appearance.setPageRect(new Rectangle(100, 400, 200, 100));
        appearance.setPageNumber(1);
        appearance.setCertificate(chain[0]);
//        appearance.setReason("For test");
//        appearance.setLocation("HKSAR");
        signer.setFieldName(fieldname);

        /*
         * ExternalBlankSignatureContainer constructor will create the PdfDictionary for
         * the signature information and will insert the /Filter and /SubFilter values
         * into this dictionary. It will leave just a blank placeholder for the
         * signature that is to be inserted later.
         */
        PreSignatureContainerToSign external = new PreSignatureContainerToSign(PdfName.Adobe_PPKLite,
                PdfName.Adbe_pkcs7_detached);
        external.setCertificates(chain);
        external.setKey(key);

        // Sign the document using an external container.
        // 8192 is the size of the empty signature placeholder.
        signer.signExternalContainer(external, 8192);
        return external;
    }

    private void createSignature(String src, String dest, String fieldName, byte[] sig, Certificate[] chain,
                                 PrivateKey key, PreSignatureContainerToSign preSignatureContainerToSign)
            throws IOException, GeneralSecurityException {
        PdfReader reader = new PdfReader(src);
        try (FileOutputStream os = new FileOutputStream(dest)) {
            PdfSigner signer = new PdfSigner(reader, os, new StampingProperties().useAppendMode());

            PreSignatureContainer external = new PreSignatureContainer(PdfName.Adobe_PPKLite,
                    PdfName.Adbe_pkcs7_detached, true, sig,
                    preSignatureContainerToSign.hash);
            external.setCertificates(chain);
            external.setKey(key);
            // Signs a PDF where space was already reserved. The field must cover the whole
            // document.
            PdfSigner.signDeferred(signer.getDocument(), fieldName, os, external);
        }
    }

    public class PreSignatureContainer implements IExternalSignatureContainer {
        private PdfDictionary sigDic;
        private PrivateKey key;
        private Certificate[] certificates;
        private byte hash[];
        private byte[] signature;
        private byte[] original;
        public void setCertificates(Certificate[] certificates) {
            this.certificates = certificates;
        }

        public void setKey(PrivateKey key) {
            this.key = key;
        }

        public PreSignatureContainer(PdfName filter, PdfName subFilter, boolean flag, byte[] signature,  byte[] original) {
            sigDic = new PdfDictionary();
            sigDic.put(PdfName.Filter, filter);
            sigDic.put(PdfName.SubFilter, subFilter);
            this.signature = signature;
            this.original = original;
        }

        @Override
        public byte[] sign(InputStream data) throws GeneralSecurityException {
            BouncyCastleDigest digest = new BouncyCastleDigest();

            try {
                var sgn = new PdfPKCS7(null, this.certificates,
                        DigestAlgorithms.SHA256, null, digest, false);
                sgn.setExternalDigest(signature,null, "RSA");
                return sgn.getEncodedPKCS7(original, PdfSigner.CryptoStandard.CMS,
                        null, null, null);
            } catch (Exception e) {
                throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
            }
        }

        @Override
        public void modifySigningDictionary(PdfDictionary signDic) {
            signDic.putAll(sigDic);
        }

        public byte[] getHash() {
            return hash;
        }
    }

    public class PreSignatureContainerToSign implements IExternalSignatureContainer {
        private PdfDictionary sigDic;
        private PrivateKey key;
        private Certificate[] certificates;
        private byte hash[];
        private byte original[];

        public void setCertificates(Certificate[] certificates) {
            this.certificates = certificates;
        }

        public void setKey(PrivateKey key) {
            this.key = key;
        }

        public PreSignatureContainerToSign(PdfName filter, PdfName subFilter) {
            sigDic = new PdfDictionary();
            sigDic.put(PdfName.Filter, filter);
            sigDic.put(PdfName.SubFilter, subFilter);
        }

        @Override
        public byte[] sign(InputStream data) throws GeneralSecurityException {
            String hashAlgorithm = "SHA256";

            BouncyCastleDigest digest = new BouncyCastleDigest();
            try {
                var sgn = new PdfPKCS7(null, this.certificates, DigestAlgorithms.SHA256, null,
                        new BouncyCastleDigest(), false);
                original = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
                // get attributes
                byte [] docsBytes = sgn.getAuthenticatedAttributeBytes(original,
                        PdfSigner.CryptoStandard.CMS,
                        null, null);

                this.hash = DigestAlgorithms.digest(new ByteArrayInputStream(docsBytes), digest.getMessageDigest(hashAlgorithm));
                return  new byte[0];
            } catch (IOException e) {
                throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
            }
        }

        @Override
        public void modifySigningDictionary(PdfDictionary pdfDictionary) {
            pdfDictionary.putAll(sigDic);
        }

    }

EDIT

Updating the code as it being used to generate the file

public void testPoints() {
        try{
            BouncyCastleProvider providerBC = new BouncyCastleProvider();
            Security.addProvider(providerBC);

            InputStream inStream = new FileInputStream("/Users/abhinav/Work/dev/client-wca.p12");

            /* Reading the certificate from a tempory store */
            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(inStream, "!234Qwer".toCharArray());
            String alias = ks.aliases().nextElement();
            PrivateKey key = (PrivateKey)ks.getKey(alias, "!234Qwer".toCharArray());
            Certificate certificate = (X509Certificate) ks.getCertificate(alias);

            // create PCKS7 for getting attributes
            Certificate[] certificates= Arrays.asList(certificate).toArray(new Certificate[0]);

            //CREATE an empty signature placeholder
            PreSignatureContainerToSign preSignatureContainerToSign = emptySignature("/Users/abhinav/Work/dev/dummy.pdf", "/Users/abhinav/Work/dev/SL_SIGNED.pdf",
                    "sig", certificates, key);

            byte[] signature = sign(preSignatureContainerToSign.hash, key);

            //Sign the PDF with original has
            createSignature("/Users/abhinav/Work/dev/SL_SIGNED.pdf", "/Users/abhinav/Work/dev/SL_SIGNED_f.pdf",
                    "sig", signature, certificates, key, preSignatureContainerToSign);
        }
        catch (Exception e){
            System.out.println();
            e.printStackTrace();
        }
    }

    public static byte[] sign(byte[] plainHash, PrivateKey privateKey) throws Exception {
        PrivateKeySignature signature = new PrivateKeySignature(privateKey, "SHA256", "BC");
        return signature.sign(plainHash);
    }

    private PreSignatureContainerToSign emptySignature(String src, String dest, String fieldname, Certificate[] chain, PrivateKey key)
            throws IOException, GeneralSecurityException {
        PdfReader reader = new PdfReader(src);
        PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties());
        PdfSignatureAppearance appearance = signer.getSignatureAppearance();
        appearance.setPageRect(new Rectangle(100, 400, 200, 100));
        appearance.setPageNumber(1);
        appearance.setCertificate(chain[0]);
//        appearance.setReason("For test");
//        appearance.setLocation("HKSAR");
        signer.setFieldName(fieldname);

        /*
         * ExternalBlankSignatureContainer constructor will create the PdfDictionary for
         * the signature information and will insert the /Filter and /SubFilter values
         * into this dictionary. It will leave just a blank placeholder for the
         * signature that is to be inserted later.
         */
        PreSignatureContainerToSign external = new PreSignatureContainerToSign(PdfName.Adobe_PPKLite,
                PdfName.Adbe_pkcs7_detached);
        external.setCertificates(chain);
        external.setKey(key);

        // Sign the document using an external container.
        // 8192 is the size of the empty signature placeholder.
        signer.signExternalContainer(external, 8192);
        return external;
    }

    private void createSignature(String src, String dest, String fieldName, byte[] sig, Certificate[] chain,
                                 PrivateKey key, PreSignatureContainerToSign preSignatureContainerToSign)
            throws IOException, GeneralSecurityException {
        PdfReader reader = new PdfReader(src);
        try (FileOutputStream os = new FileOutputStream(dest)) {
            PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());

            PreSignatureContainer external = new PreSignatureContainer(PdfName.Adobe_PPKLite,
                    PdfName.Adbe_pkcs7_detached, true, sig,
                    preSignatureContainerToSign.original, preSignatureContainerToSign.getSgn());
            external.setCertificates(chain);
            external.setKey(key);
            // Signs a PDF where space was already reserved. The field must cover the whole
            // document.
            PdfSigner.signDeferred(signer.getDocument(), fieldName, os, external);
        }
    }

    public class PreSignatureContainer implements IExternalSignatureContainer {
        private PdfDictionary sigDic;
        private PrivateKey key;
        private PdfPKCS7 sgn;
        private Certificate[] certificates;
        private byte hash[];
        private byte[] signature;
        private byte[] original;
        public void setCertificates(Certificate[] certificates) {
            this.certificates = certificates;
        }

        public void setKey(PrivateKey key) {
            this.key = key;
        }

        public PreSignatureContainer(PdfName filter, PdfName subFilter, boolean flag, byte[] signature,  byte[] original,
                                     PdfPKCS7 sgn) {
            sigDic = new PdfDictionary();
            sigDic.put(PdfName.Filter, filter);
            sigDic.put(PdfName.SubFilter, subFilter);
            this.signature = signature;
            this.original = original;
            this.sgn = sgn;
        }

        @Override
        public byte[] sign(InputStream data) throws GeneralSecurityException {
            BouncyCastleDigest digest = new BouncyCastleDigest();

            try {
                var sgn = new PdfPKCS7(null, this.certificates,
                        DigestAlgorithms.SHA256, null, digest, false);
                this.sgn.setExternalDigest(signature,null, "RSA");
                return this.sgn.getEncodedPKCS7(original, PdfSigner.CryptoStandard.CMS,
                        null, null, null);

            } catch (Exception e) {
                throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
            }
        }

        @Override
        public void modifySigningDictionary(PdfDictionary signDic) {
            signDic.putAll(sigDic);
        }

        public byte[] getHash() {
            return hash;
        }
    }

    public class PreSignatureContainerToSign implements IExternalSignatureContainer {
        private PdfDictionary sigDic;
        private PrivateKey key;
        private PdfPKCS7 sgn;
        private Certificate[] certificates;
        private byte hash[];
        private byte original[];

        public PdfPKCS7 getSgn() {
            return sgn;
        }

        public void setCertificates(Certificate[] certificates) {
            this.certificates = certificates;
        }

        public void setKey(PrivateKey key) {
            this.key = key;
        }

        public PreSignatureContainerToSign(PdfName filter, PdfName subFilter) {
            sigDic = new PdfDictionary();
            sigDic.put(PdfName.Filter, filter);
            sigDic.put(PdfName.SubFilter, subFilter);
        }

        @Override
        public byte[] sign(InputStream data) throws GeneralSecurityException {
            String hashAlgorithm = "SHA256";
            BouncyCastleDigest digest = new BouncyCastleDigest();
            try {
                this.sgn = new PdfPKCS7(null, this.certificates, DigestAlgorithms.SHA256, null,
                        new BouncyCastleDigest(), false);
//                original = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
                // get attributes
                this.original = sgn.getAuthenticatedAttributeBytes(DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm)),
                        PdfSigner.CryptoStandard.CMS,
                        null, null);
                this.hash = DigestAlgorithms.digest(new ByteArrayInputStream(this.original), digest.getMessageDigest(hashAlgorithm));
                return  new byte[0];
            } catch (IOException e) {
                throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
            }
        }

        @Override
        public void modifySigningDictionary(PdfDictionary pdfDictionary) {
            pdfDictionary.putAll(sigDic);
        }

    }

EDIT

Fixed the signed attributes and remove the sign simulation using the private key

public void testPoints() {
        try{
            BouncyCastleProvider providerBC = new BouncyCastleProvider();
            Security.addProvider(providerBC);

            InputStream inStream = new FileInputStream("/Users/abhinav/Work/dev/client-wca.p12");

            /* Reading the certificate from a tempory store */
            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(inStream, "!234Qwer".toCharArray());
            String alias = ks.aliases().nextElement();
            PrivateKey key = (PrivateKey)ks.getKey(alias, "!234Qwer".toCharArray());
            Certificate certificate = (X509Certificate) ks.getCertificate(alias);

            // create PCKS7 for getting attributes
            Certificate[] certificates= Arrays.asList(certificate).toArray(new Certificate[0]);

            //CREATE an empty signature placeholder
            PreSignatureContainerToSign preSignatureContainerToSign = emptySignature("/Users/abhinav/Work/dev/dummy.pdf", "/Users/abhinav/Work/dev/SL_SIGNED.pdf",
                    "sig", certificates, key);

//            byte[] signature = sign(preSignatureContainerToSign.hash, key);

            //Sign the PDF with original has
            createSignature("/Users/abhinav/Work/dev/SL_SIGNED.pdf", "/Users/abhinav/Work/dev/SL_SIGNED_f.pdf",
                    "sig", preSignatureContainerToSign.hash, certificates, preSignatureContainerToSign);
        }
        catch (Exception e){
            System.out.println();
            e.printStackTrace();
        }
    }

    public static byte[] sign(byte[] plainHash, PrivateKey privateKey) throws Exception {
        PrivateKeySignature signature = new PrivateKeySignature(privateKey, "SHA256", "BC");
        return signature.sign(plainHash);
    }

    private PreSignatureContainerToSign emptySignature(String src, String dest, String fieldname, Certificate[] chain, PrivateKey key)
            throws IOException, GeneralSecurityException {
        PdfReader reader = new PdfReader(src);
        PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties());
        PdfSignatureAppearance appearance = signer.getSignatureAppearance();
        appearance.setPageRect(new Rectangle(100, 400, 200, 100));
        appearance.setPageNumber(1);
        appearance.setCertificate(chain[0]);
        signer.setFieldName(fieldname);

        /*
         * ExternalBlankSignatureContainer constructor will create the PdfDictionary for
         * the signature information and will insert the /Filter and /SubFilter values
         * into this dictionary. It will leave just a blank placeholder for the
         * signature that is to be inserted later.
         */
        PreSignatureContainerToSign external = new PreSignatureContainerToSign(PdfName.Adobe_PPKLite,
                PdfName.Adbe_pkcs7_detached);
        external.setCertificates(chain);

        // Sign the document using an external container.
        // 8192 is the size of the empty signature placeholder.
        signer.signExternalContainer(external, 8192);
        return external;
    }

    private void createSignature(String src, String dest, String fieldName, byte[] sig, Certificate[] chain,
                                 PreSignatureContainerToSign preSignatureContainerToSign)
            throws IOException, GeneralSecurityException {
        PdfReader reader = new PdfReader(src);
        try (FileOutputStream os = new FileOutputStream(dest)) {
            PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());

            PreSignatureContainer external = new PreSignatureContainer(PdfName.Adobe_PPKLite,
                    PdfName.Adbe_pkcs7_detached, sig,
                    preSignatureContainerToSign.hash);
            external.setCertificates(chain);
            // Signs a PDF where space was already reserved. The field must cover the whole
            // document.
            PdfSigner.signDeferred(signer.getDocument(), fieldName, os, external);
        }
    }

    public class PreSignatureContainer implements IExternalSignatureContainer {
        private PdfDictionary sigDic;
        private Certificate[] certificates;
        private byte[] signature;
        private byte[] original;
        public void setCertificates(Certificate[] certificates) {
            this.certificates = certificates;
        }

        public PreSignatureContainer(PdfName filter, PdfName subFilter, byte[] signature,  byte[] original) {
            sigDic = new PdfDictionary();
            sigDic.put(PdfName.Filter, filter);
            sigDic.put(PdfName.SubFilter, subFilter);
            this.signature = signature;
            this.original = original;
        }

        @Override
        public byte[] sign(InputStream data) throws GeneralSecurityException {
            BouncyCastleDigest digest = new BouncyCastleDigest();
            String hashAlgorithm = "SHA256";
            try {
                byte [] dataDigest = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
                var sgn = new PdfPKCS7(null, this.certificates,
                        DigestAlgorithms.SHA256, null, digest, false);
                sgn.setExternalDigest(signature,null, "RSA");
                return sgn.getEncodedPKCS7(dataDigest, PdfSigner.CryptoStandard.CMS,
                        null, null, null);

            } catch (Exception e) {
                throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
            }
        }

        @Override
        public void modifySigningDictionary(PdfDictionary signDic) {
            signDic.putAll(sigDic);
        }
    }

    public class PreSignatureContainerToSign implements IExternalSignatureContainer {
        private PdfDictionary sigDic;
        private PdfPKCS7 sgn;
        private Certificate[] certificates;
        private byte hash[];
        private byte original[];

        public PdfPKCS7 getSgn() {
            return sgn;
        }

        public void setCertificates(Certificate[] certificates) {
            this.certificates = certificates;
        }

        public PreSignatureContainerToSign(PdfName filter, PdfName subFilter) {
            sigDic = new PdfDictionary();
            sigDic.put(PdfName.Filter, filter);
            sigDic.put(PdfName.SubFilter, subFilter);
        }

        @Override
        public byte[] sign(InputStream data) throws GeneralSecurityException {
            String hashAlgorithm = "SHA256";
            BouncyCastleDigest digest = new BouncyCastleDigest();
            try {
                this.sgn = new PdfPKCS7(null, this.certificates, DigestAlgorithms.SHA256, null,
                        new BouncyCastleDigest(), false);

                this.original = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
                // get attributes
                byte [] docBytes = sgn.getAuthenticatedAttributeBytes(this.original, PdfSigner.CryptoStandard.CMS,
                        null, null);
                this.hash = DigestAlgorithms.digest(new ByteArrayInputStream(docBytes), digest.getMessageDigest(hashAlgorithm));
                return  new byte[0];
            } catch (IOException e) {
                throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
            }
        }

        @Override
        public void modifySigningDictionary(PdfDictionary pdfDictionary) {
            pdfDictionary.putAll(sigDic);
        }

    }
0

There are 0 best solutions below