Статус: Участник
Группы: Участники
Зарегистрирован: 12.07.2022(UTC) Сообщений: 13  Сказал(а) «Спасибо»: 2 раз
|
Всем привет! Пробую подписать PDF документ встроенной подписью, чтобы было видно кто подписал и время подписи (был видимый штамп). Установлено: КриптоПро CSP 5.0.1200, КриптоПро PDF 2.0, Acrobat Reader DC x32, КриптоПро Browser plug-in, Для этого на сервере (.Net Core 3.1) создаю тестовый PDF и с помощью пакета iTextSharp (v. 5.5.13.3) добавляю туда пустой контейнер для подписи, сохраняю неподписанный pdf с пустым контейнером для подписи [unsignedPdfWithSignatureContainer] и отправляю на клиент массив байт [bytesToSign] для получения отсоединенной подписи: byte[] sourcePdf = PdfSignerService.CreatePdf();//PdfSignerService - отдельный класс, в котором содержатся методы для подписи пдф byte[] bytesToSign = PdfSignerService.GetPdfAndBytesToSign(sourcePdf, signatureFieldName, out byte[] unsignedPdfWithSignatureContainer); Создание PDF и добавление контейнера для подписи:
Код:public static byte[] CreatePdf()
{
using (MemoryStream ms = new MemoryStream())
{
Document document = new Document(PageSize.A4, 25, 25, 30, 30);
PdfWriter writer = PdfWriter.GetInstance(document, ms);
document.Open();
document.Add(new Paragraph("Hello World!"));
document.Close();
writer.Close();
return ms.ToArray();
}
}
public static byte[] GetPdfAndBytesToSign(byte[] unsignedPdf, string signatureFieldName, out byte[] pdfWithBlankSignatureContainer)
{
//unsignedPdf - массив байт исходного PDF файла
using (PdfReader reader = new PdfReader(unsignedPdf))
{
using (MemoryStream tempPdf = new MemoryStream())
{
//добавляем пустой контейнер для новой подписи
using (PdfStamper st = PdfStamper.CreateSignature(reader, tempPdf, '\0', null, true))
{
PdfSignatureAppearance appearance = st.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(205, 700, 390, 800), reader.NumberOfPages, SignatureFieldName);
ExternalBlankSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, BufferSize);
pdfWithBlankSignatureContainer = tempPdf.ToArray();
//получаем поток, который содержит последовательность, которую мы хотим подписывать
using (Stream contentStream = appearance.GetRangeStream())
{
MemoryStream memoryStream = new MemoryStream();
contentStream.CopyTo(memoryStream);
byte[] bytesToSign = memoryStream.ToArray();
return bytesToSign;
}
}
}
}
}
На клиенте (Angular (TypeScript)) с помощью плагина и npm-пакета crypto-pro 2.3.0, выбираю сертификат которым буду подписывать, тип подписи-отсоединенная, считаю хеш переданного сообщения и подписываю вычисленный хеш и отправляю подпись на сервер Код создания отсоединенной подписи на клиенте:
Код:public async createSignature(thumbprint) {
this.hash = null;
this.hashError = null;
this.signature = null;
this.signatureError = null;
this.hashStatus = 'Вычисляется...';
try {
this.hash = await createHash(this.message);
} catch (error) {
this.hashError = error.message;
return;
}
this.hashStatus = 'Не вычислен';
this.signatureStatus = 'Создается...';
if (this.detachedSignature) {
try {
this.signature = await createDetachedSignature(thumbprint, this.hash);
} catch (error) {
this.signatureError = error.message;
}
this.signatureStatus = 'Не создана';
return;
}
try {
this.signature = await createAttachedSignature(thumbprint, this.message);
} catch (error) {
this.signatureError = error.message;
}
this.signatureStatus = 'Не создана';
}
export const createDetachedSignature = _afterPluginsLoaded(
async (thumbprint: string, messageHash: string): Promise<string> => {
const { cadesplugin } = window;
const cadesCertificate = await _getCadesCert(thumbprint);
return eval(
_generateCadesFn(function createDetachedSignature(): string {
let cadesAttrs;
let cadesHashedData;
let cadesSignedData;
let cadesSigner;
try {
cadesAttrs = __cadesAsyncToken__ + __createCadesPluginObject__('CADESCOM.CPAttribute');
cadesHashedData = __cadesAsyncToken__ + __createCadesPluginObject__('CAdESCOM.HashedData');
cadesSignedData = __cadesAsyncToken__ + __createCadesPluginObject__('CAdESCOM.CadesSignedData');
cadesSigner = __cadesAsyncToken__ + __createCadesPluginObject__('CAdESCOM.CPSigner');
} catch (error) {
console.error(error);
throw new Error(_extractMeaningfulErrorMessage(error) || 'Ошибка при инициализации подписи');
}
const currentTime = _getDateObj(new Date());
try {
void (__cadesAsyncToken__ + cadesAttrs.propset_Name(CADESCOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME));
void (__cadesAsyncToken__ + cadesAttrs.propset_Value(currentTime));
} catch (error) {
console.error(error);
throw new Error(_extractMeaningfulErrorMessage(error) || 'Ошибка при установке времени подписи');
}
let cadesAuthAttrs;
try {
void (__cadesAsyncToken__ + cadesSigner.propset_Certificate(cadesCertificate));
cadesAuthAttrs = __cadesAsyncToken__ + cadesSigner.AuthenticatedAttributes2;
void (__cadesAsyncToken__ + cadesAuthAttrs.Add(cadesAttrs));
void (__cadesAsyncToken__ + cadesSigner.propset_Options(cadesplugin.CAPICOM_CERTIFICATE_INCLUDE_WHOLE_CHAIN));
} catch (error) {
console.error(error);
throw new Error(_extractMeaningfulErrorMessage(error) || 'Ошибка при установке сертификата');
}
try {
void (
__cadesAsyncToken__ +
cadesHashedData.propset_Algorithm(cadesplugin.CADESCOM_HASH_ALGORITHM_CP_GOST_3411_2012_256)
);
void (__cadesAsyncToken__ + cadesHashedData.SetHashValue(messageHash));
} catch (error) {
console.error(error);
throw new Error(_extractMeaningfulErrorMessage(error) || 'Ошибка при установке хеша');
}
let signature: string;
try {
signature =
__cadesAsyncToken__ +
cadesSignedData.SignHash(cadesHashedData, cadesSigner, cadesplugin.CADESCOM_PKCS7_TYPE);
} catch (error) {
console.error(error);
throw new Error(_extractMeaningfulErrorMessage(error) || 'Ошибка при подписании данных');
}
return signature;
}),
);
},
);
Когда сервер получает от клиента его подпись [signature], конвертирую её из base64 в массив байт [bytesSignature] и добавляю в подготовленный ранее неподписанный pdf с контейнером для подписи: byte[] bytesSignature = Convert.FromBase64String(signature); var signedPdf = PdfSignerService.GetSignedPdf(unsignedPdfWithSignatureContainer, bytesSignature); Метод "встраивания" подписи в PDF документ:
Код: public static byte[] GetSignedPdf(byte[] unsignedPdfWithContainer, byte[] signature)
{
using var pdfReader = new PdfReader(unsignedPdfWithContainer);
using var output = new MemoryStream();
var external = new MyExternalSignatureContainer(signature);
MakeSignature.SignDeferred(pdfReader, SignatureFieldName, output, external);
return output.ToArray();
}
private class MyExternalSignatureContainer : IExternalSignatureContainer
{
private readonly byte[] _signedBytes;
public MyExternalSignatureContainer(byte[] signedBytes)
{
this._signedBytes = signedBytes;
}
public byte[] Sign(Stream data)
{
return this._signedBytes;
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
throw new NotImplementedException();
}
}
В конце концов отправляю подписанный PDF на клиент и там сохраняю. Проблема в том, что когда я открываю этот документ, на нем видно место с подписью, но не указано кто его подписал, а при нажатии на подпись Adobe Reader DC выдает следующее: Error during signature verification. Error encountered while validating: Unsupported algorithm. Прикрепляю полученный PDF:  [Signed] Ehlektronnyjj zhurnal za Thu Jul 28 2022 00_00_00 GMT+0300 (Moscow Standard Time).pdf (132kb) загружен 14 раз(а).Подскажите, пожалуйста, в чем может быть проблема? Я подозреваю, что подписывается/передается что-то не то. Я пробою передавать массив байт [bytesToSign], но на клиенте он автоматически конвертируется в base64 строку. В каком формате должно быть передаваемое сообщение на клиент для вычисления хеша?
|