hiFI
hiFI

Reputation: 1961

Decryption with a Azure Key Vault Certificate returns errors

I have a self-signed 2048 bit Key which I created using OpenSSL. I have uploaded the Certificate I created from it to Azure Key Vault, intending to access it. I noticed that when called from my app (asp.net 3.1; From Local Environment) pointing to Azure Key Vault, it fails! However, if I used the same certificate pointing to my local drive it works.

Please note that I am only running from a Local Environment and yet to host it in the Cloud, In this case, Azure.

I am getting an Object reference not set to an instance of an object Error at decryptedResponseData = Encoding.UTF8.GetString( rsa.Decrypt(Convert.FromBase64String(s),RSAEncryptionPadding.Pkcs1));

Furthermore, I noticed during debug that if I am getting the Certificate from Azure Key Vault, The PrivateKey attribute returns null and HasPrivateKey is set to false at var rsa = (RSACng)cert.PrivateKey;

This code works if I am accessing the same certificate from my local drive:

private static X509Certificate2 cert = new X509Certificate2(@"C:\<Dummy Path>\My.pfx", ""); //No password
        

        public static string Decrypt(this string s) 
        {
            if (string.IsNullOrWhiteSpace(s)) return "";

            //var cert = GetCertificate();
            var decryptedResponseData = string.Empty;
            var rsa = (RSACng)cert.PrivateKey;
            var raw = cert.RawData;
            
            decryptedResponseData = Encoding.UTF8.GetString( rsa.Decrypt(Convert.FromBase64String(s),RSAEncryptionPadding.Pkcs1));
            
            return decryptedResponseData;
        }

but If I use the same Certificate uploaded to Key Vault I am getting an error:

public static string Decrypt(this string s) 
        {
            if (string.IsNullOrWhiteSpace(s)) return "";

            var cert = GetCertificate();
            var decryptedResponseData = string.Empty;
            var rsa = (RSACng)cert.PrivateKey; //returns null
            var raw = cert.RawData;
            
            decryptedResponseData = Encoding.UTF8.GetString( rsa.Decrypt(Convert.FromBase64String(s),RSAEncryptionPadding.Pkcs1));
            
            return decryptedResponseData;
        }



        private static X509Certificate2 GetCertificate()
        {
            var azureCredentialOptions = new DefaultAzureCredentialOptions();
#if DEBUG
            azureCredentialOptions.SharedTokenCacheUsername = "[email protected]";
#endif
            var client = new CertificateClient(vaultUri: new Uri("https://dummyKeyVault.vault.azure.net/"), 
                                                credential: new DefaultAzureCredential(azureCredentialOptions));
            var cert = client.GetCertificate("me-int");

            return new X509Certificate2(cert.Value.Cer);
        }

`

Am I doing something wrong here? Please help me.

Upvotes: 0

Views: 1504

Answers (2)

Tore Nestenius
Tore Nestenius

Reputation: 19901

To get the private key, you need to download it as a secret.

The code below shows how I download the certficate with the private key from Azure Key Vault.

    /// <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:Secret"] ?? "";

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

Upvotes: 1

Matt Small
Matt Small

Reputation: 2275

Certificates downloaded from Key Vault do not have the private key included by default. You can download the certificate as a secret and retrieve the entire thing.

See section 2 of this blog post for how to download the cert as a secret. You can convert to C#: https://azidentity.azurewebsites.net/post/2018/05/17/azure-key-vault-app-service-certificates-finding-downloading-and-converting

Upvotes: 1

Related Questions