使用现有证书,中间文件和远程创建的签名,使用itextpdf for Java对PDF进行两步签名

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

用例如下:本地应用程序必须使用在远程服务器上生成的完整PKCS#7分离签名对PDF文档进行签名,该签名需要通过电子邮件/ SMS发送一次密码;远程服务器会根据关联的文档哈希值生成分离的签名,并且生成的签名是“外部签名”,如CMS RFC(RFC5652)第5.2节中所定义,例如生成的签名文件是一个单独的文件。


我现在正在努力工作几天以实现针对该用例的可行解决方案(使用itextpdf 5.5.13.1,但是最终签名文件的签名有误,并带有消息“认证无效”) -请参阅附件中的“ signed_with_error”文件。概念性的“两步签名”实施过程是:

第一步]来自原始文档文件-请参见“原始”附件-我创建了一个中间文件-请参见“ intermediary”文件-在其中插入了空签名创建关联文档哈希的基础。在此步骤中,签名摘要被保存以供下一步使用

第二步:调用远程服务器,接收到分离的签名文件,我正在使用上一步和分离的签名中的签名摘要通过将内容插入预签名文件中的签名中来创建签名文件从远程服务器收到的内容。

原始,中间文件和带有错误示例文件的签名是:originalintermediarysigned_with_error

任何人都不知道我的代码下面可能有什么问题吗?相关代码部分如下:

第一步:

        ByteArrayOutputStream preSignedDocument = new ByteArrayOutputStream();
        Path customerPathInDataStorage = storageService.resolveCustomerPathInDataStorage(customerExternalId);
        PdfReader pdfReader = new PdfReader(originalDocumentContent);
        PdfStamper stamper = PdfStamper.createSignature(pdfReader, preSignedDocument, '\0', customerPathInDataStorage.toFile(), true);

        // create certificate chain using certificate received from remote server system
        byte[] certificateContent = certificateInfo.getData(); // this is the customer certificate received one time from the remote server and used for every document signing initialization
        X509Certificate certificate = SigningUtils.buildCertificateFromCertificateContent(certificateContent);
        java.security.cert.Certificate[] certificatesChain = CertificateFactory.getInstance(CERTIFICATE_TYPE).generateCertPath(
                Collections.singletonList(certificate)).getCertificates().toArray(new java.security.cert.Certificate[0]);

        // create empty digital signature inside pre-signed document
        PdfSignatureAppearance signatureAppearance = stamper.getSignatureAppearance();
        signatureAppearance.setVisibleSignature(new Rectangle(72, 750, 400, 770), 1, "Signature_" + customerExternalId);
        signatureAppearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
        signatureAppearance.setCertificate(certificate);
        CustomPreSignExternalSignature externalSignatureContainer =
                new CustomPreSignExternalSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
        MakeSignature.signExternalContainer(signatureAppearance, externalSignatureContainer, 8192);

        ExternalDigest digest = new SignExternalDigest();
        PdfPKCS7 pdfPKCS7 = new PdfPKCS7(null, certificatesChain, DOCUMENT_HASHING_ALGORITHM, null, digest, false);
        byte[] signatureDigest = externalSignatureContainer.getSignatureDigest();
        byte[] authAttributes = pdfPKCS7.getAuthenticatedAttributeBytes(signatureDigest, null, null,
                MakeSignature.CryptoStandard.CMS);

        pdfReader.close();

        documentDetails.setPreSignedContent(preSignedDocument.toByteArray()); // this is the intermediary document content used in 2nd step in the line with the comment ***PRESIGNED_CONTENT****
        documentDetails.setSignatureDigest(signatureDigest); // this is the signature digest used in 2nd step in the line with comment ****SIGNATURE_DIGEST****
        byte[] hashForSigning = DigestAlgorithms.digest(new ByteArrayInputStream(authAttributes),
                digest.getMessageDigest(DOCUMENT_HASHING_ALGORITHM));
        documentDetails.setSigningHash(hashForSigning); // this is the hash sent to remote server for signing

第二步:

        // create certificate chain from detached signature
        byte[] detachedSignatureContent = documentDetachedSignature.getSignature(); // this is the detached signature file content received from the remote server which contains also customer the certificate
        X509Certificate certificate = SigningUtils.extractCertificateFromDetachedSignatureContent(detachedSignatureContent);
        java.security.cert.Certificate[] certificatesChain = CertificateFactory.getInstance(CERTIFICATE_TYPE).generateCertPath(
                    Collections.singletonList(certificate)).getCertificates().toArray(new java.security.cert.Certificate[0]);

        // create digital signature from detached signature
        ExternalDigest digest = new SignExternalDigest();
        PdfPKCS7 pdfPKCS7 = new PdfPKCS7(null, certificatesChain, DOCUMENT_HASHING_ALGORITHM, null, digest, false);

        pdfPKCS7.setExternalDigest(detachedSignatureContent, null, SIGNATURE_ENCRYPTION_ALGORITHM);
        byte[] signatureDigest = documentVersion.getSignatureDigest(); // this is the value from 1st step for ****SIGNATURE_DIGEST****
        byte[] encodedSignature = pdfPKCS7.getEncodedPKCS7(signatureDigest, null, null, null, MakeSignature.CryptoStandard.CMS);
        ExternalSignatureContainer externalSignatureContainer = new CustomExternalSignature(encodedSignature);

        // add signature content to existing signature container of the intermediary PDF document
        PdfReader pdfReader = new PdfReader(preSignedDocumentContent);// this is the value from 1st step for ***PRESIGNED_CONTENT****
        ByteArrayOutputStream signedPdfOutput = new ByteArrayOutputStream();
        MakeSignature.signDeferred(pdfReader, "Signature_" + customerExternalId, signedPdfOutput, externalSignatureContainer);

        return signedPdfOutput.toByteArray();

依赖项:

public static final String DOCUMENT_HASHING_ALGORITHM = "SHA256";
public static final String CERTIFICATE_TYPE = "X.509";
public static final String SIGNATURE_ENCRYPTION_ALGORITHM = "RSA";

public class CustomPreSignExternalSignature implements ExternalSignatureContainer {

    private static final Logger logger = LoggerFactory.getLogger(CustomPreSignExternalSignature.class);

    private PdfDictionary dictionary;
    private byte[] signatureDigest;

    public CustomPreSignExternalSignature(PdfName filter, PdfName subFilter) {
        dictionary = new PdfDictionary();
        dictionary.put(PdfName.FILTER, filter);
        dictionary.put(PdfName.SUBFILTER, subFilter);
    }

    @Override
    public byte[] sign(InputStream data) throws GeneralSecurityException {
        try {
            ExternalDigest digest = new SignExternalDigest();
            signatureDigest = DigestAlgorithms.digest(data, digest.getMessageDigest(DOCUMENT_HASHING_ALGORITHM));
        } catch (IOException e) {
            logger.error("CustomSignExternalSignature - can not create hash to be signed", e);
            throw new GeneralSecurityException("CustomPreSignExternalSignature - can not create hash to be signed", e);
        }

        return new byte[0];
    }

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

    public byte[] getSignatureDigest() {
        return signatureDigest;
    }
}

public class CustomExternalSignature implements ExternalSignatureContainer {

        private byte[] signatureContent;

        public CustomExternalSignature(byte[] signatureContent) {
            this.signatureContent = signatureContent;
        }

        @Override
        public byte[] sign(InputStream data) throws GeneralSecurityException {
            return signatureContent;
        }

        @Override
        public void modifySigningDictionary(PdfDictionary pdfDictionary) {
        }
    }

public class SignExternalDigest implements ExternalDigest {

    @Override
    public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException {
        return DigestAlgorithms.getMessageDigest(hashAlgorithm.toUpperCase(), null);
    }

}

最新评论:

即使不正确,我也观察到,如果在第二步而不是行中:

byte[] signatureDigest = documentVersion.getSignatureDigest(); // this is the value from 1st step for ****SIGNATURE_DIGEST****

我将使用:

byte[] signatureDigest = new byte[0];

然后将使用可见的证书生成签名文件,该证书可以验证,但PDF文档无效,错误为“文档证书无效”。 ---“自从应用证书以来,文档已被更改或损坏。” -参见附件signed_certification_invalid。看来合法是无效的,但对我来说很奇怪,为什么在这种情况下,证书显示在文档中,但是使用时却被破坏了-我认为-“正确的” signatureDigest值。

java itext digital-signature x509certificate pkcs#7
1个回答
0
投票

您说

远程服务器根据关联的文档哈希值生成分离的签名,并且所得到的签名是“外部签名”,如CMS RFC(RFC5652)的5.2节中所定义

因此,您在步骤2中检索到的内容]

// create certificate chain from detached signature
byte[] detachedSignatureContent = documentDetachedSignature.getSignature(); // this is the detached signature file content received from the remote server which contains also customer the certificate

已经是CMS签名容器。因此,您不能再像下面几行那样将其插入到PKCS#7 / CMS签名容器中,而是可以立即将其注入PDF。

所以您的第二步应该是

// create certificate chain from detached signature
byte[] detachedSignatureContent = documentDetachedSignature.getSignature(); // this is the detached signature file content received from the remote server which contains also customer the certificate
ExternalSignatureContainer externalSignatureContainer = new CustomExternalSignature(detachedSignatureContent);

// add signature content to existing signature container of the intermediary PDF document
PdfReader pdfReader = new PdfReader(preSignedDocumentContent);// this is the value from 1st step for ***PRESIGNED_CONTENT****
ByteArrayOutputStream signedPdfOutput = new ByteArrayOutputStream();
MakeSignature.signDeferred(pdfReader, "Signature_" + customerExternalId, signedPdfOutput, externalSignatureContainer);

return signedPdfOutput.toByteArray();

此外,错误的哈希被签名。确实,在第1步中,您也不应该使用PdfPKCS7类,而只需使用

documentDetails.setSigningHash(externalSignatureContainer.getSignatureDigest()); // this is the hash sent to remote server for signing
© www.soinside.com 2019 - 2024. All rights reserved.