Первый быстрый просмотр вашего кода выявил две основные ошибки.
Хеширование дважды
Вы дважды хешируете данные документа (для этого используются разные API ...странно!):
Stream data = appearance.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(data, "SHA256");
[...]
_signatureHash = hash;// signatureHash;
}
}
[...]
using (SHA256.Create())
{
_signatureHash = SHA256.Create().ComputeHash(_signatureHash);
}
Это неправильно, это не имеет смысла.
Ввод неправильного контейнера подписи
Вы говорите
Запрашивающие сервисы Esign дают ответ в формате PKCS7 (CMS).
Но вместо использования контейнера сигнатур CMS из результата как такового вы пытаетесь создать собственный контейнер CMS, внедряя контейнер CMS ответа Esign response.как если бы это был просто подписанный хеш:
XmlNodeList UserX509Certificate = xmlDoc.GetElementsByTagName("UserX509Certificate");
byte[] rawdat = Convert.FromBase64String(UserX509Certificate[0].InnerText);
var chain = new List<Org.BouncyCastle.X509.X509Certificate>
{
Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(rawdat))
};
var signaturee = new PdfPKCS7(null, chain, "SHA256", false);
_signature = signaturee;
_signature.SetExternalDigest(Convert.FromBase64String(signature), null, "RSA");
byte[] encodedSignature = _signature.GetEncodedPKCS7(_hash, null, null, null, CryptoStandard.CMS);
Согласно вашим комментариям в XML
<DocSignature error="" id="1" sigHashAlgorithm="SHA256">--Signature in base 64 in PKCS7(CMS)---</DocSignature>
этот DocSignature
элемент содержит контейнер сигнатуры CMS.
Таким образом, удалите указанный выше сегмент кода и вместо этого поместите содержимое элемента DocSignature
(не забудьте декодировать base64) в byte[] encodedSignature
.Теперь вы можете вставить его в подготовленную подпись, как и раньше:
IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSignature);
MakeSignature.SignDeferred(reader, "sign1", os, external);
После того, как вы исправили вышеперечисленные проблемы, появились еще две:
Использованиенеправильный режим файла
Вы открываете поток для записи следующим образом:
using (FileStream os = System.IO.File.OpenWrite(signedPdf))
File.OpenWrite
- , задокументировано на docs.microsoft.com , чтобы быть
эквивалентно перегрузке конструктора FileStream(String, FileMode, FileAccess, FileShare)
с режимом файла, установленным на OpenOrCreate
, доступом к Write
и режимом совместного использования, установленным на None
.
Режим файла OpenOrCreate
, в свою очередь, задокументирован для указания
, что операционная система должна открывать файл, если он существует;в противном случае должен быть создан новый файл.
Таким образом, , если в указанном месте уже есть файл, этот файл остается, и вы начинаете запись в него.
Если новый файл, который вы создаете, длиннее старого, это не проблема, вы в конечном итоге перезаписываете все содержимое старого файла, а затем файл увеличивается для размещения дополнительного нового содержимого.
Но если новый файл, который вы создаете, короче старого, у вас есть проблема: после окончания нового файла все еще остаются данные из старого, более длинного файла.Таким образом, ваш результат - сборка из двух файлов.
Это произошло в случае файлов с примерами, которыми вы поделились, ваш новый контент "signaturePdf.pdf" имеет длину всего 175982 байта, но, похоже, некоторые старыефайл с тем именем длиной 811986 байт.Таким образом, файл "signaturePdf.pdf", которым вы поделились, имеет длину 811986 байт, первые 175982 байта содержат результат вашей операции, остальные данные из какого-то другого файла.
Если вы урезаете свой общий "signaturePdf.pdf "файла до его первых 175982 байт, результат выглядит намного лучше!
Чтобы решить эту проблему, вы должны использовать режим файла Create
, который задокументирован , чтобы быть
эквивалентно запросу, что если файл не существует, используйте CreateNew
;в противном случае используйте Truncate
.
using (FileStream os = new FileStream(signedPdf, FileMode.Create, FileAccess.Write, FileShare.None))
Проблема с вашей службой подписи - идентификационная информация еще не действительна
Как упоминалось выше, если вы урезаете свою общую подписанную подписьPign.pdfmsgstr "файл на первые 175982 байта, результат выглядит намного лучше!К сожалению, просто лучше, но пока не очень хорошо:
Причина, по которой ваша «личность истекла или еще не действительна», становится яснее, если взглянуть на детали.:
Т.е. время подписания, заявленное в PDF, составляет 09:47:59 UTC + 1.
Но, глядя насертификат:
Т.е. ваш сертификат действителен не ранее 09:48:40 UTC + 1.
Таким образом, заявленная подписьпрошло более полминуты, прежде чем ваш пользовательский сертификат вступил в силу!Это, очевидно, не может быть одобрено валидатором ...
Очевидно, ваша служба подписи создает для вас краткосрочный сертификат по требованию, действительный всего за полчаса.И время, когда вы начали создавать подпись PDF, в этом интервале составляет , а не .
Я сомневаюсь, что они изменят дизайн службы подписи под ваши требования. Таким образом, вам придется немного обмануть и немного использовать время подписания в будущем.
По умолчанию время подписи устанавливается текущим конструктором PdfSignatureAppearance
, т. Е. Когда эта строка выполняется:
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
К счастью, вы можете изменить заявленное время подписи, если вы сразу же используете
appearance.SignDate = [some other date time];
Дата, которую вы должны использовать здесь, должна быть вскоре (я бы предложил не более 5 минут) после того времени, когда вы позвоните в службу подписи.
Это, конечно, означает, что вы не можете произвольно ждать, пока не будет выполнен этот сервисный вызов. Как только вы назначили заявленное время подписания выше, вы обязуетесь успешно вызвать вашу подпись сервис незадолго до этого потребовал времени!
Кроме того, если служба подписи реагирует медленно или только после нескольких попыток, ваше программное обеспечение должно окончательно проверить сертификат в контейнере подписи, который вы извлекаете из него, и сравнить его срок действия с указанным вами временем подписания. Если заявленное время подписания не находится в этом интервале, начните подписывать снова!
Теперь стало очевидно, что AllPagesSignatureContainer
, который вы использовали, был разработан для очень особого варианта использования и все же должен был быть адаптирован к вашему варианту использования.
Адаптация AllPagesSignatureContainer
для режима добавления
Реализация AllPagesSignatureContainer
по существу скопирована с этого ответа работала нормально, когда не выполняла подпись в режиме добавления, но при подписывании в режиме добавления она не удалась.
Сначала это было правдоподобно, потому что этот класс должен предсказать номер объекта, который будет использоваться для значения подписи. Этот прогноз зависит от конкретного варианта использования, и включение режима добавления значительно меняет этот вариант использования. Таким образом, мой совет в комментарии был
Если вам нужен режим добавления, попробуйте заменить
PdfLiteral PRefLiteral = ...
строка в AllPagesSignatureContainer
по
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + reader.NumberOfPages) + " 0 R");
В моих тестах это работало, но в ваших тестах оно все еще не работало. Анализ вашего подписанного файла выявил причину: в моем тестовом файле использовались таблицы перекрестных ссылок, а в вашем - потоки перекрестных ссылок.
Адаптация AllPagesSignatureContainer
для режима добавления и потоков объектов
iText в режиме добавления использует функции сжатия исходного файла, т. Е. В случае вашего файла он создает поток объектов, как только сохраняет косвенный объект, который позволяет хранить его в потоке объектов.
В случае, если ваш файл iText зарезервировал номер объекта для потока объекта, он делал это между временем, AllPagesSignatureContainer
предсказывающим номер объекта значения подписи, и временем, когда значение подписи фактически было сгенерировано. Таким образом, в вашем файле фактический номер объекта значения подписи был больше прогнозируемого числа на 1.
Чтобы решить эту проблему для PDF-файлов с потоками перекрестных ссылок, можно просто заменить строку PdfLiteral PRefLiteral = ...
на
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + reader.NumberOfPages + 1) + " 0 R");
т.е. добавив 1 к первоначально предсказанному значению. К сожалению, сейчас прогноз неверен для PDF-файлов с таблицами перекрестных ссылок ...
Лучший способ исправить это - заставить iText зарезервировать номер объекта для потока объекта для PDF-файлов с перекрестными ссылками, прежде чем прогнозировать номер объекта значения подписи, а затем использовать исходный код прогнозирования. Один из способов сделать это - создать и записать косвенный объект непосредственно перед предсказанием, например, как это:
stamper.Writer.AddToBody(new PdfNull(), stamper.Writer.PdfIndirectReference, true);
PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + reader.NumberOfPages) + " 0 R");
Ответ Реализация AllPagesSignatureContainer
, по существу скопированная с, была соответствующим образом обновлена.