0

I have imported a certificate in "Certificates" section of Azure key vault

Created an user managed identity and added a role assignment of above mentioned key vault with contributor role.

Trying to download the certificate using below c# code,

    var kvUri = $"https://{KeyVaultName}.vault.azure.net";
string UserManagedIdentityClientId = ConfigKeys.ReadConfigValues("UserManagedIdentityClientId");
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions()
                {
                    ExcludeAzureCliCredential= true,
                    ExcludeAzurePowerShellCredential= true,
                    ExcludeEnvironmentCredential= true,
                    ExcludeInteractiveBrowserCredential= true,
                    ExcludeManagedIdentityCredential= false,
                    ExcludeSharedTokenCacheCredential= true,
                    ExcludeVisualStudioCodeCredential= true,
                    ExcludeVisualStudioCredential= true,
                    ManagedIdentityClientId = UserManagedIdentityClientId
                });
var client = new CertificateClient(new Uri(kvUri), credential);
var certificate = client.DownloadCertificate(CertName);

Getting below errors,

  1. In hosted environment,

    "The system cannot find the file specified"

  2. In local environment,

Exception occurred

Azure.Identity.CredentialUnavailableException

 HResult=0x80131500

 Message=ManagedIdentityCredential authentication unavailable. Multiple attempts failed to obtain a token from the managed identity endpoint.

 Source=Azure.Identity

 StackTrace:

  at Azure.Identity.DefaultAzureCredential.<GetTokenFromSourcesAsync>d__14.MoveNext()

  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

  at Azure.Identity.DefaultAzureCredential.<GetTokenImplAsync>d__12.MoveNext()

  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

  at Azure.Identity.CredentialDiagnosticScope.FailWrapAndThrow(Exception ex, String additionalMessage)

  at Azure.Identity.DefaultAzureCredential.<GetTokenImplAsync>d__12.MoveNext()

  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

  at Azure.Identity.DefaultAzureCredential.GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)

  at Azure.Core.Pipeline.BearerTokenAuthenticationPolicy.AccessTokenCache.<GetHeaderValueFromCredentialAsync>d__9.MoveNext()

  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

  at Azure.Core.Pipeline.BearerTokenAuthenticationPolicy.AccessTokenCache.<GetHeaderValueAsync>d__6.MoveNext()

  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

  at Azure.Core.Pipeline.BearerTokenAuthenticationPolicy.AccessTokenCache.<GetHeaderValueAsync>d__6.MoveNext()

  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

  at Azure.Core.Pipeline.BearerTokenAuthenticationPolicy.AuthenticateAndAuthorizeRequest(HttpMessage message, TokenRequestContext context)

  at Azure.Security.KeyVault.ChallengeBasedAuthenticationPolicy.<AuthorizeRequestOnChallengeAsyncInternal>d__10.MoveNext()

  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

  at Azure.Security.KeyVault.ChallengeBasedAuthenticationPolicy.AuthorizeRequestOnChallenge(HttpMessage message)

  at Azure.Core.Pipeline.BearerTokenAuthenticationPolicy.<ProcessAsync>d__11.MoveNext()

  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

  at Azure.Core.Pipeline.BearerTokenAuthenticationPolicy.Process(HttpMessage message, ReadOnlyMemory`1 pipeline)

  at Azure.Core.Pipeline.HttpPipelinePolicy.ProcessNext(HttpMessage message, ReadOnlyMemory`1 pipeline)

  at Azure.Core.Pipeline.RedirectPolicy.<ProcessAsync>d__7.MoveNext()

  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

  at Azure.Core.Pipeline.RedirectPolicy.Process(HttpMessage message, ReadOnlyMemory`1 pipeline)

  at Azure.Core.Pipeline.HttpPipelinePolicy.ProcessNext(HttpMessage message, ReadOnlyMemory`1 pipeline)

  at Azure.Core.Pipeline.RetryPolicy.<ProcessAsync>d__5.MoveNext()

  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

  at Azure.Core.Pipeline.RetryPolicy.<ProcessAsync>d__5.MoveNext()

  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

  at Azure.Core.Pipeline.RetryPolicy.Process(HttpMessage message, ReadOnlyMemory`1 pipeline)

  at Azure.Core.Pipeline.HttpPipelinePolicy.ProcessNext(HttpMessage message, ReadOnlyMemory`1 pipeline)

  at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.Process(HttpMessage message, ReadOnlyMemory`1 pipeline)

  at Azure.Core.Pipeline.HttpPipelinePolicy.ProcessNext(HttpMessage message, ReadOnlyMemory`1 pipeline)

  at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.Process(HttpMessage message, ReadOnlyMemory`1 pipeline)

  at Azure.Core.Pipeline.HttpPipelinePolicy.ProcessNext(HttpMessage message, ReadOnlyMemory`1 pipeline)

  at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.Process(HttpMessage message, ReadOnlyMemory`1 pipeline)

  at Azure.Core.Pipeline.HttpPipeline.Send(HttpMessage message, CancellationToken cancellationToken)

  at Azure.Core.Pipeline.HttpPipeline.SendRequest(Request request, CancellationToken cancellationToken)

  at Azure.Security.KeyVault.KeyVaultPipeline.SendRequest(Request request, CancellationToken cancellationToken)

  at Azure.Security.KeyVault.KeyVaultPipeline.SendRequest[TResult](RequestMethod method, Func`1 resultFactory, CancellationToken cancellationToken, String[] path)

  at Azure.Security.KeyVault.Certificates.CertificateClient.DownloadCertificate(DownloadCertificateOptions options, CancellationToken cancellationToken)

  at Azure.Security.KeyVault.Certificates.CertificateClient.DownloadCertificate(String certificateName, String version, CancellationToken cancellationToken)

Inner Exception

Inner Exception 1:

   AggregateException: Retry failed after 4 tries. Retry settings can be adjusted in ClientOptions.Retry or by configuring a custom retry policy in ClientOptions.RetryPolicy.
  

   Inner Exception 2:

   RequestFailedException: Unable to connect to the remote server
   

   Inner Exception 3:

   WebException: Unable to connect to the remote server

  
   Inner Exception 4:

   SocketException: A socket operation was attempted to an unreachable network 

Using CertificateClient, I want to download a certificate imported in Azure key vault "Certificates" section.

Harshitha
  • 3,784
  • 2
  • 4
  • 9

2 Answers2

0

To load the certificate INCLUDING the private key, then you need to retrieve it as a secret, not a certificte. If you get it as a certificate, you only get it with its public key.

this is the code I use to get a complete certificate back 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: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}");
    }
}
Tore Nestenius
  • 16,431
  • 5
  • 30
  • 40
  • Thanks Tore !! But importing a certificate in secret has been deprecated isn't it? Also we are not going to use Client Secret any more. – Bharanidharan Apr 18 '23 at 06:40
  • I mean, you upload it as a certificate, but to retrieve it with the public key, then you need to get it using GetSecrets.. yes, its wierd, but otherwise you only get the public key part if you download it as a certificate. – Tore Nestenius Apr 18 '23 at 06:53
0

Using certificate options with KeyStorageFlags as "MachineKeySet" fixed this issue.

DownloadCertificateOptions certificateOptions = new DownloadCertificateOptions(CertName);
certificateOptions.KeyStorageFlags = X509KeyStorageFlags.MachineKeySet;
var certificate = client.DownloadCertificate(certificateOptions);