pdfbox-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Paresh Chouhan <pareshchouhan2...@gmail.com>
Subject PDF Signing, generated PDF Document certification is invalid, using HSM
Date Wed, 24 May 2017 10:12:57 GMT
I have a service which signs the data and provides me with the signed hash,
it correctly generates PKCS#7 DigestInfo as stated here
https://tools.ietf.org/html/rfc2315#section-9.4

find the code at the end of this email,
the problem is the generated PDF is shown as invalid

public class DetachedPkcs7 implements SignatureInterface {
    public static void main(String[] args) {
        Security.addProvider(new
org.bouncycastle.jce.provider.BouncyCastleProvider());
        try {
            //load pdf document
            PDDocument document = PDDocument.load(new File("test.pdf"));
            int accessPermissions = getMDPPermission(document);
            if (accessPermissions == 1)
            {
                throw new IllegalStateException("No changes to the
document are permitted due to DocMDP transform parameters
dictionary");
            }
            //prepare signature
            PDSignature signature = new PDSignature();
            signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
            signature.setName("Ankit Agarwal");
            signature.setLocation("Bhopal, IN");
            signature.setReason("Testing");
            // TODO extract the above details from the signing
certificate? Reason as a parameter?

            // the signing date, needed for valid signature
            signature.setSignDate(Calendar.getInstance());

//            // Optional: certify
            if (accessPermissions == 0)
            {
                setMDPPermission(document, signature, 3);
            }

            FileOutputStream fos = new FileOutputStream(new
File("signed_file.pdf"));

            DetachedPkcs7 detachedPkcs7 = new DetachedPkcs7();
            //populate signature options for visible signature. if any.
            SignatureOptions signatureOptions = null;
            document.addSignature(signature);
            ExternalSigningSupport externalSigning =
document.saveIncrementalForExternalSigning(fos);
            InputStream dataToSign = externalSigning.getContent();
            byte[] cmsSignature = detachedPkcs7.sign(dataToSign);
            externalSigning.setSignature(cmsSignature);
        }
        catch(FileNotFoundException fex) {
            fex.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    class CMSTypedDataInputStream implements CMSTypedData {
        InputStream in;

        public CMSTypedDataInputStream(InputStream is) {
            in = is;
        }

        @Override
        public ASN1ObjectIdentifier getContentType() {
            return PKCSObjectIdentifiers.data;
        }

        @Override
        public Object getContent() {
            return in;
        }

        @Override
        public void write(OutputStream out) throws IOException,
                CMSException {
            byte[] buffer = new byte[8 * 1024];
            int read;
            while ((read = in.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
            in.close();
        }
    }

    static class CMSProcessableInputStream implements CMSProcessable,
CMSTypedData {
        private InputStream input;
        private boolean used = false;
        private final ASN1ObjectIdentifier contentType;

        public CMSProcessableInputStream(
                InputStream input)
        {
            this(new
ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), input);
        }

        public CMSProcessableInputStream(ASN1ObjectIdentifier
contentType, InputStream input) {
            this.input = input;
            this.contentType = contentType;
        }

        public InputStream getInputStream()
        {
//            checkSingleUsage();

            return input;
        }

        public void write(OutputStream zOut)
                throws IOException, CMSException
        {
//            checkSingleUsage();

            Streams.pipeAll(input, zOut);
            input.close();
        }

        public Object getContent()
        {
            return getInputStream();
        }

//        private synchronized void checkSingleUsage()
//        {
//            if (used)
//            {
//                throw new
IllegalStateException("CMSProcessableInputStream can only be used
once");
//            }
//
//            used = true;
//        }

        @Override
        public ASN1ObjectIdentifier getContentType() {
            return contentType;
        }
    }

    public byte[] sign(InputStream content) throws IOException {
        try {
            //prepare message digest
            MessageDigest mdOriginal = MessageDigest.getInstance("SHA-256");
            byte[] dataToDigest = IOUtils.toByteArray(content);
            byte[] digestOriginal = mdOriginal.digest(dataToDigest);
            System.out.println("SHA256Original : " +
Hex.encodeHexString(digestOriginal));
            //the certificate of the signer.
            String certPem = "-----BEGIN CERTIFICATE-----\n" +
                    "USER CERT HERE, THIS IS ALSO TAKEN FROM USB TOKEN
(HSM)\n" +
                    "-----END CERTIFICATE-----";
            ByteArrayInputStream inStream = new
ByteArrayInputStream(certPem.getBytes());

            BufferedInputStream bis = new BufferedInputStream(inStream);
            System.out.println(bis.available());
            CertificateFactory cf = null;

            cf = CertificateFactory.getInstance("X.509");
            List<Certificate> certList = new ArrayList<Certificate>();
            //generate certificate from input stream
            Certificate certificate = cf.generateCertificate(bis);

            certList.add(certificate);

            Store certs = new JcaCertStore(certList);
            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

            Scanner s = new Scanner(System.in);

           //wait for the user to enter base64 encoded signed value.
            final String signedHash = s.nextLine();
            System.out.println("Signing. . . ");
            s.close();
            ContentSigner nonSigner = new ContentSigner() {

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

                @Override
                public OutputStream getOutputStream() {
                    return new ByteArrayOutputStream();
                }

                @Override
                public AlgorithmIdentifier getAlgorithmIdentifier() {
                    return new
DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
                }
            };
            org.bouncycastle.asn1.x509.Certificate cert =
org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(certificate.getEncoded()));
            JcaSignerInfoGeneratorBuilder sigb = new
JcaSignerInfoGeneratorBuilder(new
JcaDigestCalculatorProviderBuilder().build());
            sigb.setDirectSignature(true);
            gen.addCertificates(certs);
            gen.addSignerInfoGenerator(sigb.build(nonSigner, new
X509CertificateHolder(cert)));

            CMSTypedDataInputStream msg = new
CMSTypedDataInputStream(new ByteArrayInputStream(dataToDigest)); //
this is never used.
            CMSSignedData signedData = gen.generate(msg, false);
            byte[] pkcs7 = signedData.getEncoded();

            //this is the signature.
            content.close();
            return pkcs7;
        } catch (CertificateException e) {
            content.close();
            e.printStackTrace();
        } catch (CMSException e) {
            content.close();
            e.printStackTrace();
        } catch (OperatorCreationException e) {
            content.close();
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            content.close();
            e.printStackTrace();
        }
        content.close();
        return null;
    }

    static public int getMDPPermission(PDDocument doc)
    {
        COSBase base =
doc.getDocumentCatalog().getCOSObject().getDictionaryObject(COSName.PERMS);
        if (base instanceof COSDictionary)
        {
            COSDictionary permsDict = (COSDictionary) base;
            base = permsDict.getDictionaryObject(COSName.DOCMDP);
            if (base instanceof COSDictionary)
            {
                COSDictionary signatureDict = (COSDictionary) base;
                base = signatureDict.getDictionaryObject("Reference");
                if (base instanceof COSArray)
                {
                    COSArray refArray = (COSArray) base;
                    for (int i = 0; i < refArray.size(); ++i)
                    {
                        base = refArray.getObject(i);
                        if (base instanceof COSDictionary)
                        {
                            COSDictionary sigRefDict = (COSDictionary) base;
                            if
(COSName.DOCMDP.equals(sigRefDict.getDictionaryObject("TransformMethod")))
                            {
                                base =
sigRefDict.getDictionaryObject("TransformParams");
                                if (base instanceof COSDictionary)
                                {
                                    COSDictionary transformDict =
(COSDictionary) base;
                                    int accessPermissions =
transformDict.getInt(COSName.P, 2);
                                    if (accessPermissions < 1 ||
accessPermissions > 3)
                                    {
                                        accessPermissions = 2;
                                    }
                                    return accessPermissions;
                                }
                            }
                        }
                    }
                }
            }
        }
        return 0;
    }

    static public void setMDPPermission(PDDocument doc, PDSignature
signature, int accessPermissions)
    {
        COSDictionary sigDict = signature.getCOSObject();

        // DocMDP specific stuff
        COSDictionary transformParameters = new COSDictionary();
        transformParameters.setItem(COSName.TYPE,
COSName.getPDFName("TransformParams"));
        transformParameters.setInt(COSName.P, accessPermissions);
        transformParameters.setName(COSName.V, "1.2");
        transformParameters.setNeedToBeUpdated(true);

        COSDictionary referenceDict = new COSDictionary();
        referenceDict.setItem(COSName.TYPE, COSName.getPDFName("SigRef"));
        referenceDict.setItem("TransformMethod", COSName.getPDFName("DocMDP"));
        referenceDict.setItem("DigestMethod", COSName.getPDFName("SHA1"));
        referenceDict.setItem("TransformParams", transformParameters);
        referenceDict.setNeedToBeUpdated(true);

        COSArray referenceArray = new COSArray();
        referenceArray.add(referenceDict);
        sigDict.setItem("Reference", referenceArray);
        referenceArray.setNeedToBeUpdated(true);

        // Catalog
        COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject();
        COSDictionary permsDict = new COSDictionary();
        catalogDict.setItem(COSName.PERMS, permsDict);
        permsDict.setItem(COSName.DOCMDP, signature);
        catalogDict.setNeedToBeUpdated(true);
        permsDict.setNeedToBeUpdated(true);
    }

}

-- 
Regards
Paresh Chouhan
https://github.com/pareshchouhan

Mime
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message