The PDF Document API allows you to replace a built-in PKCS#7 signature builder (the Pkcs7Signer
class) with a custom signer.
The code sample project shows how to create a Pkcs7SignerBase
descendant to implement a custom signer based on the Bouncy Castle C# API and calculate a document hash using a custom digest calculator.
To sign a PDF file using an external web service, retrieve a certificate/certificate chain from the service, and calculate the document hash. Then, sign the calculated document hash with a private key obtained from the external service in the SignDigest
method of the BouncyCastleSigner
class.
[!IMPORTANT]
The Universal Subscription or Office File API Subscription is required to use this example in production code. Please refer to the following page for pricing information: DevExpress Subscriptions
Files to Review
Documentation
More Examples
- Use a Custom Timestamp Client to Apply Signatures to a Document
- Use the Azure Key Vault API to Sign a PDF document
- Validate Document Signatures
Does this example address your development requirements/objectives?
(you will be redirected to DevExpress.com to submit your response)
Example Code
C#using DevExpress.Pdf;
using System;
using System.Diagnostics;
using DevExpress.Office.Tsp;
using DevExpress.Office.DigitalSignatures;
namespace CustomSigner
{
static class Program
{
static void Main(string[] args)
{
using (var signer = new PdfDocumentSigner(@"Document.pdf"))
{
//Create a timestamp:
ITsaClient tsaClient = new TsaClient(new Uri(@"https://freetsa.org/tsr"), HashAlgorithmType.SHA256);
//Specify the signature's field name and location:
var description = new PdfSignatureFieldInfo(1);
description.Name = "SignatureField";
description.SignatureBounds = new PdfRectangle(10, 10, 50, 150);
//Create a custom signer object:
var bouncyCastleSigner = new BouncyCastleSigner("certificate.pfx", "123", tsaClient);
//Apply a signature to a new form field:
var signatureBuilder = new PdfSignatureBuilder(bouncyCastleSigner, description);
//Specify the image and signer information:
signatureBuilder.SetImageData(System.IO.File.ReadAllBytes("signature.jpg"));
signatureBuilder.Location = "LOCATION";
//Sign and save the document:
signer.SaveDocument("SignedDocument.pdf", signatureBuilder);
Process.Start(new ProcessStartInfo("SignedDocument.pdf") { UseShellExecute = true}) ;
}
return;
}
}
}
C#using DevExpress.Pdf;
using DevExpress.Office.DigitalSignatures;
using DevExpress.Office.Tsp;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace CustomSigner
{
// Declare a custom class to calculate a digest value:
public class BouncyCastleDigestCalculator : IDigestCalculator
{
readonly IDigest digest;
public string AlgorithmOid => DigestUtilities.GetObjectIdentifier(digest.AlgorithmName).Id;
public BouncyCastleDigestCalculator()
{
digest = new Sha512Digest();
}
public byte[] ComputeDigest(Stream stream)
{
digest.Reset();
byte[] buffer = new byte[1024 * 1024];
int readByteCount;
do
{
readByteCount = stream.Read(buffer, 0, buffer.Length);
if (readByteCount != 0)
digest.BlockUpdate(buffer, 0, readByteCount);
}
while (readByteCount != 0);
byte[] result = new byte[GetDigestSize()];
digest.DoFinal(result, 0);
return result;
}
public int GetDigestSize()
{
return digest.GetDigestSize();
}
}
public class BouncyCastleSigner : Pkcs7SignerBase
{
//Specify the signing algoritm's OID:
const string PKCS1RsaEncryption = "1.2.840.113549.1.1.1";
readonly byte[][] certificates;
readonly IAsymmetricBlockCipher rsaEngine;
//Specify a custom digest calculator:
protected override IDigestCalculator DigestCalculator { get { return new BouncyCastleDigestCalculator(); } }
protected override string SigningAlgorithmOID => PKCS1RsaEncryption;
public BouncyCastleSigner(string file, string password, ITsaClient tsaClient) : base(tsaClient, null, null, PdfSignatureProfile.Pdf)
{
//Read PKCS#12 file:
var pkcs = new Pkcs12StoreBuilder().Build();
pkcs.Load(File.Open(file, FileMode.Open), password.ToCharArray());
//Get the certificate's alias:
var alias = pkcs.Aliases.OfType<string>().First(a => pkcs.IsKeyEntry(a));
//Get the certificate's chain:
certificates = pkcs.GetCertificateChain(alias).Select(c => c.Certificate.GetEncoded()).ToArray();
//Initialize the encryption engine:
rsaEngine = new Pkcs1Encoding(new RsaBlindedEngine());
rsaEngine.Init(true, pkcs.GetKey(alias).Key);
}
protected override IEnumerable<byte[]> GetCertificates()
{
return certificates;
}
protected override byte[] SignDigest(byte[] digest)
{
//Create the digest info object
//Encrypted by the signer's private key:
var dInfo = new DigestInfo(new AlgorithmIdentifier(new DerObjectIdentifier(DigestCalculator.AlgorithmOid), DerNull.Instance), digest);
byte[] digestInfo = dInfo.GetEncoded();
return rsaEngine.ProcessBlock(digestInfo, 0, digestInfo.Length);
}
}
}