有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

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


我花了好几天的时间为这个用例实现一个有效的解决方案(使用itextpdf 5.5.13.1),但最终签名的文件中有签名错误,消息是“Certification by is invalid”——请参阅附件中的“signed_with_error”。 实施的“两步签字”概念流程是:

第一步:从原始文档文件——参见“原始”附加文件——我创建了一个中间文件——参见附加的“中间”文件——我在其中插入了一个空签名,根据该签名创建了关联的文档哈希。在这一步中,将保存签名摘要以供下一步使用

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

原始、中间和带错误签名的示例文件为:
original
intermediary
signed_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。它看起来是合法的无效,但对我来说奇怪的是,在这种情况下,证书是在文档中显示的,但是当使用我认为的“右”签名摘要值时,它被破坏了。p>


共 (1) 个答案

  1. # 1 楼答案

    你说

    the remote server generates the detached signature based on the associated document hash and the resulting signature is an “external signature”, as defined in the CMS RFC (RFC5652), section 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