简体   繁体   中英

Azure Key Vault - Download original PFX from Ket Vault

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.

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}");
}
}

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM