Я пытаюсь подписать PDF-файл цифровой подписью в два этапа: вычислить хэш, подписать хэш, а затем объединить подписанный хеш с выходным файлом. Я использую библиотеку iText 7.
Однако в результате файл поврежден и не может быть открыт.
Что не так?
Сначала я вычисляю хэш и подписываю входной файл пустым контейнером для подписи:
public static byte[] ComputeHash(string source, string temp, X509Certificate[] chains, string reason, string location, int qtySigns, int pageNumber)
{
IList<ICrlClient> crlList = new List<ICrlClient>();
crlList.Add(new CrlClientOnline(chains));
using (var reader = new PdfReader(source))
{
using (var os = new FileStream(temp, FileMode.OpenOrCreate, FileAccess.Write))
{
var signer = new PdfSigner(reader, os, new StampingProperties().UseAppendMode());
var signatureAppearance = signer.GetSignatureAppearance();
signatureAppearance
.SetReason(reason)
.SetLocation(location)
.SetPageNumber(pageNumber)
.SetRenderingMode(PdfSignatureAppearance.RenderingMode.NAME_AND_DESCRIPTION)
.SetCertificate(chains[0])
.SetPageRect(new iText.Kernel.Geom.Rectangle(36, 20, 144, 53));
var container = new EmptySignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signer.SignExternalContainer(container, 8192);
byte[] hash = container.Hash;
return hash;
}
}
}
Вот контейнер:
public class EmptySignatureContainer : IExternalSignatureContainer
{
private PdfDictionary _sigDic;
public byte[] Hash;
public byte[] Sign(Stream data)
{
string hashAlgorithm = "SHA256";
try
{
this.Hash = DigestAlgorithms.Digest(data, hashAlgorithm);
}
catch (IOException e)
{
throw new GeneralSecurityException("EmptySignatureContainer signing exception", e);
}
return new byte[0];
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.PutAll(_sigDic);
}
public EmptySignatureContainer(PdfName filter, PdfName subFilter)
{
_sigDic = new PdfDictionary();
_sigDic.Put(PdfName.Filter, filter);
_sigDic.Put(PdfName.SubFilter, subFilter);
}
}
После этого я получаю аутентифицированные байты атрибута и вычисляю его хэш:
var hash = ComputeHash(inputFile, tempFile, chain, "sign-reason", "", 1, 1);
PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", false);
var authenAttrBytes = sgn.GetAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, null, null);
var computedHash = SHA256Managed.Create().ComputeHash(authenAttrBytes);`
Затем вычисленный хэш будет отправлен на сервер hsm для подписи. Затем подписанный хэш будет добавлен обратно во временный файл в виде следующего кода:
Sign(tempFile, outputFile, chain, authenAttrBytes, signedHash);
А вот и метод Sign:
public static void Sign(string tempFile, string targetFile, X509Certificate[] chains, byte[] hash, byte[] signedHash)
{
using (PdfReader reader = new PdfReader(tempFile))
{
using (FileStream outStream = System.IO.File.OpenWrite(targetFile))
{
var signedContainer = new SignedSignatureContainer(hash, signedHash, chains);
var signer = new PdfSigner(reader, outStream, new StampingProperties());
signer.SignExternalContainer(signedContainer, 8192);
}
}
}
Подписанный контейнер выглядит следующим образом:
public class SignedSignatureContainer : IExternalSignatureContainer
{
public byte[] Hash { get; set; }
public byte[] SignedHash { get; set; }
public X509Certificate[] CertChains { get; set; }
public SignedSignatureContainer(byte[] hash, byte[] signedHash, X509Certificate[] certCertChains)
{
this.Hash = hash;
this.SignedHash = signedHash;
this.CertChains = certCertChains;
}
public byte[] Sign(Stream data)
{
var sgn = new PdfPKCS7(null, CertChains, "SHA256", false);
sgn.SetExternalDigest(this.SignedHash, null, "RSA");
return sgn.GetEncodedPKCS7(this.Hash, CryptoStandard.CMS, null, null, null );
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite);
signDic.Put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached);
}
}
В вашем методе Sign
вы используете метод PdfSigner
SignExternalContainer
:
var signedContainer = new SignedSignatureContainer(hash, signedHash, chains);
var signer = new PdfSigner(reader, outStream, new StampingProperties());
signer.SignExternalContainer(signedContainer, 8192);
Но это не заполняет изначально подготовленную подпись! Вместо этого создается другое поле подписи и заполняется подписью исходного.
Вместо этого используйте статический PdfSigner
метод SignDeferred
:
/// <summary>Signs a PDF where space was already reserved.</summary>
/// <param name = "document">the original PDF</param>
/// <param name = "fieldName">the field to sign. It must be the last field</param>
/// <param name = "outs">the output PDF</param>
/// <param name = "externalSignatureContainer">
/// the signature container doing the actual signing. Only the
/// method ExternalSignatureContainer.sign is used
/// </param>
public static void SignDeferred(PdfDocument document, String fieldName, Stream outs,
IExternalSignatureContainer externalSignatureContainer)