How to use with CloudHSM

Hi there!

Right now I am using GemBox.pdf to add a signature to my PDF using a YubicoKey.

In future, I would like to use a CloudHSM service (Google KMS) to sign my PDF documents.
This means that my private key will be in the cloud instead of on my YubicoKey.

Is there any documented example how to sign using a CloudHSM?
I do not find any information about how to add a hash digest manually.

Hi,

We didn’t try using AWS CloudHSM nor Google KMS but based on our review of those products, both should be able to integrate with GemBox.Pdf.

For AWS CloudHSM, you should use their PKCS#11 Library. Based on their Installing instructions, Windows binaries of the PKCS #11 Library for Client SDK 5 will be located in ‘C:\ProgramFiles\Amazon\CloudHSM’. You must specify a path to a DLL file contained in this directory when creating an instance of a PdfPkcs11Module class.

Note that you must bootstrap Client SDK 5. For more information about bootstrapping, see Bootstrapping the Client SDK.

Also, note that the PIN that you specify in the PdfPkcs11Token.Login method must be in a format as specified in the Authenticating to the PKCS #11 Library page.

If their implementation of PKCS#11 standard is correct, the rest of the code from the example PDF digital signature with PKCS#11 (Cryptoki) device in C# and VB.NET should work as expected (as long as you provide the correct values for token label and certificate subject common name from your ASW CloudHSM that are used in that example).

For Google KMS, you must use their client library for C# as explained in the Cloud KMS Client Libraries page.

After that, you should use a combination of a code in the Creating and validating digital signatures page and the Digitally sign a PDF file with an external signature example like in the following code snippet:

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using GemBox.Pdf;
using GemBox.Pdf.Forms;
using GemBox.Pdf.Security;
using Google.Cloud.Kms.V1;
using Google.Protobuf;

class Program
{
    static void Main()
    {
        // If using Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY");

        using (var document = PdfDocument.Load("Reading.pdf"))
        {
            // Add a visible signature field to the first page of the PDF document.
            var signatureField = document.Form.Fields.AddSignature(document.Pages[0], 300, 500, 250, 50);

            // Get a digital ID from Google Cloud KMS.
            using (var digitalId = new GoogleCloudKMSDigitalId())
            {
                // Create a PDF signer that will create the digital signature.
                var signer = new PdfSigner(digitalId);

                // Initiate signing of a PDF file with the specified signer.
                signatureField.Sign(signer);

                // Finish signing of a PDF file.
                document.Save("Google Cloud KMS Digital Signature.pdf");
            }
        }
    }
}

/// <summary>
/// Represents a digital ID that uses Google Cloud KMS to create a digital signature.
/// </summary>
/// <seealso href="https://cloud.google.com/kms/docs/create-validate-signatures">Creating and validating digital signatures</seealso>
class GoogleCloudKMSDigitalId : PdfDigitalId, IDisposable
{
    private RSA privateAndPublicKey;

    public GoogleCloudKMSDigitalId(string projectId = "my-project", string locationId = "us-east1", string keyRingId = "my-key-ring", string keyId = "my-key", string keyVersionId = "123") : this(new RSAGoogleCloudKMS(KeyManagementServiceClient.Create(), new CryptoKeyVersionName(projectId, locationId, keyRingId, keyId, keyVersionId)))
    {
    }

    private GoogleCloudKMSDigitalId(RSA privateAndPublicKey) : base(CreateCertificate(privateAndPublicKey))
    {
        this.privateAndPublicKey = privateAndPublicKey;
    }

    private static PdfCertificate CreateCertificate(RSA privateAndPublicKey)
    {
        var certificateRequest = new CertificateRequest("CN=GemBox Common Name, OU=End entity, O=GemBox Software, L=Zagreb, C=HR", privateAndPublicKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

        // https://tools.ietf.org/html/rfc5280#page-39
        certificateRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, true, 0, true));
        // https://tools.ietf.org/html/rfc5280#page-29
        certificateRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation | X509KeyUsageFlags.KeyCertSign, true));

        var generator = X509SignatureGenerator.CreateForRSA(privateAndPublicKey, RSASignaturePadding.Pkcs1);
        var now = DateTimeOffset.Now;
        var serialNumber = new byte[8];
        RandomNumberGenerator.Fill(serialNumber);
        var certificate = certificateRequest.Create(certificateRequest.SubjectName, generator, now.AddDays(-1), now.AddYears(1), serialNumber);

        return new PdfCertificate(certificate);
    }

    public void Dispose()
    {
        if (this.privateAndPublicKey != null)
        {
            this.privateAndPublicKey.Dispose();
            this.privateAndPublicKey = null;
        }
    }

    protected override byte[] SignHash(byte[] hash, PdfHashAlgorithm hashAlgorithm, PdfRSASignaturePadding rsaSignaturePadding)
    {
        return this.privateAndPublicKey.SignHash(hash, new HashAlgorithmName(hashAlgorithm.ToString()), rsaSignaturePadding?.ToString() == "Pss" ? RSASignaturePadding.Pss : RSASignaturePadding.Pkcs1);
    }
}

/// <summary>
/// Represents an implementation of the <see cref="RSA" /> algorithm that uses Google Cloud KMS for signing and retrieving the public key.
/// </summary>
class RSAGoogleCloudKMS : RSA
{
    private readonly KeyManagementServiceClient client;
    private readonly CryptoKeyVersionName keyVersionName;

    private RSA publicKey;

    public RSAGoogleCloudKMS(KeyManagementServiceClient client, CryptoKeyVersionName keyVersionName)
    {
        this.client = client;
        this.keyVersionName = keyVersionName;

        // See: https://cloud.google.com/kms/docs/retrieve-public-key
        var result = client.GetPublicKey(keyVersionName);

        this.publicKey = RSA.Create();
        this.publicKey.ImportFromPem(result.Pem);
    }

    public override void ImportParameters(RSAParameters parameters) => this.publicKey.ImportParameters(parameters);

    public override RSAParameters ExportParameters(bool includePrivateParameters) => this.publicKey.ExportParameters(includePrivateParameters);

    protected override byte[] HashData(byte[] data, int offset, int count, HashAlgorithmName hashAlgorithm)
    {
        using (HashAlgorithm algorithm = HashAlgorithm.Create(hashAlgorithm.Name))
            return algorithm.ComputeHash(data, offset, count);
    }

    public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding)
    {
        // Build the digest.
        Digest digest = new Digest();
        switch (hashAlgorithm.ToString())
        {
            case "SHA256":
                digest.Sha256 = ByteString.CopyFrom(hash);
                break;

            case "SHA384":
                digest.Sha384 = ByteString.CopyFrom(hash);
                break;

            case "SHA512":
                digest.Sha512 = ByteString.CopyFrom(hash);
                break;

            default:
                throw new NotSupportedException();
        }

        // Call the API.
        AsymmetricSignResponse result = this.client.AsymmetricSign(this.keyVersionName, digest);

        // Get the signature.
        byte[] signature = result.Signature.ToByteArray();

        // Return the result.
        return signature;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (this.publicKey != null)
            {
                this.publicKey.Dispose();
                this.publicKey = null;
            }
        }

        base.Dispose(disposing);
    }
}

This code dynamically creates a self-signed X509 certificate from the RSA public key retrieved from the Google KMS and other information (subject name, certificate extensions, validity interval, and serial number) with the System.Security.Cryptography.X509Certificates.CertificateRequest class because PDF digital signature requires that, at least, the signer’s certificate is stored in the signed PDF file.

This code requires .NET 5 because it uses RSA.ImportFromPem method.

Regards,
Mario

Hi Mario,

thank you very much for your detailed answer. This has helped me a lot!
I really appreciate that you have given so much effort to support me!