NOP-MOV
NOP-MOV

Reputation: 890

How should I sign a CSR using a signature created in HSM, in C# .NET Core?

I'm exhausted after looking for an answer for 3 days. I don't know if my suggested flow is wrong or my Google skills have really deteriorated.

My API needs to create a valid certificate from a CSR it received, by signing it with a private key that exists ONLY inside an HSM-like service (Azure KeyVault), which unfortunately doesn't offer Certificate Authority functions BUT does offer signing data with a key that exists there. My CA certificate's private key is stored in the HSM. I'm using ECDSA.

My suggested flow:

  1. Client generates Key Pair + CSR and sends CSR to API
  2. API creates a certificate from the CSR
  3. API asks HSM to sign the CSR data and receives back a signature
  4. API appends the signature to the certificate and returns a signed (and including CA in chain) certificate to the Client

Flow

I'm using C# .NET Core and would like to keep it cross-platform (as it runs in Linux containers), so I have to keep it as native as possible or using Bouncy Castle (which I'm still not sure if runs in Linux .NET Core).

I really appreciate your help!

Upvotes: 1

Views: 1487

Answers (1)

Anshul Vanawat
Anshul Vanawat

Reputation: 11

I had faced a similar issue and found a solution. You'll have to use the PKCS11Interop.X509Store library. The solution uses dotnet core native System.Security.Cryptography.X509Certificates.CertificateRequest::Create method for generating a certificate.

As per the docs:

Pkcs11Interop is managed library written in C# that brings the full power of PKCS#11 API to the .NET environment

Pkcs11Interop.X509Store is managed library built on top of Pkcs11Interop. It's main goal is to provide easy to use PKCS#11 based read-only X.509 certificate store that can be easily integrated with standard .NET ecosystem.

Till v0.3.0, implementation for issuing a certificate (i.e signing a CSR) is not available. With minor modifications in the PKCS11Interop library, I was able to sign the CSR.

Mentioned in Issue #30, the code is now added in the PKCS11Interop.X509Store library version 0.4.0.

The below code is taken from test cases for BasicEcdsaCertificateRequestTest. Test cases for RSA CertificateRequest are also there.

// Load PKCS#11 based store
using (var pkcs11Store = new Pkcs11X509Store(SoftHsm2Manager.LibraryPath, SoftHsm2Manager.PinProvider))
{
    // Find signing certificate (CA certificate)
    Pkcs11X509Certificate pkcs11CertOfCertificateAuthority = Helpers.GetCertificate(pkcs11Store, SoftHsm2Manager.Token1Label, SoftHsm2Manager.Token1TestUserEcdsaLabel);

    // Generate new key pair for end entity
    ECDsa ecKeyPairOfEndEntity = ECDsa.Create(ECCurve.NamedCurves.nistP256);

    // Define certificate request
    CertificateRequest certificateRequest = new CertificateRequest(
        new X500DistinguishedName("C=SK,L=Bratislava,CN=BasicEcdsaCertificateRequestTest"),
        ecKeyPairOfEndEntity,
        HashAlgorithmName.SHA256);

    // Define certificate extensions
    certificateRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
    certificateRequest.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(certificateRequest.PublicKey, false));
    certificateRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false));

    // Issue X.509 certificate for end entity
    X509Certificate2 certificateOfEndEntity = certificateRequest.Create(
        pkcs11CertOfCertificateAuthority.Info.ParsedCertificate.SubjectName,
        X509SignatureGenerator.CreateForECDsa(pkcs11CertOfCertificateAuthority.GetECDsaPrivateKey()),
        DateTimeOffset.UtcNow,
        DateTimeOffset.UtcNow.AddDays(365),
        new BigInteger(1).ToByteArray());

    // Verify signature on X.509 certificate for end entity
    Assert.IsTrue(CaCertSignedEndEntityCert(pkcs11CertOfCertificateAuthority.Info.ParsedCertificate.RawData, certificateOfEndEntity.RawData));

    // Asociate end entity certificate with its private key
    certificateOfEndEntity = certificateOfEndEntity.CopyWithPrivateKey(ecKeyPairOfEndEntity);

    // Export end entity certificate to PKCS#12 file
    string basePath = Helpers.GetBasePath();
    string pkcs12FilePath = Path.Combine(basePath, "BasicEcdsaCertificateRequestTest.p12");
    File.WriteAllBytes(pkcs12FilePath, certificateOfEndEntity.Export(X509ContentType.Pkcs12, "password"));
}

Hope this helps.

Upvotes: 1

Related Questions