I am working on an Azure Function that needs to send a certificate to an API that will authenticate the request and return the data accordingly.
I am successfully getting the certificate from Azure Key Vault as a X509Certificate2. This is making a call (will be making several) to my API I am getting a 403 response.
I have followed this configuration https://learn.microsoft.com/en-us/aspnet/core/security/authentication/certauth?view=aspnetcore-6.0 and my code currently looks like this
-- Program.cs
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<KestrelServerOptions>(options =>
{
options.ConfigureHttpsDefaults(options =>
options.ClientCertificateMode = ClientCertificateMode.AllowCertificate);
});
builder.Services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var validationService = context.HttpContext.RequestServices
.GetRequiredService<ICertificateValidationService>();
if (validationService.ValidateCertificate(context.ClientCertificate).Result)
{
var claims = new[]
{
new Claim(
ClaimTypes.NameIdentifier,
context.ClientCertificate.Subject,
ClaimValueTypes.String, context.Options.ClaimsIssuer),
new Claim(
ClaimTypes.Name,
context.ClientCertificate.Subject,
ClaimValueTypes.String, context.Options.ClaimsIssuer)
};
context.Principal = new ClaimsPrincipal(
new ClaimsIdentity(claims, context.Scheme.Name));
context.Success();
}
return Task.CompletedTask;
},
OnAuthenticationFailed = failedContext =>
{
failedContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.CompletedTask;
}
};
});
-- Validation Service
public class CertificateValidationService : ICertificateValidationService
{
private readonly IKeyVaultCertificateService _keyVaultService;
private readonly KeyVaultConfiguration _keyVauyltConfiguration;
public CertificateValidationService(IKeyVaultCertificateService keyVaultService, IOptions<KeyVaultConfiguration> keyVauyltConfiguration)
{
_keyVaultService = keyVaultService;
_keyVauyltConfiguration = keyVauyltConfiguration.Value;
}
public async Task<bool> ValidateCertificate(X509Certificate2 clientCertificate)
{
X509Certificate2 expectedCertificate = await _keyVaultService.GetX509CertificateAsync(_keyVauyltConfiguration.CertificateName);
return clientCertificate.Thumbprint == expectedCertificate.Thumbprint;
}
}
-- Controller
[Authorize(AuthenticationSchemes = CertificateAuthenticationDefaults.AuthenticationScheme)]
public IActionResult GetCount()
{
/// removed
}
When the request is made I am not hitting the method ValidateCertificate and I am just getting a 403 response.
I am having the same when I make a request through Postman and send the certificate in the request there too.
I would be grateful if someone could help me as I am stuck without success right now
--- Edit 1
_client = new HttpClient();
if (_erpConfiguration.UserCertificateAuthentication)
{
X509Certificate2 certificate = _keyVaultCertificateService
.GetX509CertificateAsync(_keyVaultConfiguration.CertificateName).Result;
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(certificate);
_client = new HttpClient(handler);
}
var countHttpResponse = await _client.GetAsync(fullUri);
--- End Edit 1
--- Edit 2
I am getting the certificate from KeyVault like this
public async Task<KeyVaultCertificateWithPolicy> GetCertificateWithPolicyAsync(string certificateName)
{
Response<KeyVaultCertificateWithPolicy>? certificate = await _certificateClient.GetCertificateAsync(certificateName);
return certificate;
}
public async Task<X509Certificate2> GetX509CertificateAsync(string certificateName)
{
KeyVaultCertificateWithPolicy certificate = await GetCertificateWithPolicyAsync(certificateName);
var certContent = certificate.Cer;
return new X509Certificate2(certContent);
}
--- End Edit 2
--- Edit 3
public async Task<X509Certificate2> GetX509CertificateAsync(string certificateName)
{
KeyVaultCertificateWithPolicy certificate = await GetCertificateWithPolicyAsync(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 = await _secretClient.GetSecretAsync(secretName, secretVersion, CancellationToken.None);
// For PEM, you'll need to extract the base64-encoded message body.
if (!"application/x-pkcs12".Equals(secret.Properties.ContentType,
StringComparison.InvariantCultureIgnoreCase))
{
throw new VerificationException("Unable to validate certificate");
}
byte[] pfx = Convert.FromBase64String(secret.Value);
return new X509Certificate2(pfx);
}
--- End Edit 3