Мне нужно добавить криптографическую подпись в документ PDF с помощью CMS.В конце концов, коммерческая служба будет использоваться для создания подписи на основе хеша из PDF-документа.В данный момент я пытаюсь создать такой документ, используя самозаверяющие сертификаты.
Для этого я использую библиотеку ITextSharp версии 5.5.13.Я создал простое консольное приложение, которое по существу выполняет следующие действия:
- Создает хеш и подписанный подготовленный документ PDF на основе существующего документа PDF.
- Создает подпись SHA256 изхэш с использованием сертификата X509
- Добавьте подпись к документу pdf, созданному на шаге 1
Уже работает следующее:
- Создайте подготовленный документ PDF
- Создание хеш-значения
- Создание подписи SHA256
Но я не могу применить подпись к подготовленному PDF-документу.
Я загружаю подготовленный pdf, но внешний вид подписи равен нулю.Мне нужно добавить криптографическую подпись в PDF-документ с помощью CMS.В конце концов, коммерческая служба будет использоваться для создания подписи на основе хеша из PDF-документа.В данный момент я пытаюсь создать такой документ, используя самозаверяющие сертификаты.
Но я не могу применить подпись к подготовленному PDF-документу.Я загружаю подготовленный PDF, но внешний вид подписи равен нулю.
public void AddSignatureToPreparedPdf(byte[] signature)
{
//read the prepared pdf document (This document is ready to be signed)
using (PdfReader reader = new PdfReader(PreparedForSigningDocumentPath))
{
//The filestream into which the newly signed document must be written.
using (FileStream fileStream = File.OpenWrite(SignedPdfPath))
{
PdfStamper stamper = new PdfStamper(reader, fileStream, '\0');
var appearance = stamper.SignatureAppearance;
var pdfDictionary = new PdfDictionary();
var paddedSignature = new byte[SIGNATURE_ESTIMATED_SIZE];
Array.Copy(signature, 0, paddedSignature, 0, signature.Length);
pdfDictionary.Put(PdfName.CONTENTS, new PdfString(paddedSignature).SetHexWriting(true));
Dictionary<PdfName, int> exclusionSizes = new Dictionary<PdfName, int>();
exclusionSizes.Add(PdfName.CONTENTS, (SIGNATURE_ESTIMATED_SIZE * 2) + 2);
appearance.PreClose(exclusionSizes);
appearance.Close(pdfDictionary);
}
}
}
Пример ниже содержит один класс со всем необходимым кодом для запуска. Для его запуска требуются следующие задачи: Измените пути к файлам PDF, создайте самозаверяющий сертификат и измените его.путь и пароль в коде
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using com.itextpdf.text.pdf.security;
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.security;
using Org.BouncyCastle.Pkcs;
namespace MobileIDTestConsole
{
class Program
{
static void Main(string[] args)
{
string unsignedDocumentPath = @"C:\temp\UnsignedDocument.pdf";
string signedDocumentPath = @"C:\temp\SignedDocument.pdf";
string preparedForSigningDocumentPath = @"C:\temp\SigningPreparedDocument.pdf";
ITextTest iTextTest = new ITextTest(unsignedDocumentPath, signedDocumentPath, preparedForSigningDocumentPath);
//Create the new sign-ready pdf, and returns the hash value
iTextTest.PreparePdfForSigning();
//Sign the hash value
// >> This is eventually done by a dedicated service <<
var signature = iTextTest.SignHash();
//Add the newly created signature to the previously created document
iTextTest.AddSignatureToPreparedPdf(signature);
}
}
public class ITextTest
{
//Space allocated in the new pdf document for the signature
private int SIGNATURE_ESTIMATED_SIZE = 8192;
//Path to my test certificate (including the private key). Needs to be replaced with certificate from cert store
private string CertificatePath = @"C:\project\tech\mobileidtest\ws16.pfx";
private string CertificatePassword = "xxx";
private readonly string UnsignedPdfPath;
private readonly string PreparedForSigningDocumentPath;
private readonly string SignedPdfPath;
public string PdfHash { get; set; }
public ITextTest(string UnsignedPdfPath, string SignedPdfPath, string preparedForSigningDocumentPath)
{
this.UnsignedPdfPath = UnsignedPdfPath;
this.SignedPdfPath = SignedPdfPath;
this.PreparedForSigningDocumentPath = preparedForSigningDocumentPath;
}
/// <summary>
/// Prepare a pdf document for signing
/// * Add a signature placeholder
/// * Create a hash which will be signed later
/// * Save the prepared pdf document
/// </summary>
public void PreparePdfForSigning()
{
using (PdfReader reader = new PdfReader(UnsignedPdfPath))
{
FileStream os = File.OpenWrite(PreparedForSigningDocumentPath);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(500, 150, 400, 200), 1, "SignatureFieldName1");
appearance.SignDate = DateTime.Now;
appearance.Reason = "My signing reason";
appearance.Location = "Somewhere";
appearance.Contact = "Foo Bar";
StringBuilder buf = new StringBuilder();
buf.Append("Digitally signed by");
buf.Append("\n");
buf.Append("Foo Bar");
buf.Append("\n");
buf.Append("Date: " + appearance.SignDate);
appearance.Layer2Text = buf.ToString();
appearance.Acro6Layers = true;
appearance.CertificationLevel = 0;
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED)
{
Date = new PdfDate(appearance.SignDate),
Name = "Foo Bar"
};
dic.Reason = appearance.Reason;
dic.Location = appearance.Location;
dic.Contact = appearance.Contact;
appearance.CryptoDictionary = dic;
Dictionary<PdfName, int> exclusionSizes = new Dictionary<PdfName, int>();
exclusionSizes.Add(PdfName.CONTENTS, (SIGNATURE_ESTIMATED_SIZE * 2) + 2);
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_SHA1);
MakeSignature.SignExternalContainer(appearance, external, SIGNATURE_ESTIMATED_SIZE);
// appearance.PreClose(exclusionSizes); >> not sure whether this is required
HashAlgorithm sha = new SHA256CryptoServiceProvider();
Stream s = appearance.GetRangeStream();
int read = 0;
byte[] buff = new byte[0x2000];
while ((read = s.Read(buff, 0, 0x2000)) > 0)
{
sha.TransformBlock(buff, 0, read, buff, 0);
}
sha.TransformFinalBlock(buff, 0, 0);
StringBuilder hex = new StringBuilder(sha.Hash.Length * 2);
foreach (byte b in sha.Hash)
hex.AppendFormat("{0:x2}", b);
PdfHash = hex.ToString();
}
}
/// <summary>
/// Create a SHA256 signature from a given hash value
/// It uses a self signed certificate
/// Once this runs properly, this task will be performed by a secure signing service
/// </summary>
/// <returns>A SHA256 signature</returns>
public byte[] SignHash()
{
if (string.IsNullOrEmpty(PdfHash))
throw new Exception("no hash available");
byte[] hash = StringToByteArray(PdfHash);
FileStream fs = new FileStream(CertificatePath, FileMode.Open);
Pkcs12Store store = new Pkcs12Store(fs, CertificatePassword.ToCharArray());
String alias = "";
foreach (string al in store.Aliases)
if (store.IsKeyEntry(al) && store.GetKey(al).Key.IsPrivate)
{
alias = al;
break;
}
AsymmetricKeyEntry pk = store.GetKey(alias);
X509CertificateEntry[] chain = store.GetCertificateChain(alias);
List<Org.BouncyCastle.X509.X509Certificate> c = new List<Org.BouncyCastle.X509.X509Certificate>();
foreach (X509CertificateEntry en in chain)
{
c.Add(en.Certificate);
}
PrivateKeySignature signature = new PrivateKeySignature(pk.Key, "SHA256");
String hashAlgorithm = signature.GetHashAlgorithm();
PdfPKCS7 sgn = new PdfPKCS7(null, c, hashAlgorithm, false);
DateTime signingTime = DateTime.Now;
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
byte[] extSignature = signature.Sign(sh);
sgn.SetExternalDigest(extSignature, null, signature.GetEncryptionAlgorithm());
return sgn.GetEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);
}
/// <summary>
/// Creates a byte array from a hex string
/// </summary>
/// <param name="hex"></param>
/// <returns></returns>
private static byte[] StringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
/// <summary>
/// Adds a signature to the prepared pdf document
/// </summary>
/// <param name="signature"></param>
public void AddSignatureToPreparedPdf(byte[] signature)
{
//read the prepared pdf document (This document is ready to be signed)
using (PdfReader reader = new PdfReader(PreparedForSigningDocumentPath))
{
//The filestream into which the newly signed document must be written.
using (FileStream fileStream = File.OpenWrite(SignedPdfPath))
{
PdfStamper stamper = new PdfStamper(reader, fileStream, '\0');
var appearance = stamper.SignatureAppearance;
var pdfDictionary = new PdfDictionary();
var paddedSignature = new byte[SIGNATURE_ESTIMATED_SIZE];
Array.Copy(signature, 0, paddedSignature, 0, signature.Length);
pdfDictionary.Put(PdfName.CONTENTS, new PdfString(paddedSignature).SetHexWriting(true));
Dictionary<PdfName, int> exclusionSizes = new Dictionary<PdfName, int>();
exclusionSizes.Add(PdfName.CONTENTS, (SIGNATURE_ESTIMATED_SIZE * 2) + 2);
appearance.PreClose(exclusionSizes);
appearance.Close(pdfDictionary);
}
}
}
}
}