Вариант использования следующий: локальное приложение должно подписать документ 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.