4

How can I achieve the very basic CSR Signing HSM functionality with Azure Key Vault?

I had found a very long and manual process to somehow achieve it:

  1. Create a private key in Key Vault
  2. Create a CSR, digest it with SHA256
  3. Sign the digest with the previous private key using the Sign() method
  4. Create a local x.509 cert and append the signature
  5. Upload the new signed cert to Key Vault

Problem is, it is manual, long (also, quite a bit of latency) and error prone. Also I haven't found a single C# code example for this, and I'm looking for EC and not RSA.

The question is, is there a simple CertificateRequest.Sign() function in Key Vault? this seems to be so basic for an HSM-like service...

Thanks

gigatt
  • 105
  • 7
NOP-MOV
  • 792
  • 2
  • 8
  • 28

2 Answers2

2

This blog post by Vitaliy Slepakov describes a solution that he created which implements the steps you listed above using C#/.NET Core.

The code is available here: https://github.com/vslepakov/keyvault-ca/

The heart of it is the following:

byte[] certificateRequest = /* ... */;
string issuerCertificateName = /* ... */;
KeyVaultServiceClient keyVaultServiceClient = /* ... */;
X509SignatureGenerator generator = /* see next section */;

var pkcs10CertificationRequest = new Pkcs10CertificationRequest(certificateRequest);
//TODO: Validate CSR via pkcs10CertificationRequest.Verify()

var info = pkcs10CertificationRequest.GetCertificationRequestInfo();
var notBefore = DateTime.UtcNow.AddDays(-1);

// Get the RSA public key from the CSR
var asymmetricKeyParameter = Org.BouncyCastle.Security.PublicKeyFactory.CreateKey(info.SubjectPublicKeyInfo);
var rsaKeyParameters = (Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters)asymmetricKeyParameter;
var rsaKeyInfo = new RSAParameters
{
    Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned(),
    Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned()
};
var publicKey = RSA.Create(rsaKeyInfo);
//TODO: Validate publicKey

var certBundle = await keyVaultServiceClient.GetCertificateAsync(issuerCertificateName).ConfigureAwait(false);

var signingCert = new X509Certificate2(certBundle.Cer);

// new serial number
var serialNumber = new byte[SerialNumberLength];
RandomNumberGenerator.Fill(serialNumber);
serialNumber[0] &= 0x7F;

var subjectDN = new X500DistinguishedName(subjectName);
var request = new CertificateRequest(subjectDN, publicKey, GetRSAHashAlgorithmName(hashSizeInBits), RSASignaturePadding.Pkcs1);

// Basic constraints
request.CertificateExtensions.Add(
    new X509BasicConstraintsExtension(caCert, caCert, 0, true));

// Subject Key Identifier
var ski = new X509SubjectKeyIdentifierExtension(
    request.PublicKey,
    X509SubjectKeyIdentifierHashAlgorithm.Sha1,
    false);

request.CertificateExtensions.Add(ski);

// Authority Key Identifier
if (issuerCAKeyCert != null)
    request.CertificateExtensions.Add(BuildAuthorityKeyIdentifier(issuerCAKeyCert));
else
    request.CertificateExtensions.Add(BuildAuthorityKeyIdentifier(subjectDN, serialNumber.Reverse().ToArray(), ski));

if (caCert)
    request.CertificateExtensions.Add(
        new X509KeyUsageExtension(
            X509KeyUsageFlags.DigitalSignature |
            X509KeyUsageFlags.KeyCertSign |
            X509KeyUsageFlags.CrlSign,
            true));
else
{
    // Key Usage
    var defaultFlags =
        X509KeyUsageFlags.DigitalSignature |
        X509KeyUsageFlags.DataEncipherment |
        X509KeyUsageFlags.NonRepudiation |
        X509KeyUsageFlags.KeyEncipherment;

    request.CertificateExtensions.Add(new X509KeyUsageExtension(defaultFlags, true));

    // Enhanced key usage
    request.CertificateExtensions.Add(
        new X509EnhancedKeyUsageExtension(
            new OidCollection {
            new Oid("1.3.6.1.5.5.7.3.1"),
            new Oid("1.3.6.1.5.5.7.3.2") }, true));
}

if (issuerCAKeyCert != null)
{
    if (notAfter > issuerCAKeyCert.NotAfter)
    {
        notAfter = issuerCAKeyCert.NotAfter;
    }
    if (notBefore < issuerCAKeyCert.NotBefore)
    {
        notBefore = issuerCAKeyCert.NotBefore;
    }
}

var issuerSubjectName = issuerCAKeyCert != null ? issuerCAKeyCert.SubjectName : subjectDN;
X509Certificate2 signedCert = request.Create(
    issuerSubjectName,
    generator,
    notBefore,
    notAfter,
    serialNumber);

The custom X509SignatureGenerator implementation is here and used the following Key Vault SDK method:

HashAlgorithm hash = /* see GitHub */;
var digest = hash.ComputeHash(data);

var resultKeyVaultPkcs = await keyVaultClient.SignAsync(signingKey, algorithm, digest, RSASignaturePadding.Pkcs1);

Hopefully you can adapt this code to meet your needs. I'll be doing that as well.

Lars Kemmann
  • 5,509
  • 3
  • 35
  • 67
0

KV has a sign operation, but it's not specific to any data type (such as a cert request).

The KV .NET SDK offers this https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.keyvault.keyvaultclient.signwithhttpmessagesasync?view=azure-dotnet-legacy which is just a wrapper over REST APIs https://learn.microsoft.com/en-us/rest/api/keyvault/

This isn't exactly what you want, but I wrote some code to demonstrate C# KV encrypt/decrypt which should help you get started https://github.com/x509cert/AzureKeyVault

  • Thanks! unfortunately this is a very simple KV code and I'm looking for an x509 signing specific code, which will take care of signing, building the signed cert etc... in ECDSA. Thanks anyway! – NOP-MOV Jun 28 '20 at 11:07
  • I did try but this is far from what I asked! Thanks anyway. – NOP-MOV Jul 19 '20 at 08:41