PDFBox 签名无效

问题描述 投票:0回答:1

我有两个来自不同 TSP 的基于 CSC 的凭据(例如 CredA 和 CredB)。我正在尝试使用这两个凭据执行 PDF 签名。 我用两种方式实现了相同的功能,一种使用

PDFBox
,另一种使用
Itext

CredA 适用于以下

PDFBox
实现,但在
Itext
实现中失败。


    public static void testSign(InputStream is, PDDocument document, Certificate[] certificateChain, String accessToken) throws Exception {
        try (
                OutputStream output = new FileOutputStream(new File("/[filepath]/", "Signed.pdf"));
        ) {
            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);

            X509Certificate cert = (X509Certificate) certificateChain[0];

            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);

            ContentSigner contentSigner = new ContentSigner() {
                private MessageDigest digest = MessageDigest.getInstance("SHA-256");
                private OutputStream stream = OutputStreamFactory.createStream(digest);
                @Override
                public byte[] getSignature() {
                    try {

                        byte[] b = new byte[4096];
                        int count;

                        while ((count = is.read(b)) > 0) {
                            digest.update(b, 0, count);
                        }
                        byte[] hashBytes = digest.digest();

                        java.util.Base64.Encoder encoder = java.util.Base64.getEncoder();
                        List<String> hash = Arrays.asList(encoder.encodeToString(hashBytes));
                        byte[] signedHash = signHash(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"));
                }
            };

            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
            gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
            gen.addSignerInfoGenerator(sigb.build(contentSigner, new X509CertificateHolder(cert2)));


            final CMSProcessableByteArray content = new CMSProcessableByteArray(
                    IOUtils.toByteArray(externalSigning.getContent()));
            CMSSignedData signedData = gen.generate(content, false);

            byte[] cmsSignature = signedData.getEncoded();
            externalSigning.setSignature(cmsSignature);
        }
    }

    private static byte[] signHash(String accessToken, List hash) throws Exception {

        String SAD = "";
        JSONParser parser = new JSONParser();
        //CredentialsAuthorize API
        JSONObject requestHeaderCredentialsAuthorize = new JSONObject();
        requestHeaderCredentialsAuthorize.put("Content-type", "application/json");
        requestHeaderCredentialsAuthorize.put("Authorization", "Bearer " + accessToken);

        JSONObject requestPayloadCredentialsAuthorize = new JSONObject();
        requestPayloadCredentialsAuthorize.put("credentialID", keyID);
        requestPayloadCredentialsAuthorize.put("numSignatures", "1");
        requestPayloadCredentialsAuthorize.put("PIN", SecretPIN);

        JSONArray hashArray = new JSONArray();
        hashArray.add(hash.get(0));
        requestPayloadCredentialsAuthorize.put("hash", hashArray);

        JSONObject credentialsAuthorizeResponse = PosttoHttpURLConnection.getResponseHTTP(CSC_CredentialsAuthorize_URL, requestPayloadCredentialsAuthorize.toString(), requestHeaderCredentialsAuthorize);
        if (credentialsAuthorizeResponse != null) {
            JSONObject credentialsAuthorizeResponseObject = (JSONObject) parser.parse(credentialsAuthorizeResponse.get("Response").toString());
            if (credentialsAuthorizeResponse.get("StatusCode").toString().equals("200")) {
                SAD = credentialsAuthorizeResponseObject.get("SAD") == null ? "" : credentialsAuthorizeResponseObject.get("SAD").toString();
                String expires_in = credentialsAuthorizeResponseObject.get("expires_in") == null ? "" : credentialsAuthorizeResponseObject.get("expires_in").toString();
            } else {
                return null;
            }
        }

        String keyAlgorithm = "SHA256withRSA";
        //signHash API
        JSONObject requestHeadersignHash = new JSONObject();
        requestHeadersignHash.put("Content-type", "application/json");
        requestHeadersignHash.put("Authorization", "Bearer " + accessToken);

        JSONObject requestPayloadsignHash = new JSONObject();
        requestPayloadsignHash.put("credentialID", keyID);
        requestPayloadsignHash.put("SAD", SAD);
        requestPayloadsignHash.put("hashAlgo", "2.16.840.1.101.3.4.2.1");
        requestPayloadsignHash.put("signAlgo", "1.2.840.113549.1.1.1");
        requestPayloadsignHash.put("signAlgoParams", keyAlgorithm);
        JSONArray signHashArray = new JSONArray();
        signHashArray.add(hash.get(0));
        requestPayloadsignHash.put("hash", signHashArray);

        JSONObject signHashResponse = PosttoHttpURLConnection.getResponseHTTP(CSC_signHash_URL, requestPayloadsignHash.toString(), requestHeadersignHash);
        if (signHashResponse != null) {
            JSONObject signHashResponseObject = (JSONObject) parser.parse(signHashResponse.get("Response").toString());
            if (signHashResponse.get("StatusCode").toString().equals("200")) {
                JSONArray signedHashArray = (JSONArray) parser.parse(signHashResponseObject.get("signatures").toString());
                String rawSignature = signedHashArray.get(0).toString();
                byte[] signatureBytes = org.bouncycastle.util.encoders.Base64.decode(rawSignature);
                return signatureBytes;
            }
        }
                return null;
    }

CredB 适用于以下

IText
实现,但无法用于以上
PDFBox
实现。

            String access_token = getAccessTokenAPI();
            JSONParser parser = new JSONParser();


            String CredentialsInfoAlgoValue = "1.2.840.113549.1.1.1";
            JSONArray certificates = getCredentialsInfoAPI();

            //Hash Doc
            int contentEstimated = 32768;
            PdfReader readerpdf = new PdfReader(filePath);
            ByteArrayOutputStream fout = new ByteArrayOutputStream();
            PdfStamper stamperpdf = PdfStamper.createSignature(readerpdf, fout, '\0', null, true);
            PdfSignatureAppearance appearance = stamperpdf.getSignatureAppearance();
            appearance.setReason("Demo");
            appearance.setLocation("10.80.100.46");
            Calendar cal = Calendar.getInstance();
            cal.add(Calendar.MINUTE, 5);
            appearance.setSignDate(cal);
            appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
            appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
            appearance.setImage(null);

            appearance.setAcro6Layers(false);
            String[] str = "285,300,415,370".split(",");
            Float[] intarray = new Float[str.length];
            int i = 0;
            for (String strs : str) {
                intarray[i] = Float.parseFloat(strs.trim());
                i++;
            }

            int[] pages = {Integer.parseInt("1")};
            List<Rectangle> listRectangle = new ArrayList<>();
            listRectangle.add(new Rectangle(intarray[0], intarray[1], intarray[2], intarray[3]));
            appearance.setVisibleSignature(new Rectangle(intarray[0], intarray[1], intarray[2], intarray[3]), pages[0], null);
            HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
            exc.put(PdfName.CONTENTS, new Integer(contentEstimated * 2 + 2));
            PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
            dic.setReason(appearance.getReason());
            dic.setLocation(appearance.getLocation());
            dic.setContact(appearance.getContact());
            Calendar calDate = Calendar.getInstance();
            dic.setDate(new PdfDate(calDate));
            appearance.setCryptoDictionary(dic);
            appearance.setLayer2Text("Signed by: " + "Demo User" + " \nReason: Demo");
            appearance.setLayer2Font(new Font(Font.FontFamily.HELVETICA, 6, Font.NORMAL, BaseColor.BLACK));
            appearance.preClose(exc);

            Certificate[] chain = new Certificate[certificates.size()];
            int c = 1;
            for (int k = 0; k < certificates.size(); k++) {
                X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(org.bouncycastle.util.encoders.Base64.decode(certificates.get(k).toString().getBytes())));
                String subject = getSubjectType(cert);
                if (subject.equalsIgnoreCase("EndEntity")) {
                    chain[0] = cert;
                } else {
                    chain[c++] = cert;
                }
            }

            ExternalDigest externalDigest = new ExternalDigest() {
                public MessageDigest getMessageDigest(String hashAlgorithm)
                        throws GeneralSecurityException {
                    return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
                }
            };
            PdfPKCS7 sgn = null;
            byte hash[] = null;
            byte ocsp[] = null;

            InputStream data = appearance.getRangeStream();;
            if (CredentialsInfoAlgoValue.equals("1.2.840.10045.4.3.3")) {
                sgn = new PdfPKCS7(null, chain, "SHA384", null, externalDigest, false);
                hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA384"));
            } else if (CredentialsInfoAlgoValue.equals("1.2.840.10045.4.3.4")) {
                sgn = new PdfPKCS7(null, chain, "SHA512", null, externalDigest, false);
                hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA512"));
            } else {
                sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
                hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
            }

            byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, calDate, ocsp, null, MakeSignature.CryptoStandard.CMS);

            String stringDocumentHash = new String(org.bouncycastle.util.encoders.Base64.encode(sh));
            String SAD = getCredentialsAuthoriseAPI();
            
            String rawSignature = getSignHashAPI();
            byte[] signatureBytes = org.bouncycastle.util.encoders.Base64.decode(rawSignature);
            ByteArrayOutputStream os = fout;
            sgn.setExternalDigest(signatureBytes, null, "RSA");
            Collection<byte[]> crlBytes = null;
            TSAClient tsaClient = null;
            byte[] pkcs7 = sgn.getEncodedPKCS7(hash, calDate, tsaClient, ocsp, crlBytes, MakeSignature.CryptoStandard.CMS);
            byte[] paddedSig = new byte[32768];
            System.arraycopy(pkcs7, 0, paddedSig, 0, pkcs7.length);
            PdfDictionary dic2 = new PdfDictionary();
            dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
            appearance.close(dic2);

            System.out.println("Signed PDF Document saved at: " + filePath + "_signed.pdf");
            String outputFile1 = filePath+"_signed.pdf";
            Path signedFile = Paths.get(outputFile1);
            Files.write(signedFile, os.toByteArray());
            return;

任何人都可以帮助我识别两种实现之间的差异(除了外观)。我想使用

PDFBox
实现来运行 CredA 和 CredB,但 CredB 仅适用于
IText
,并因
PDFBox
失败,错误为
Invalid Signature(Document has been altered or corrupted since it was signed)

java pdfbox digital-signature
1个回答
0
投票

基于 iText 的代码和基于 PDFBox 的代码之间的区别在于

  • 在前一种情况(iText)中,您发送actual经过身份验证的属性进行签名(您将载体对象称为

    stringDocumentHash
    ,但它实际上是一个包含经过身份验证的属性的字符串,文档哈希只是这些属性之一),但是

  • 在后一种情况(PDFBox)中,您发送经过身份验证的属性的哈希值以进行签名。

因此,显然,CredB TSP(与 iText 配合使用)希望您发送要签名的实际字节,并自行计算其哈希值,而 CredA TSP(与 PDFBox 配合使用)希望您发送要签名字节的哈希值并按原样使用该值。

要使您的 PDFBox 代码适用于两个 TSP,您要么必须更改代码以仅选择性地散列经过身份验证的字节(并根据所使用的 TSP 请求散列),要么必须编排您的 CredB 访问以期望原始数据要签名的数据,而不是其哈希值。


不幸的是,当将代码复制到您的问题中时,两个版本中都会出现一些错误,导致它们无法编译。因此,上述分析只是松散地基于该代码。相反,它主要基于您提供的示例文件。


顺便说一下,签名者证书的主题

...,PostalCode=\ 201304,ST=\ Uttar Pradesh,...

正在为
PostalCode
ST
条目开头的空白问题找麻烦...

© www.soinside.com 2019 - 2024. All rights reserved.