Get X509 Certificate WITH PRIVATE KEY from Azure Keyvault c# - c#

I am currently working on an authentication server developed in C #, this one is hosted on an azure function app, and I use a KeyVault where my secrets are stored. My problem is the following, in my keyvault, I store a certificate (certificate + private key)
and when I retrieve it in my code, but the private key is not returned. if I test the following method: HasPrivateKey the code returns false ...
but if i use the same .pfx in localy the code return me true ...
my code:
var client = new CertificateClient(vaultUri: new Uri("https://diiage2p1g3chest.vault.azure.net/"),credential: new DefaultAzureCredential());
KeyVaultCertificate kcertificate = client.GetCertificate("try");
var cert_content = kcertificate.Cer;
X509Certificate2 certificate = new X509Certificate2(cert_content, "password", X509KeyStorageFlags.EphemeralKeySet);
any idea where the problem comes from?

CertificateClient has a method that returns a certificate with private key, but it's not obvious that's what it does.
From CertificateClient.DownloadCertificate:
Because Cer contains only the public key, this method attempts to download the managed secret that contains the full certificate. If you do not have permissions to get the secret, RequestFailedException will be thrown with an appropriate error response. If you want an X509Certificate2 with only the public key, instantiate it passing only the Cer property. This operation requires the certificates/get and secrets/get permissions.
So just refactor your code to use DownloadCertificate to get a cert with the private key.
var client = new CertificateClient(new Uri("https://diiage2p1g3chest.vault.azure.net/"), new DefaultAzureCredential());
X509Certificate2 certificate = client.DownloadCertificate("try");

The simplest way to get the full bytes of a certificate with its private information from keyvault is this. Please note you need permission to get secrets in your client id.
You need the following packages:
Azure.Identity
Azure.Security.KeyVault.Secrets
The following are deprecated:
Microsoft.IdentityModel.Clients.ActiveDirectory
Microsoft.Azure.KeyVault
Code:
using System;
using System.Threading.Tasks;
using Azure;
using Azure.Identity;
using System.Security.Cryptography.X509Certificates;
using Azure.Security.KeyVault.Secrets;
...
public async Task<X509Certificate2> GetCertificate(string certificateName,string clientId, string clientSecret, string keyVaultAddress, string tenantId)
{
ClientSecretCredential clientCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
var secretClient = new SecretClient(new Uri(keyVaultAddress), clientCredential);
var response = await secretClient.GetSecretAsync(certificateName);
var keyVaultSecret = response?.Value;
if(keyVaultSecret != null)
{
var privateKeyBytes = Convert.FromBase64String(keyVaultSecret.Value);
return new X509Certificate2(privateKeyBytes);
}
return null;
}

var _keyVaultName = $"VAULTURL";
var secretName = "CERTIFICATENAME";
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var _client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
var secret = _client.GetSecretAsync(_keyVaultName, secretName);
var privateKeyBytes = Convert.FromBase64String(secret.Result.Value);
certificate = new X509Certificate2(privateKeyBytes, string.Empty);
i solve my probleme like that :)

X509Certificate2 certificate;
ClientSecretCredential clientCredential = new ClientSecretCredential(TenantId,ClientId,ClientSecret);
var secretClient = new SecretClient(new Uri(KeyVaultUrl), clientCredential);
var response = await secretClient.GetSecretAsync(individualEnrollment.DeviceId.Replace("-", ""));
var keyVaultSecret = response?.Value;
var privateKeyBytes = Convert.FromBase64String(keyVaultSecret.Value);
certificate = new X509Certificate2(privateKeyBytes);
using (var security = new SecurityProviderX509Certificate(certificate))
using (var transport = new ProvisioningTransportHandlerAmqp(TransportFallbackType.TcpOnly))
{
ProvisioningDeviceClient provClient =
ProvisioningDeviceClient.Create(GlobalDeviceEndpoint, Scope, security, transport);
var deviceClient = await ProvisionX509Device(provClient, security);
}

Related

How to pass .crt and .key file with httpclient .Net Core Console Application

I am working on connection with hashicorp. We need to call there decrypt api in .net. for calling decrypt API, we need to pass token in it.
But token call is different which is using client certificate and key for authentication. We are calling token generation url from .net application but getting error ""{"errors":["client certificate must be supplied"]}\n"".
var allKeyytes = File.ReadAllBytes(#"file.key");
var privateKey = new X509Certificate2(allKeyytes, "XXXXXX").PrivateKey as DSACryptoServiceProvider;
var certificate2 = new X509Certificate2(#"file.crt");
certificate2.CopyWithPrivateKey(privateKey);
HttpClientHandler handler = new HttpClientHandler();
handler.ClientCertificates.Add(certificate2);
using (HttpClient client = new HttpClient(handler))
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, vaultUrl);
HttpResponseMessage response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsStringAsync().Result;
}
}
After adding above line of code getting error "System.Security.Cryptography.CryptographicException: 'Cannot find the requested object."
Please let me know what I am doing wrong!
Thank you in advance.
You cannot create a X509Certificate2 with just a private key. You need to read it in with DSA.ImportFromEncryptedPem.
You are not actually using the result of CopyWithPrivateKey which returns a new certificate, it does not modify the original.
You are also missing using on various objects, and you also need to use await rather than .Result otherwise you may deadlock
var allKeyytes = File.ReadAllText(#"file.key");
using (var crt = new X509Certificate2(#"file.crt"))
using (var var dsa = DSA.Create())
{
dsa.ImportFromEncryptedPem(allKeyytes.AsSpan(), "XXXXXX".AsSpan())
using (var certificate2 = crt.CopyWithPrivateKey(dsa))
{
HttpClientHandler handler = new HttpClientHandler();
handler.ClientCertificates.Add(certificate2);
using (var client = new HttpClient(handler))
using (var request = new HttpRequestMessage(HttpMethod.Post, vaultUrl))
{
using (var response = client.SendAsync(request))
{
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
}
}
}
}
}
If the private key is actually RSA then you will need to cast to RSA.Create() etc instead.
Ideally the HttpClient would be cached in a static . For that you would only dispose crt and privateKey not certificate2.
Code is not right. On line 2, you are reading privatekey in first parameter, it should have been encrypted certificate containing private key. Also, this method is obsolete.
I did the below code and it runs successfully. If you are using DSA, you can replace RSA with DSA
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
int bytesread;
var allKeyytes = File.ReadAllBytes(#"tls.key");
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.ImportFromPem(File.ReadAllText("tls.key"));
var certificate2 = new X509Certificate2(#"tls.cer");
certificate2.CopyWithPrivateKey(provider);
HttpClientHandler handler = new HttpClientHandler();
handler.ClientCertificates.Add(certificate2);
using (HttpClient client = new HttpClient(handler))
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://www.google.com");
HttpResponseMessage response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsStringAsync().Result;
}
}
Thank you all for helping. I was able to solve the issue.
How it Solved:
1: Created Pem file using .crt and .key file.
2: Exported the certificate using pfx type and passphrase and got byte array.
3: Created new certificate using byte array of pfx file and Add int HttpClientHandler.
4: Passed clientHandler object in HttpClient.
using (X509Certificate2 certWithKey = X509Certificate2.CreateFromPemFile(certificateionPath, key))
{
byte[] pfxRawData = certWithKey.Export(X509ContentType.Pfx, "123456");
using (X509Certificate2 pfxCertWithKey = new X509Certificate2(pfxRawData, "123456"))
{
HttpClientHandler handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback = (a, b, c, d) => { return true; };
handler.ClientCertificates.Add(pfxCertWithKey);
using (var client = new HttpClient(handler))
using (var request = new HttpRequestMessage(HttpMethod.Post, vaultUrl))
{
client.DefaultRequestHeaders.Add("X-Vault-Namespace", vaultNamespace);
var response = client.SendAsync(request).GetAwaiter().GetResult();
var result = response.Content.ReadAsStringAsync();
}
}
}
Very useful link found: .NET Standard - Merge a certificate and a private key into a .pfx file programmatically
This link helped me a lot!.
Thank you everyone.

Sign JWT token using Azure Key Vault

I'm using a private key to sign a JWT token, which works as expected. However, I'd like to leverage Azure Key Vault to do the signing for me, so that the private key doesn't leave KeyVault. I'm struggling to get this to work, but not sure why.
Here's the code that doesn't use KeyVault and does work...
var handler = new JwtSecurityTokenHandler();
var expiryTime = DateTimeOffset.UtcNow.AddMinutes(10).ToUnixTimeSeconds();
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Iss, clientId),
new Claim(JwtRegisteredClaimNames.Sub, integrationUser),
new Claim(JwtRegisteredClaimNames.Aud, "https://test.example.com"),
new Claim(JwtRegisteredClaimNames.Exp, expiryTime.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) // Add JTI for additional security against replay attacks
};
var privateKey = File.ReadAllText(#"selfsigned.key")
.Replace("-----BEGIN PRIVATE KEY-----", "")
.Replace("-----END PRIVATE KEY-----", "");
var privateKeyRaw = Convert.FromBase64String(privateKey);
var provider = new RSACryptoServiceProvider();
provider.ImportPkcs8PrivateKey(new ReadOnlySpan<byte>(privateKeyRaw), out _);
var rsaSecurityKey = new RsaSecurityKey(provider);
var token = new JwtSecurityToken
(
new JwtHeader(new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha256)),
new JwtPayload(claims)
);
var token = handler.WriteToken(token);
This works, and if I copy the JWT into jwt.io, and also paste the public key - it says that the signature is verified...
The token also works against the API I'm calling too.
However, if signing with KeyVault...
var handler = new JwtSecurityTokenHandler();
var expiryTime = DateTimeOffset.UtcNow.AddMinutes(10).ToUnixTimeSeconds();
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Iss, clientId),
new Claim(JwtRegisteredClaimNames.Sub, integrationUser),
new Claim(JwtRegisteredClaimNames.Aud, "https://test.example.com"),
new Claim(JwtRegisteredClaimNames.Exp, expiryTime.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) // Add JTI for additional security against replay attacks
};
var header = #"{""alg"":""RS256"",""typ"":""JWT""}";
var payload = JsonConvert.SerializeObject(new JwtPayload(claims));
var headerAndPayload = $"{Base64UrlEncoder.Encode(header)}.{Base64UrlEncoder.Encode(payload)}";
// Sign token
var credential = new InteractiveBrowserCredential();
var client = new KeyClient(vaultUri: new Uri(kvUri), credential);
var key = (KeyVaultKey)client.GetKey("dan-test");
var cryptoClient = new CryptographyClient(keyId: key.Id, credential);
var digest = new SHA256CryptoServiceProvider().ComputeHash(Encoding.Unicode.GetBytes(headerAndPayload));
var signature = await cryptoClient.SignAsync(SignatureAlgorithm.RS256, digest);
var token = $"{headerAndPayload}.{Base64UrlEncoder.Encode(signature.Signature)}";
(uses Azure.Security.KeyVault.Keys and Azure.Identity nuget packages)
This doesn't work. The first two parts of the token - ie. header and payload are identical to the JWT that does work. The only thing that's different is the signature at the end.
I'm out of ideas! Note that this is closely related to this Stackoverflow question, where the answers seem to suggest what I'm doing should be correct.
Your code is mostly correct, though you should use either Encoding.UTF8 or Encoding.ASCII (since the base64url characters are all valid ASCII and you eliminate any BOM concerns) to get the bytes for headerAndPayload.
I was able to get this to work and found that https://jwt.io is rather vague when it says you can paste either a public key or certificate. It has to be PEM-encoded, and if posting an RSA public key you have to use the less-common "BEGIN RSA PUBLIC KEY" label instead of the more-common "BEGIN PUBLIC KEY".
I tried a few things that all should've worked, and when I found that using a certificate from Key Vault did with "BEGIN CERTIFICATE", I went back to trying "BEGIN PUBLIC KEY". It wasn't until, on a whim, when I changed it to "BEGIN RSA PUBLIC KEY" the JWT was successfully verified.
Below is the code I tried using certificate URI:
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using Azure.Identity;
using Azure.Security.KeyVault.Certificates;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;
using Microsoft.IdentityModel.Tokens;
var arg = args.Length > 0 ? args[0] : throw new Exception("Key Vault key URI required");
var uri = new Uri(arg, UriKind.Absolute);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Iss, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Aud, "https://test.example.com"),
new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.Now.AddMinutes(10).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var header = #"{""alg"":""RS256"",""typ"":""JWT""}";
var payload = JsonSerializer.Serialize(new JwtPayload(claims));
var headerAndPayload = $"{Base64UrlEncoder.Encode(header)}.{Base64UrlEncoder.Encode(payload)}";
var id = new KeyVaultKeyIdentifier(uri);
var credential = new DefaultAzureCredential();
var certClient = new CertificateClient(id.VaultUri, credential);
KeyVaultCertificate cert = await certClient.GetCertificateAsync(id.Name);
using X509Certificate2 pfx = await certClient.DownloadCertificateAsync(id.Name, id.Version);
var pem = PemEncoding.Write("CERTIFICATE".AsSpan(), pfx.RawData);
Console.WriteLine($"Certificate (PEM):\n");
Console.WriteLine(pem);
Console.WriteLine();
using var rsaKey = pfx.GetRSAPublicKey();
var pubkey = rsaKey.ExportRSAPublicKey();
pem = PemEncoding.Write("RSA PUBLIC KEY".AsSpan(), pubkey.AsSpan());
Console.WriteLine($"Public key (PEM):\n");
Console.WriteLine(pem);
Console.WriteLine();
var cryptoClient = new CryptographyClient(cert.KeyId, credential);
using var sha256 = SHA256.Create();
var digest = sha256.ComputeHash(Encoding.ASCII.GetBytes(headerAndPayload));
var signature = (await cryptoClient.SignAsync(SignatureAlgorithm.RS256, digest)).Signature;
var token = $"{headerAndPayload}.{Base64UrlEncoder.Encode(signature)}";
Console.WriteLine($"JWT:\n\n{token}");
For using only a key, the following should work:
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Azure.Identity;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;
using Microsoft.IdentityModel.Tokens;
var arg = args.Length > 0 ? args[0] : throw new Exception("Key Vault key URI required");
var uri = new Uri(arg, UriKind.Absolute);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Iss, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Aud, "https://test.example.com"),
new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.Now.AddMinutes(10).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var header = #"{""alg"":""RS256"",""typ"":""JWT""}";
var payload = JsonSerializer.Serialize(new JwtPayload(claims));
var headerAndPayload = $"{Base64UrlEncoder.Encode(header)}.{Base64UrlEncoder.Encode(payload)}";
var id = new KeyVaultKeyIdentifier(uri);
var credential = new DefaultAzureCredential();
var keyClient = new KeyClient(id.VaultUri, credential);
KeyVaultKey key = await keyClient.GetKeyAsync(id.Name, id.Version);
using var rsaKey = key.Key.ToRSA();
var pubkey = rsaKey.ExportRSAPublicKey();
var pem = PemEncoding.Write("RSA PUBLIC KEY".AsSpan(), pubkey.AsSpan());
Console.WriteLine($"Public key (PEM):\n");
Console.WriteLine(pem);
Console.WriteLine();
var cryptoClient = new CryptographyClient(key.Id, credential);
using var sha256 = SHA256.Create();
var digest = sha256.ComputeHash(Encoding.ASCII.GetBytes(headerAndPayload));
var signature = (await cryptoClient.SignAsync(SignatureAlgorithm.RS256, digest)).Signature;
var token = $"{headerAndPayload}.{Base64UrlEncoder.Encode(signature)}";
Console.WriteLine($"JWT:\n\n{token}");
To generate token you can created your own implementation of CryptoProviderFactory in SigningCredentials.
var credentials = new SigningCredentials(new RsaSecurityKey(RSA.Create()), algorithm: SecurityAlgorithms.RsaSha256);
credentials.CryptoProviderFactory = _cryptoProviderFactory;
var descriptor = new SecurityTokenDescriptor
{
Subject = _owinContext.Request.User.Identity as ClaimsIdentity,
Expires = DateTime.UtcNow.AddHours(4),
SigningCredentials = credentials,
Audience = _configuration.AccessTokenAudience,
Issuer = _configuration.AccessTokenIssuer,
IssuedAt = DateTime.UtcNow,
};
var token = tokenHandler.CreateToken(descriptor);
SignatureProviderFactory implementation:
public class CustomCryptoProviderFactory : CryptoProviderFactory
{
private readonly CryptographyClient _cryptoClient;
public CustomCryptoProviderFactory()
{
var client = new KeyClient(new Uri("{url}"), new DefaultAzureCredential());
var key = client.GetKey("{key-name}");
_cryptoClient = new CryptographyClient(new Uri(key.Value.Key.Id), new DefaultAzureCredential());
}
public override SignatureProvider CreateForSigning(SecurityKey key, string algorithm)
{
return new CustomSignatureProvider(_cryptoClient, key, algorithm);
}
public override SignatureProvider CreateForSigning(SecurityKey key, string algorithm, bool cacheProvider)
{
return new CustomSignatureProvider(_cryptoClient, key, algorithm);
}
public override SignatureProvider CreateForVerifying(SecurityKey key, string algorithm)
{
return new CustomSignatureProvider(_cryptoClient, key, algorithm;
}
public override SignatureProvider CreateForVerifying(SecurityKey key, string algorithm, bool cacheProvider)
{
return new CustomSignatureProvider(_cryptoClient, key, algorithm);
}
}
CustomSignatureProvider implementation
public class CustomSignatureProvider : SignatureProvider
{
private readonly CryptographyClient _cryptoClient;
public CustomSignatureProvider(CryptographyClient cryptoClient,
SecurityKey key,
string algorithm)
: base(key, algorithm)
{
_cryptoClient = cryptoClient;
}
public override byte[] Sign(byte[] input)
{
var result = _cryptoClient.Sign(SignatureAlgorithm.RS256, GetSHA256(input));
return result.Signature;
}
public override bool Verify(byte[] input, byte[] signature)
{
var verificationResult = _cryptoClient.Verify(SignatureAlgorithm.RS256,
GetSHA256(input),
signature);
return verificationResult.IsValid;
}
protected override void Dispose(bool disposing)
{
}
private byte[] GetSHA256(byte[] input)
{
var sha = SHA256.Create();
return sha.ComputeHash(input);
}
}

How to generate JWT token signed with RS256 algorithm in C#

I have a RSA Private key with me and I have to generate a JWT token using RS256 algorithm.
I started with the below code which was working for "HmacSha256" algorithm
but when i change it to RS256 it throws errors like " IDX10634: Unable to create the SignatureProvider.Algorithm: 'System.String',SecurityKey:'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey'"
After that I modified the code with RSACryptoServiceProvider() class. But i didnt get a solution.
Please anyone can help with a sample code using RSACryptoServiceProvider class with a private key.
public static string CreateToken()//Dictionary<string, object> payload
{
string key = GetConfiguration["privateKey"].Tostring();
var securityKey =
new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(
Encoding.UTF8.GetBytes(key));
var credentials =
new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, "RS256");
var header = new JwtHeader(credentials);
JwtPayload payload = new JwtPayload();
payload.AddClaim(
new System.Security.Claims.Claim(
"context", "{'user': { 'name': 'username', 'email': 'email' }}",
JsonClaimValueTypes.Json));
payload.AddClaim(new System.Security.Claims.Claim("iss", #"app-key"));
payload.AddClaim(new System.Security.Claims.Claim("aud", "meet.jitsi.com"));
payload.AddClaim(new System.Security.Claims.Claim("sub", "meet.jitsi.com"));
payload.AddClaim(new System.Security.Claims.Claim("room", "TestRoom"));
var secToken = new JwtSecurityToken(header, payload);
var handler = new JwtSecurityTokenHandler();
var tokenString = handler.WriteToken(secToken);
return tokenString;
}

Azure Function APP [Expose functions key]

I'm trying to create an Authentication function for user login, but my idea is to expose the "function keys" of the rest of the functions. So the mobile app can grave the keys to star calling the rest of the functions.
Is a way to do this?
If you want to manage Azure function key, you can use the Key management API to implement it. For more details, please refer to document
Get function key
GET https://<functionappname>.azurewebsites.net/admin/functions/{functionname}/keys
Create Function key
PUT https://<functionappname>.azurewebsites.net/admin/functions/{functionname}/keys/{keyname}
{
"name": "keyname",
"value" : "keyvalue"
}
The code
tring clientId = "client id";
string secret = "secret key";
string tenant = "tenant id";
var functionName ="functionName";
var webFunctionAppName = "functionApp name";
string resourceGroup = "resource group name";
var credentials = new AzureCredentials(new ServicePrincipalLoginInformation { ClientId = clientId, ClientSecret = secret}, tenant, AzureEnvironment.AzureGlobalCloud);
var azure = Azure
.Configure()
.Authenticate(credentials)
.WithDefaultSubscription();
var webFunctionApp = azure.AppServices.FunctionApps.GetByResourceGroup(resourceGroup, webFunctionAppName);
var ftpUsername = webFunctionApp.GetPublishingProfile().FtpUsername;
var username = ftpUsername.Split('\\').ToList()[1];
var password = webFunctionApp.GetPublishingProfile().FtpPassword;
var base64Auth = Convert.ToBase64String(Encoding.Default.GetBytes($"{username}:{password}"));
var apiUrl = new Uri($"https://{webFunctionAppName}.scm.azurewebsites.net/api");
var siteUrl = new Uri($"https://{webFunctionAppName}.azurewebsites.net");
string JWT;
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Basic {base64Auth}");
var result = client.GetAsync($"{apiUrl}/functions/admin/token").Result;
JWT = result.Content.ReadAsStringAsync().Result.Trim('"'); //get JWT for call funtion key
}
// get key
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + JWT);
var key = await client.GetAsync($"{siteUrl}/admin/functions/{functionName}/keys").Result.Content.ReadAsStringAsync();
}
// create key
var map = new Dictionary<string, string>();
map.Add("name", "keyName");
map.Add("value", "keyVaule");
using (var client = new HttpClient()) {
var content = new StringContent(JsonConvert.SerializeObject(map), System.Text.Encoding.UTF8, "application/json");
await client.PutAsync($"{siteUrl}/admin/functions/{functionname}/keys/{keyname}", content);
}
Besides, according to my research, we also can use Azure REST API to manage Azure function key. For more details, please refer to
a. Create Azure function key
b. List Azure function key

MimeKit: 'RSACng' is currently not supported

I'm trying to create S/MIME email with MimeKit in the .NET Core App. It's working fine with local certificate like this:
var signer = new CmsSigner("cert.pfx", "password");
message.Body = MultipartSigned.Create(ctx, signer, message.Body);
To make app more secure, I've uploaded my certificate to the Azure KeyVault. And here's my problems has begun.
The idea is to use only certificate from Azure KeyVault without any passwords etc (so only store reference to the KeyVault instead of storing two links (KeyVault cert + KeyVault certPass)).
Here's how I'm trying to get certificate from the Azure KeyVault.
private static async Task<X509Certificate2> GetCertificate()
{
var tokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback));
var azureKeyVaultCert = await keyVaultClient.GetSecretAsync("https://<myapp>.vault.azure.net/secrets/<secretName>/<secretId>");
var certBytes = Convert.FromBase64String(azureKeyVaultCert.Value);
var cert = new X509Certificate2(certBytes);
return cert;
}
And then:
var cert = new GetCertificate();
var signer = new CmsSigner(cert);
Here I got error like this: MimeKit: 'RSACng' is currently not supported.
After that I tried another approach. It's working fine, however, I need to use my password. I don't think this is good idea (despite the fact I can also use Azure KeyVault for cert password).
private static async Task<byte[]> GetCertificateBytes()
{
var tokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback));
var azureKeyVaultCert = await keyVaultClient.GetSecretAsync("https://<myapp>.vault.azure.net/secrets/<secretName>/<secretId>");
var certBytes = Convert.FromBase64String(azureKeyVaultCert.Value);
var certCollection = new X509Certificate2Collection();
certCollection.Import(certBytes, null, X509KeyStorageFlags.Exportable);
return certCollection.Export(X509ContentType.Pkcs12, "password");
}
var certBytes = await GetCertificateBytes();
using(var certStream = new System.IO.MemoryStream(certBytes))
{
var signer = new CmsSigner(certStream, "password");
}
In case when I use await keyVaultClient.GetCertificateAsync(), I got an error that there's no private key.
Also, there's difference between certificates created via new X509Certificate2() and X509Certificate2Collection.Export().
Maybe I miss something?

Categories