skip to Main Content

I dont have a great understanding of Key Vault & certificates and struggling with an issue. I am making use of a PFX file to generate a JWT token to call an external webservice. Its working all right. Now I need to store the PFX in Key Vault and do the same.

I am uploading the cert using Az DevOps & Az Cli command

az keyvault certificate import --file $(filename.secureFilePath) --name pfx-cert-name --vault-name "keyvault-name" --password "password"

Now when I try to use the PFX in my .net core. I am using CertificateClient class & GetCertificateAsync methods to fetch the byte array of a PFX file.

var client = new CertificateClient(new Uri(kvUri), new DefaultAzureCredential());            var cert = await client.GetCertificateAsync(certName);
certInBytes = cert.Value.Cer;

The code fails. After doing online reading, I understand its because Get Certificate fetches the public details of the PFX file. Hence I started doing some reading online and doing import and download using Az Cli command on powershell.

I tried another technique to download original form of PFX using the below command:

az keyvault secret download --file inputCert.pfx --vault-name keyvault-name --encoding base64 --name pfx-cert-name

The command gives me another pfx but its still not the original form of PFX. When I try to use this cert to get JWT token, I get an error for invalid password.

I have two alternates, but I don’t want to use either as they are not clean solutions:

  1. Either store a byte array of PFX as a secret in key vault
  2. Store base 64 encoded version of byte array of pfx for extra security.

2

Answers


  1. Chosen as BEST ANSWER

    There are 2 ways to solve this problem. One with the help of DefaultCredentials class while the other solution being with the help of a SPN using class ClientSecretCredentials.

    I have written a detailed article on both the solution. Since the original problem was in regards to DefaultCredentials, I wrote about it first

    https://blog.devgenius.io/fetch-pfx-cert-from-key-vault-using-defaultcredentials-3795bd23d294?sk=be8a6fea080ff19056a0b90fc9532cd7

    https://blog.devgenius.io/fetch-pfx-cert-from-key-vault-using-clientsecretcredentials-c0e80b129b37?sk=63c93f776bde72f49ef12263838e8d82


  2. To get the certificate with its private key, then you need to download it as a secret, not as a certificate. Yes, it does sounds weird, by that is how you do it.

    This is the code I use to download a certificate with private key from AKV:

    /// <summary>
    /// Load a certificate (with private key) from Azure Key Vault
    ///
    /// Getting a certificate with private key is a bit of a pain, but the code below solves it.
    /// 
    /// Get the private key for Key Vault certificate
    /// https://github.com/heaths/azsdk-sample-getcert
    /// 
    /// See also these GitHub issues: 
    /// https://github.com/Azure/azure-sdk-for-net/issues/12742
    /// https://github.com/Azure/azure-sdk-for-net/issues/12083
    /// </summary>
    /// <param name="config"></param>
    /// <param name="certificateName"></param>
    /// <returns></returns>
    public static X509Certificate2 LoadCertificate(IConfiguration config, string certificateName)
    {
        string vaultUrl = config["Vault:Url"] ?? "";
        string clientId = config["Vault:ClientId"] ?? "";
        string tenantId = config["Vault:TenantId"] ?? "";
        string secret = config["Vault:ClientSecret"] ?? "";
    
        Console.WriteLine($"Loading certificate '{certificateName}' from Azure Key Vault");
    
        var credentials = new ClientSecretCredential(tenantId: tenantId, clientId: clientId, clientSecret: secret);
        var certClient = new CertificateClient(new Uri(vaultUrl), credentials);
        var secretClient = new SecretClient(new Uri(vaultUrl), credentials);
    
        var cert = GetCertificateAsync(certClient, secretClient, certificateName);
    
        Console.WriteLine("Certificate loaded");
        return cert;
    }
    
    
    /// <summary>
    /// Helper method to get a certificate
    /// 
    /// Source https://github.com/heaths/azsdk-sample-getcert/blob/master/Program.cs
    /// </summary>
    /// <param name="certificateClient"></param>
    /// <param name="secretClient"></param>
    /// <param name="certificateName"></param>
    /// <returns></returns>
    private static X509Certificate2 GetCertificateAsync(CertificateClient certificateClient,
                                                            SecretClient secretClient,
                                                            string certificateName)
    {
    
        KeyVaultCertificateWithPolicy certificate = certificateClient.GetCertificate(certificateName);
    
        // Return a certificate with only the public key if the private key is not exportable.
        if (certificate.Policy?.Exportable != true)
        {
            return new X509Certificate2(certificate.Cer);
        }
    
        // Parse the secret ID and version to retrieve the private key.
        string[] segments = certificate.SecretId.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
        if (segments.Length != 3)
        {
            throw new InvalidOperationException($"Number of segments is incorrect: {segments.Length}, URI: {certificate.SecretId}");
        }
    
        string secretName = segments[1];
        string secretVersion = segments[2];
    
        KeyVaultSecret secret = secretClient.GetSecret(secretName, secretVersion);
    
        // For PEM, you'll need to extract the base64-encoded message body.
        // .NET 5.0 preview introduces the System.Security.Cryptography.PemEncoding class to make this easier.
        if ("application/x-pkcs12".Equals(secret.Properties.ContentType, StringComparison.InvariantCultureIgnoreCase))
        {
            byte[] pfx = Convert.FromBase64String(secret.Value);
            return new X509Certificate2(pfx);
        }
    
        throw new NotSupportedException($"Only PKCS#12 is supported. Found Content-Type: {secret.Properties.ContentType}");
    }
    }
    

    The code above depends on these NuGet packages:

    • Azure.Security.KeyVault.Certificates
    • Azure.Security.KeyVault.Keys
    • Azure.Security.KeyVault.Secrets
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search