Двухэтапное подписание PDF с itextpdf для Java с использованием существующего сертификата, промежуточного файла и удаленно созданной подписи - PullRequest
0 голосов
/ 13 февраля 2020

Вариант использования следующий: локальное приложение должно подписать документ PDF, используя полную PKCS # 7 отдельную подпись, созданную на удаленном сервере, для которой требуется одноразовый пароль, отправленный электронной почтой. почта / SMS; удаленный сервер генерирует отдельную подпись на основе связанного документа ha sh, а результирующая подпись является «внешней подписью», как определено в разделе CMS RF C (RFC5652), раздел 5.2, такой как результирующий файл подписи отдельный файл.


Я уже несколько дней пытаюсь реализовать рабочее решение для этого варианта использования (используя itextpdf 5.5.13.1 ), но последний подписанный файл имеет ошибочная подпись с сообщением «Сертификация недействительна» - см. прикрепленный файл «signature_with_error». Концептуальный процесс «двухэтапной подписи»:

1-й шаг: из исходного файла документа - см. «Оригинальный» прикрепленный файл - я создаю промежуточный файл - см. прикрепленный промежуточный файл, в котором я вставил пустую подпись, на основе которой создается связанный документ ha sh. На этом этапе дайджест подписи сохраняется для использования на следующем шаге

2-й шаг: удаленный сервер вызывается, отсоединенный файл подписи получен, и я создаю подписанный файл путем вставки содержимого в подпись из предварительно подписанного файла с использованием дайджеста подписи из предыдущего шага и отдельного содержимого подписи, полученного с удаленного сервера.

Исходные, промежуточные и подписанные с примерами файлов ошибки: оригинал Посредник signature_with_error

Кто-нибудь знает, что может быть не так с моим кодом ниже? Соответствующие части кода следующие:

1-й шаг:

        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

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
        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 недействителен с ошибкой «Сертификация документа недействительна». --- «Документ был изменен или поврежден с момента применения сертификации». - см. прикрепленный файл signature_certification_invalid . Похоже, что git является недействительным, но для меня странно, почему в этом случае сертификация отображается в документе, но она нарушается при использовании - что я считаю - "правильного" значения signatureDigest.

1 Ответ

0 голосов
/ 13 февраля 2020

Вы говорите

, удаленный сервер генерирует отдельную подпись на основе связанного документа ha sh, а полученная подпись является «внешней подписью», как определено в CMS RF C (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();

Кроме того, неправильный ха sh подписывается. В самом деле, на шаге 1 вы не должны также играть с классом PdfPKCS7, а просто использовать

documentDetails.setSigningHash(externalSignatureContainer.getSignatureDigest()); // this is the hash sent to remote server for signing
...