15

I have a bunch of strings and pfx certificates, which I want to store in Azure Key vault, where only allowed users/apps will be able to get them. It is not hard to do store a string as a Secret, but how can I serialize a certificate in such way that I could retrieve it and deserialize as an X509Certificate2 object in C#?

I tried to store it as a key. Here is the Azure powershell code

$securepfxpwd = ConvertTo-SecureString -String 'superSecurePassword' -AsPlainText -Force
$key = Add-AzureKeyVaultKey -VaultName 'UltraVault' -Name 'MyCertificate' -KeyFilePath 'D:\Certificates\BlaBla.pfx' -KeyFilePassword $securepfxpwd

But when I tried to get it with GetKeyAsync method, I couldn't use it.

McGuireV10
  • 9,572
  • 5
  • 48
  • 64
zdebyman
  • 550
  • 1
  • 4
  • 22

5 Answers5

14

Here's a PowerShell script for you. Replace the file path, password, vault name, secret name.

$pfxFilePath = 'C:\mycert.pfx'
$pwd = '123'
$flag = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
$collection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection 
$collection.Import($pfxFilePath, $pwd, $flag)
$pkcs12ContentType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12
$clearBytes = $collection.Export($pkcs12ContentType)
$fileContentEncoded = [System.Convert]::ToBase64String($clearBytes)
$secret = ConvertTo-SecureString -String $fileContentEncoded -AsPlainText –Force
$secretContentType = 'application/x-pkcs12'
Set-AzureKeyVaultSecret -VaultName 'myVaultName' -Name 'mySecretName' -SecretValue $Secret -ContentType $secretContentType

This is a common question, so we are going to polish this up and release as a helper.

The script above strips the password because there's no value in having a password protected PFX and then storing the password next to it.

Sumedh Barde
  • 310
  • 2
  • 3
  • 1
    The $clearBytes = $collection.Export($pkcs12ContentType) doesn't appear to export the private key. Using the $collection.Export($pkcs12ContentType, $pwd) method allowed me to import the certificate as a X509Certificate2 (c#) using the password. – Trey Mar 23 '16 at 17:56
  • 1
    @Sumedh The helper you are referring to is that the new Key Vault certificates feature? https://learn.microsoft.com/en-us/rest/api/keyvault/certificate-scenarios Thank you – H Boyce Nov 30 '16 at 22:53
  • 2
    @HBoyce yes indeed. Here is a tutorial for the certificates feature. Much more concise now than the script above, and your certificates stay auto-renewed too. https://blogs.technet.microsoft.com/kv/2016/09/26/get-started-with-azure-key-vault-certificates/ (Thank you for resurrecting this thread.) – Sumedh Barde Dec 04 '16 at 09:00
  • Ok, so it comes back as a 'Secret' and not as a 'Certification' (to get the private value and make it useful to me). So, I see a string of hex digits.. Passing that to the constructor of a X509Certificate2 throws an error with path too long... So what do I do with it to get it back into a X509Cert? – Traderhut Games Jun 21 '17 at 21:17
  • var sec2 = _keyVaultClient.GetSecretAsync(CertSecretPath)).Result; X509Certificate2 cert2 = new X509Certificate2(Encoding.ASCII.GetBytes(sec2.Value)); Was attempted, but failed. Not sure what to do with the 'value' I get back – Traderhut Games Jun 21 '17 at 21:30
  • The GetCertificateAsync() works to give me a cert, but doesn't have the obvious parameter to the request (includePrivateKey = false) to make it useful :-( – Traderhut Games Jun 21 '17 at 21:32
  • Azure-CLI version for this?? – fletchsod Aug 30 '17 at 18:00
  • 1
    Downvote, there isn't much point in showing somebody how to store something without also demonstrating retrieval. Also the question was about C# and retrieval as an `x509Certificate2` object which the preceding comments show is not straightforward. – McGuireV10 Jan 10 '18 at 15:35
  • 2
    If people coming to this are as confused as I wa. it turns out you need to upload the .pfx to the `secrets` sections of Keyvault and not the `keys` section – ElFik Feb 02 '18 at 06:35
9

The original question asked how to retrieve the stored PFX as an X509Certificate2 object. Using a Base64 process similar to that posted by Sumedh Barde above (which has the advantage of stripping the password), the following code will return a X509 object. In a real application, the KeyVaultClient should be cached if you're retrieving multiple secrets, and the individual secrets should also be cached.

public static async Task<X509Certificate2> GetSecretCertificateAsync(string secretName)
{
    string baseUri = @"https://xxxxxxxx.vault.azure.net/secrets/";

    var provider = new AzureServiceTokenProvider();
    var client =  new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(provider.KeyVaultTokenCallback));
    var secretBundle = await client.GetSecretAsync($"{baseUri}{secretName}").ConfigureAwait(false);
    string pfx = secretBundle.Value;

    var bytes = Convert.FromBase64String(pfx);
    var coll = new X509Certificate2Collection();
    coll.Import(bytes, "certificatePassword", X509KeyStorageFlags.Exportable);
    return coll[0];
}
Sirar Salih
  • 2,514
  • 2
  • 19
  • 18
McGuireV10
  • 9,572
  • 5
  • 48
  • 64
  • 1
    This is exactly right. Remember that you need to save your key under the `secrets` section of your keyvault and not the `keys` section of you keyvault – ElFik Feb 02 '18 at 06:42
  • They really should have named the keys section something that better clarified it's meant for automated public key sharing and rotation, versus general purpose key storage... – McGuireV10 Feb 02 '18 at 10:47
  • Want to use this and similar solutions but with .Net Core 2.1, VS 2017 + Azure SDK getting: `'KeyVaultClient' does not contain a definition for 'GetSecretAsync'`. – ttugates Jul 31 '18 at 20:16
  • @ttugates I'm not where I can check the code but I think it's in an extensions package that you either need to reference or maybe you just need a `using` statement. – McGuireV10 Aug 01 '18 at 10:35
  • 1
    @McGuireV10, found the extension package referenced at [Azure Key Vault configuration provider in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-2.1&tabs=aspnetcore2x#package) – ttugates Aug 01 '18 at 13:59
  • 1
    Actually the code was incorrect, I fixed it now. You should use the ```client``` object to do the async call. – Sirar Salih Mar 08 '19 at 09:49
  • In my case the certificate was in coll[2]. Please be sure to see your thumbprint in the correct array position* – Gustavo Oct 30 '19 at 18:15
1

Here is how I solved this. First, convert your PFX file to a base64 string. You can do that with these two simple PowerShell commands:

$fileContentBytes = get-content 'certificate.pfx' -Encoding Byte
[System.Convert]::ToBase64String($fileContentBytes) | Out-File 'certificate_base64.txt'

Create a secret in Azure Key Vault via the Azure Portal. Copy the certificate base64 string that you created previously and paste it in the secret value field in your Azure Key Vault via the Azure Portal. Then simply call the Azure Key Vault from the code to get the base64 string value and convert that to a X509Certificate2:

private async Task<X509Certificate2> GetCertificateAsync()
{
    var azureServiceTokenProvider = new AzureServiceTokenProvider();
    var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
    var secret = await keyVaultClient.GetSecretAsync("https://path/to/key/vault").ConfigureAwait(false);
    var pfxBase64 = secret.Value;
    var bytes = Convert.FromBase64String(pfxBase64);
    var coll = new X509Certificate2Collection();
    coll.Import(bytes, "certificatePassword", X509KeyStorageFlags.Exportable);
    return coll[0];
}
Sirar Salih
  • 2,514
  • 2
  • 19
  • 18
1

See the following answer, which describes how to do this using the latest .NET Azure SDK client libraries:

How can I create an X509Certificate2 object from an Azure Key Vault KeyBundle

Christopher Scott
  • 2,676
  • 2
  • 26
  • 26
0

Here is the script for uploading pfx certificate in python using azure cli

azure keyvault secret set --vault-name <Valut name> --secret-name <Secret Name> --value <Content of PFX file>

Getting the content of PFX file in python

fh = open(self.getPfxFilePath(), 'rb')
    try:
        ba = bytearray(fh.read())
        cert_base64_str = base64.b64encode(ba)
        password = self.getPassword()
        json_blob = {
            'data': cert_base64_str,
            'dataType': 'pfx',
            'password': password
        }
        blob_data= json.dumps(json_blob)
        content_bytes= bytearray(blob_data)
        content = base64.b64encode(content_bytes)
        return content
    finally:
        fh.close
    fh.close()
Raghu K Nair
  • 3,854
  • 1
  • 28
  • 45