public const string DIGEST_ALGORITM = "SHA256"; public const int ESTIMATED_SIGNATURE_SIZE = 12000; // 1st step - prepare signature public static byte[] PrepareSignature(PrepSignatureData data, out byte[] resultPdf) { int pageNum = data.Page - 1; PdfLoadedDocument doc = new(data.SourcePdf); PdfLoadedPage page = doc.Pages[pageNum] as PdfLoadedPage; DigestSigningExternalPreSigner externalSigner = new(DIGEST_ALGORITM); List certificates = new() { X509Certificate2.CreateFromPem(data.CertificatePem) }; PdfSignature signature = new(doc, page, null, data.SignatureName); signature.Bounds = new RectangleF(data.PositionX, data.PositionY, data.Width, data.Height); signature.SignedName = data.SignerName; signature.Settings.CryptographicStandard = CryptographicStandard.CADES; signature.Settings.DigestAlgorithm = DigestAlgorithm.SHA256; signature.EstimatedSignatureSize = ESTIMATED_SIGNATURE_SIZE; signature.EnableValidationAppearance = true; // currently we are creating the appearance like this, but at this point we don't have the actual signing date and time PdfGraphics appearance = signature.Appearance.Normal.Graphics; appearance.DrawString( $"Signed by {data.SignerName}\nSigned date: {???}", data.Font, PdfBrushes.Black, new PointF(0, 0) ); signature.AddExternalSigner(externalSigner, certificates, null); signature.EnableLtv = true; Uri timeStampServer = new(data.TimeStampUrl); signature.TimeStampServer = new(timeStampServer, data.TimeStampUsername, data.TimeStampPassword); signature.CreateLongTermValidity(certificates, true); using (MemoryStream ms = new()) { doc.Save(ms); doc.Close(true); resultPdf = ms.ToArray(); } return externalSigner.DocumentHash; } // 2nd step - complete signature public static byte[] CompleteSignature(CompleteSignatureData data) { var externalSigner = new DigestSigningExternalPostSigner(DIGEST_ALGORITM, data.SignedDigest, data.TimeStampUrl, data.TimeStampUsername, data.TimeStampPassword); List certificates = new() { X509Certificate2.CreateFromPem(data.CertificatePem) }; byte[] resultPdf; using (MemoryStream preparedPdfMs = new(data.PreparedPdf)) using (MemoryStream resultPdfMs = new()) { // probably we have to do something here, so we can add the correct date and time of the signing in the appearance PdfSignature.ReplaceEmptySignature(preparedPdfMs, "", resultPdfMs, data.SignatureName, externalSigner, certificates); resultPdf = resultPdfMs.ToArray(); } return resultPdf; } // IPdfExternalSigner implementations: public class DigestSigningExternalPreSigner : IPdfExternalSigner { private readonly string _hashAlgorithm; public DigestSigningExternalPreSigner(string hashAlg) { this._hashAlgorithm = hashAlg; } public string HashAlgorithm => _hashAlgorithm; public byte[] DocumentHash { get; private set; } public byte[] Sign(byte[] message, out byte[] timeStampResponse) { using (var s = SHA256.Create()) { DocumentHash = s.ComputeHash(message); } timeStampResponse = null; return null; } } public class DigestSigningExternalPostSigner : IPdfExternalSigner { private readonly string _HashAlgorithm; private readonly byte[] SignedHash; private readonly string TimeStampUrl; private readonly string TimeStampUsername; private readonly string TimeStampPassword; public DigestSigningExternalPostSigner( string hashAlgorithm, byte[] signedHash, string timeStampUrl, string timeStampUsername, string timeStampPassword) { this._HashAlgorithm = hashAlgorithm; this.SignedHash = signedHash; this.TimeStampUrl = timeStampUrl; this.TimeStampUsername = timeStampUsername; this.TimeStampPassword = timeStampPassword; } public string HashAlgorithm => this._HashAlgorithm; public byte[] Sign(byte[] message, out byte[] timeStampResponse) { timeStampResponse = TimeStamp(this.SignedHash); return this.SignedHash; } public byte[] TimeStamp(byte[] data) { byte[] hash; using (var sha256 = SHA256.Create()) { hash = sha256.ComputeHash(data); } TimeStampRequestGenerator requestGenerator = new TimeStampRequestGenerator(); requestGenerator.SetCertReq(true); TimeStampRequest tsReq = requestGenerator.Generate(TspAlgorithms.Sha256, hash); byte[] tsData = tsReq.GetEncoded(); var authValue = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{this.TimeStampUsername}:{this.TimeStampPassword}")); byte[] tsBytes; using (HttpClient client = new()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authValue); var content = new ByteArrayContent(tsData); content.Headers.ContentType = new MediaTypeHeaderValue("application/timestamp-query"); var response = client.PostAsync(this.TimeStampUrl, content).Result; response.EnsureSuccessStatusCode(); tsBytes = response.Content.ReadAsByteArrayAsync().Result; } TimeStampResponse tsResponse = new(tsBytes); tsResponse.Validate(tsReq); return tsResponse.TimeStampToken.GetEncoded(); } }