Прежде всего, хотя я уже давно следил за StackOverflow, я впервые что-то опубликовал, поэтому, если я делаю что-то не так или не в соответствии с правилами, пожалуйста, не стесняйтесь указывать мне правильнонаправление.
Я разрабатываю приложение для цифровой подписи PDF, использующее iText5, которое зависит от внешней службы, предоставляющей подписанный хэш после подготовки PDF для подписания.
Как описано в Документация iText , на первом этапе я подготовил PDF-файл (в окончательной реализации все PDF-файлы могут быть мультиподписанными, поэтому я использую режим добавления), например так:
public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName, List<Org.BouncyCastle.X509.X509Certificate> certificateChain) {
// we create a reader and a stamper
using (PdfReader reader = new PdfReader(unsignedPdf)) {
using (FileStream baos = File.OpenWrite(tempPdf)) {
List<Org.BouncyCastle.X509.X509Certificate> chain = certificateChain;
PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
sap = pdfStamper.SignatureAppearance;
sap.Certificate = certificateChain[0];
sap.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 720, 160, 780), 1, signatureFieldName);
//sap.SetVisibleSignature(signatureFieldName);
sap.SignDate = DateTime.Now;
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.Date = new PdfDate(sap.SignDate);
dic.Name = CertificateInfo.GetSubjectFields(chain[0]).GetField("CN");
sap.CryptoDictionary = dic;
sap.Certificate = certificateChain[0];
sap.Acro6Layers = true;
sap.Reason = "test";
sap.Location = "test";
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(sap, external, 8192);
signatureContainer = new PdfPKCS7(null, chain, "SHA256", false);
byte[] hash = DigestAlgorithms.Digest(sap.GetRangeStream(), "SHA256");
//byte[] signatureHash = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
return hash;
}
}
}
После этого шага я отправляю хэш внешней службе, которая возвращает подписанный хеш.
Проверка хеша, который я отправляю в службу, кажется правильной, посколькуон охватывает весь PDF, за исключением нового содержимого подписи.
Затем я завершаю процесс подписи, используя метод, приведенный ниже:
private byte[] Sign(PdfPKCS7 signatureContainer, List<X509Certificate2> chain2, List<Org.BouncyCastle.X509.X509Certificate> chain, byte[] hash, byte[] signedBytes, string tmpPdf, string signedPdf, string signatureFieldName) {
System.Security.Cryptography.RSACryptoServiceProvider publicCertifiedRSACryptoServiceProvider = chain2[0].PublicKey.Key as System.Security.Cryptography.RSACryptoServiceProvider;
bool verify = publicCertifiedRSACryptoServiceProvider.VerifyHash(hash, "SHA256", signedBytes); //verify if the computed hash is same as signed hash using the cert public key
Console.WriteLine("PKey signed computed hash is equal to signed hash: " + verify);
AsnEncodedData asnEncodedData = new AsnEncodedData(signedBytes);
Console.WriteLine(asnEncodedData.Format(true));
//ITEXT5
try {
//Console.WriteLine("Signed bytes: " + Encoding.UTF8.GetString(signedBytes));
using (PdfReader reader = new PdfReader(tmpPdf)) {
using (FileStream outputStream = File.OpenWrite(signedPdf)) {
IExternalSignatureContainer external = new Objects.MyExternalSignatureContainer(signedBytes, chain, signatureContainer);
MakeSignature.SignDeferred(reader, signatureFieldName, outputStream, external);
}
}
return new byte[] { };
}
catch(Exception ex) {
File.Delete(tmpPdf);
Console.WriteLine("Error signing file: " + ex.Message);
return new byte[] { };
}
}
В начале метода Sign я проверяю,хэш, отправленный во внешнюю службу, подписанный тем же сертификатом, равен ответу внешней службы, что является истинным.
Код MyExternalSignatureContainer:
public class MyExternalSignatureContainer : IExternalSignatureContainer {
private readonly byte[] signedBytes;
public List<Org.BouncyCastle.X509.X509Certificate> Chain;
private PdfPKCS7 sigField;
public MyExternalSignatureContainer(byte[] signedBytes) {
this.signedBytes = signedBytes;
}
public MyExternalSignatureContainer(byte[] signedBytes, List<Org.BouncyCastle.X509.X509Certificate> chain, PdfPKCS7 pdfPKCS7) {
this.signedBytes = signedBytes;
this.Chain = chain;
this.sigField = pdfPKCS7;
}
public byte[] Sign(Stream data) {
try {
sigField.SetExternalDigest(signedBytes, null, "RSA");
return sigField.GetEncodedPKCS7(signedBytes, null, null, null, CryptoStandard.CMS);
}
catch (IOException ioe) {
throw ioe;
}
}
public void ModifySigningDictionary(PdfDictionary signDic) {
}
}
Проблема заключается в том, что когда я открываю PDF в Acrobat, он утверждает, что документ был изменен или поврежден с момента применения подписи.
(Если я открою тот же PDF-файл в PDF-XChange, он скажет, что PDF-файл не был изменен).
То, что я пробовал без удачи:
Не будучи полностью уверенным, что внешняя служба использует SHA256, я уже пытался изменить дайджест на SHA1 предварительной подписи, что привело к «ошибке форматирования» в Acrobat Reader.
Как указано в другом посте вВ StackOverlow, касающемся той же проблемы (я не могу найти сообщение, связывающее ее), потенциальная проблема - использование разных потоков для временного файла.Я уже пытался использовать тот же поток без удачи.
Образцы файлов PDF:
Исходный файл
Temp File
Подписанный файл
Хэш Base64, отправленный в сервис:
XYfaS/SisA/tk5hcl035RpBjOczrH9E5rgiAMpqgkjI=
Base64 подписанный хеш, отправленный в ответ:
CnV3WL7skhMCtZG1r1Qi2oyE9WPO3KP4Ieu/Xm4lec+DAbYbhQxCvjMISsG3sTwYY7Lqi4luD60uceViDH848rS9OkTn8szzAnnX2fSYIwqDpG3qjJAb6NOXEv41hy+XYhSBJWS4ji2mM2ReruwPafxB1aM25L5Jyd0V7WecuNFUevUrvd85Y2KBkyBw9zCA8NDAQPPY0UT4GkXZi3Z35+Sf/s2o8zxCOlBDaIJyMvJ9De79nw4jC5L9NesHpFxx3mX1g1N33GHjUNdETgFMhnd8RDUlGLW6bsAyv78gvwE6aXF6COObap/VtlLvMOME68MzLr6izKte6uA35Zwj9Q==
Обновление после MKL-ответа :
Согласно ответу, я изменил код подписи документа только в одну фазу и в итоге использовал следующие методы:
using (PdfReader reader = new PdfReader(fileLocation)) {
using (FileStream baos = File.OpenWrite(tmpFile)) {
List<Org.BouncyCastle.X509.X509Certificate> chain = Chain;
PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
PdfSignatureAppearance sap = pdfStamper.SignatureAppearance;
sap.Certificate = Chain[0];
sap.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 720, 160, 780), 1, signatureFieldName);
//sap.SetVisibleSignature(signatureFieldName);
sap.SignDate = DateTime.Now;
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.Date = new PdfDate(sap.SignDate);
dic.Name = CertificateInfo.GetSubjectFields(chain[0]).GetField("CN");
sap.CryptoDictionary = dic;
sap.Certificate = Chain[0];
sap.Acro6Layers = true;
//sap.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS;
sap.Reason = "test";
sap.Location = "test";
IExternalSignature signature = new Objects.RemoteSignature(client, signatureRequest);
MakeSignature.SignDetached(sap, signature, Chain, null, null, null, 8192, CryptoStandard.CMS);
}
}
И реализация IExternalSignature:
public virtual byte[] Sign(byte[] message) {
IDigest messageDigest = DigestUtilities.GetDigest(GetHashAlgorithm());
byte[] messageHash = DigestAlgorithms.Digest(messageDigest, message);
//
// Request signature for hash value messageHash
// and return signature bytes
//
signatureRequest.Hash = messageHash;
SignatureService.SignatureResponse signatureResponse = client.Signature(signatureRequest);
if (signatureResponse.Status.Code == "00") {
return signatureResponse.DocumentSignature;
}
else {
throw new Exception("Error signing file: " + signatureResponse.Status.Message);
}
}
signatureResponse.DocumentSignature представляет подписанные байты, возвращенные службой.
В результате PDF теперь я получаю ошибку декодирования BER.
Анализируя ваш пример PDF, вы, кажется, объявляете неверный сертификат в качестве сертификата подписавшего
Хотя я знаю, что текущий сертификат недействителен, он предоставляется службой и в предыдущей реализации службы, куда я отправлял весь PDF для подписания, подписанный PDF также былподписанный этим сертификатом.
Вопрос: Зная, что при двухэтапной подписи мне удалось подписать PDF-файл этим сертификатом (кроме измененного или поврежденного документа после ошибки подписи)разве этот метод не должен работать с тем же сертификатом?
В настоящее время происходит следующее:
Проверка подписи:
Опять же, если я открою тот же PDF-файл в PDF-XChange, подпись действительна, и документ не был изменен.Требование, чтобы PDF был действительным в Acrobat, но я озадачен этой разницей между читателями.
Результат PDF
Обновление 2
Т.е. вам нужно только префикс вашего хэша с последовательностью байтов 30 31 30 0d06 09 60 86 48 01 65 03 04 02 01 05 00 04 20.
После добавления этого префикса SHA256 в дайджест сообщения полученный PDF-файл теперь правильно подписан.
Примет ли Adobe Reader фиксированную подпись?
Я сомневаюсь в этом.Использование ключа сертификата подписавшего содержит только значение для подписи других сертификатов.
Текущий сертификат используется только для тестирования.В производственной среде я считаю, что сертификат, предоставленный внешней службой, будет действительным.
У меня есть еще два вопроса по этой проблеме:
Для вашего кодаэто означает, что вы должны упаковать хеш в структуру DigestInfo перед отправкой в сервис.
В: Как вы проверили контейнер для подписи, чтобы сделать вывод, что он неправильный?
В: В моем исходном коде у меня была двухфазная подпись.Будет ли тот же принципал, примененный в методе единого знака, все еще действительным, то есть, применяя префикс SHA256, делать предварительно подписанные байты и после установки дайджеста с результирующими подписанными байтами?