When the private key is not available to the application (a smart card, an HSM, a cloud key or a remote signing server), SignLib separates the preparation of the signature from the cryptographic operation. The library prepares the data to sign and a class written by you, which implements IExternalSignature, creates the signature value.

How it works

  1. The application gives the library only the public part of the signing certificate (DigitalSignatureCertificate).
  2. It binds the provider to the next signature with DigitalCertificate.UseExternalSignatureProvider. The provider is released at the end of the signing operation, so it is set again before every signature.
  3. The library calls ApplySignature(message, hashAlgorithm): the provider hashes the data and signs it with the private key, wherever the key is. The document itself is never sent.

C# code: an external signature provider

In this sample the provider signs with the key of a certificate (for example a non-exportable key of the Windows store, through CNG); in a real application it can be any signing engine.

/// <summary>
/// Signs a message: it hashes the message with the requested hash algorithm and it signs the hash with the private key.
/// </summary>
/// <param name="message">The data to sign (SignLib decides what has to be signed: for a PDF signature it is the encoded
/// CMS signed attributes). It is not a hash: the hash is computed here.</param>
/// <param name="hashAlgorithm">The OID of the hash algorithm requested by the library (for example SHA-256).</param>
/// <returns>The signature value.</returns>
public byte[] ApplySignature(byte[] message, Oid hashAlgorithm)
{
    HashAlgorithmName hashAlgorithmName = GetHashAlgorithmName(hashAlgorithm.Value);

    // RSA certificate: PKCS#1 v1.5 signature.
    using (RSA rsaKey = _certificateWithPrivateKey.GetRSAPrivateKey())
    {
        if (rsaKey != null)
            return rsaKey.SignData(message, hashAlgorithmName, RSASignaturePadding.Pkcs1);
    }

    // ECDSA certificate: SignData returns the raw signature value (r || s). SignLib converts it to the form
    // required by the signature format.
    using (ECDsa ecdsaKey = _certificateWithPrivateKey.GetECDsaPrivateKey())
    {
        if (ecdsaKey != null)
            return ecdsaKey.SignData(message, hashAlgorithmName);
    }

    throw new CryptographicException("The private key of the certificate was not found or its algorithm is not supported.");
}

/// <summary>
/// Signs a PDF file using an external signature provider.
/// </summary>
static void SignPdfWithExternalSignature(string unsignedPdfFile, string signedPdfFile)
{
    PdfSignature pdfSignature = new PdfSignature(LibrarySerialNumber);

    pdfSignature.LoadPdfDocument(unsignedPdfFile);

    pdfSignature.HashAlgorithm = SignLib.HashAlgorithm.SHA256;

    X509Certificate2 certificateWithPrivateKey = LoadCertificate();

    // The signing certificate given to the library is only the public part of the certificate: the library does not
    // need the private key, because the signature value is created by the external signature provider.
    pdfSignature.DigitalSignatureCertificate = new X509Certificate2(certificateWithPrivateKey.RawData);

    // Bind the external signature provider to the next signature. The provider is released by the library at the end
    // of the signing operation, so it must be set again before every signature.
    DigitalCertificate.UseExternalSignatureProvider = new CertificateStoreExternalSignature(certificateWithPrivateKey);

    File.WriteAllBytes(signedPdfFile, pdfSignature.ApplyDigitalSignature());

    Console.WriteLine("The PDF signature was created." + Environment.NewLine);
}

C# code: a remote signing service

The project PDF Remote External Digital Signature sends the data to sign to a web service and receives the signature value. Replace the calls of the web service with the calls of your own signing service.

/// <summary>
/// An external signature provider that gets the signature value from a remote signing web service.
/// The class implements SignLib.Certificates.IExternalSignature and its single method, ApplySignature.
/// </summary>
public class RemoteSignatureProvider : IExternalSignature, IDisposable
{
    private readonly ServiceReferenceRemoteSignature.RemoteSignatureSoapClient _remoteSignatureClient;

    /// <summary>
    /// The public part of the signing certificate, received from the remote server.
    /// </summary>
    public X509Certificate2 RemoteSignatureCertificate { get; }

    public RemoteSignatureProvider()
    {
        // The client of the web service (SOAP over HTTPS).
        _remoteSignatureClient = new ServiceReferenceRemoteSignature.RemoteSignatureSoapClient(
            ServiceReferenceRemoteSignature.RemoteSignatureSoapClient.EndpointConfiguration.RemoteSignatureSoap);

        // Get the signing certificate (public part only) from the remote server.
        RemoteSignatureCertificate = new X509Certificate2(_remoteSignatureClient.GetSigningCertificate());
    }

    /// <summary>
    /// Signs a message with the private key stored on the remote server.
    /// </summary>
    /// <param name="message">The data to sign (SignLib decides what has to be signed). The document itself is not sent.</param>
    /// <param name="hashAlgorithm">The OID of the hash algorithm requested by SignLib.</param>
    /// <returns>The signature value, created by the remote server.</returns>
    public byte[] ApplySignature(byte[] message, Oid hashAlgorithm)
    {
        try
        {
            // The remote server hashes the data with the hash algorithm (given by its OID) and signs the hash.
            return _remoteSignatureClient.RemoteSignWithOid(message, hashAlgorithm.Value);
        }
        catch (Exception ex)
        {
            throw new CryptographicException("The remote signature cannot be created: " + ex.Message, ex);
        }
    }

    /// <summary>
    /// Closes the connection with the web service.
    /// </summary>
    public void Dispose()
    {
        try
        {
            _remoteSignatureClient.Close();
        }
        catch (Exception)
        {
            _remoteSignatureClient.Abort();
        }
    }
}

/// <summary>
/// Signs a PDF file with a private key stored on a remote server.
/// </summary>
static void SignPdfWithRemoteSignature(string unsignedPdfFile, string signedPdfFile)
{
    PdfSignature pdfSignature = new PdfSignature(LibrarySerialNumber);

    pdfSignature.LoadPdfDocument(unsignedPdfFile);

    pdfSignature.SigningReason = "I approve this document";
    pdfSignature.SigningLocation = "Accounting department";
    pdfSignature.SignaturePosition = SignaturePosition.TopLeft;

    pdfSignature.HashAlgorithm = SignLib.HashAlgorithm.SHA256;

    using (RemoteSignatureProvider remoteSignature = new RemoteSignatureProvider())
    {
        // The library receives only the public part of the certificate: the private key stays on the remote server.
        pdfSignature.DigitalSignatureCertificate = remoteSignature.RemoteSignatureCertificate;

        // Bind the external signature provider to the next signature. The provider is released by the library at the
        // end of the signing operation, so it must be set again before every signature.
        DigitalCertificate.UseExternalSignatureProvider = remoteSignature;

        File.WriteAllBytes(signedPdfFile, pdfSignature.ApplyDigitalSignature());
    }

    Console.WriteLine("The PDF remote signature was created." + Environment.NewLine);
}

The same mechanism is used for Azure Key Vault HSM keys.

Run the full sample. The complete Visual Studio project PDF Digital Signature - External Signature is included in the library download, in the folder NET Sample Projects (.NET 8, .NET 9, .NET 10 and .NET 11), with the test files and a test certificate. Without a serial number the library runs in demo mode. Download the library and the samples · User manual

Frequently asked questions

Is the PDF document sent to the signing server?

No. Only the data to sign (the encoded signed attributes, which contain the hash of the document) is sent; the server returns the signature value.

Does it work for CAdES and XAdES signatures too?

Yes. IExternalSignature is used by all the signature formats of the library; the download also includes a CAdES remote signature project.