Я использую iText 7 для добавления подписей к PDF-документам. Я также использую свою собственную реализацию IExternalSignatureContainer для интеграции сертификатов в PKCS7 CMS, поскольку служба подписи возвращает только подпись PKCS1.
Процесс подписи асинхронный (пользователь должен пройти аутентификацию), я хотел бы сделать следующее:
- Подготовьте документ (PdfReader)
- Вернуть пользователю хеш-значение документа
- Выбросьте документ (PdfReader)
- Разрешить пользователю пройти аутентификацию (не имеет прямого отношения к процессу подписи iText) и создать подпись (PKCS1)
- Если пользователь аутентифицирован, подготовьте документ еще раз и примените подпись.
Причина в том, что мне не нужно слишком хранить подготовленный документ в памяти, а также для пакетной подписи.
Моя проблема в том, что созданное хеш-значение всегда разное. (Даже если я установил дату / время через pdfSigner.SetSignDate на одно и то же значение) или каждый экземпляр PdfReader / PdfSigner.
//Create the hash of of the pdf document
//Part of my IExternalSignatureContainer Sign method
//Called from iText pdfSigner.SignExternalContainer
//The produced hash is always different
byte[] hash = DigestAlgorithms.Digest(pdfStream, DigestAlgorithms.GetMessageDigest(hashAlgorithm));
Вопрос: есть ли способ
- Заранее создайте хэш PDF-документа на одном экземпляре PdfReader.
- Создайте подпись
- Примените подпись к другому экземпляру PdfReader
Прилагается полный пример процесса (включая создание подписи, которое на самом деле должно выполняться другой службой).
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using iText.Kernel.Pdf;
using iText.Signatures;
using Org.BouncyCastle.X509;
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
namespace SignExternalTestManuel
{
class Program
{
const string filePath = @"c:\temp\pdfsign\";
public static string pdfToSign = Path.Combine(filePath, @"test.pdf");
public static string destinationFile = Path.Combine(filePath, "test_signed.pdf");
public static string LocalUserCertificatePublicKey = Path.Combine(filePath, "BITSignTestManuel5Base64.cer");
public static string LocalCaCertificatePublicKey = Path.Combine(filePath, "BITRoot5Base64.cer");
public static string privateKeyFile = Path.Combine(filePath, "BITSignTestManuel5.pfx");
public static string privateKeyPassword = "test";
public static void Main(String[] args)
{
PdfReader reader = new PdfReader(pdfToSign);
using (FileStream os = new FileStream(destinationFile, FileMode.OpenOrCreate))
{
StampingProperties stampingProperties = new StampingProperties();
stampingProperties.UseAppendMode();
PdfSigner pdfSigner = new PdfSigner(reader, os, stampingProperties);
pdfSigner.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);
IExternalSignatureContainer external = new GsSignatureContainer(
PdfName.Adobe_PPKLite,
PdfName.Adbe_pkcs7_detached);
pdfSigner.SetSignDate(new DateTime(2021, 2, 22, 10, 0, 0));
pdfSigner.SetFieldName("MySignatureField");
pdfSigner.SignExternalContainer(external, 32000);
}
}
}
public class GsSignatureContainer : IExternalSignatureContainer
{
private PdfDictionary sigDic;
public GsSignatureContainer(PdfName filter, PdfName subFilter)
{
sigDic = new PdfDictionary();
sigDic.Put(PdfName.Filter, filter);
sigDic.Put(PdfName.SubFilter, subFilter);
}
/// <summary>
/// Implementation based on https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7#HowtouseaDigitalSigningService(DSS)suchasGlobalSign,withiText7-Examplecode
/// </summary>
/// <param name="pdfStream"></param>
/// <returns></returns>
public byte[] Sign(Stream pdfStream)
{
//Create the certificate chaing since the signature is just a PKCS1, the certificates must be added to the signature
X509Certificate[] chain = null;
string cert = System.IO.File.ReadAllText(Program.LocalUserCertificatePublicKey);
string ca = System.IO.File.ReadAllText(Program.LocalCaCertificatePublicKey);
chain = CreateChain(cert, ca);
X509CrlParser p = new X509CrlParser();
String hashAlgorithm = DigestAlgorithms.SHA256;
PdfPKCS7 pkcs7Signature = new PdfPKCS7(null, chain, hashAlgorithm, false);
//Create the hash of of the pdf document
//Part of my IExternalSignatureContainer Sign method
//Called from iText pdfSigner.SignExternalContainer
//The produced hash is always different
byte[] hash = DigestAlgorithms.Digest(pdfStream, DigestAlgorithms.GetMessageDigest(hashAlgorithm));
byte[] signature = null;
//Create the hash based on the document hash which is suitable for pdf siging with SHA256 and a X509Certificate
byte[] sh = pkcs7Signature.GetAuthenticatedAttributeBytes(hash, null, null, PdfSigner.CryptoStandard.CMS);
//Create the signature via own certificate
signature = CreateSignature(sh, Program.privateKeyFile, Program.privateKeyPassword);
pkcs7Signature.SetExternalDigest(signature, null, "RSA");
return pkcs7Signature.GetEncodedPKCS7(hash, null, null, null, PdfSigner.CryptoStandard.CMS);
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.PutAll(sigDic);
}
private static X509Certificate[] CreateChain(String cert, String ca)
{
//Note: The root certificate could be omitted and it would still work
X509Certificate[] chainy = new X509Certificate[2];
X509CertificateParser parser = new X509CertificateParser();
chainy[0] = new X509Certificate(parser.ReadCertificate(Encoding.UTF8.GetBytes(cert))
.CertificateStructure);
chainy[1] = new X509Certificate(parser.ReadCertificate(Encoding.UTF8.GetBytes(ca))
.CertificateStructure);
return chainy;
}
#region "Create signature, will be done by an actual service"
private byte[] CreateSignature(byte[] hash, string privateKeyFile, string privateKeyPassword)
{
//Sign data directly with a X509Certificate
X509Certificate2 rootCertificateWithPrivateKey = new X509Certificate2();
byte[] rawData = System.IO.File.ReadAllBytes(privateKeyFile);
rootCertificateWithPrivateKey.Import(rawData, privateKeyPassword, X509KeyStorageFlags.Exportable);
using (var key = rootCertificateWithPrivateKey.GetRSAPrivateKey())
{
return key.SignData(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
#endregion
}
}