I was working on an Azure Function that needed to sign documents with a certificate. I stored the certificate and private key in Azure Key Vault, but when I tried to use it, I got a CryptographicException: Keyset does not exist
.
This error is frustrating because the key is definitely there. The issue is that Azure Functions run in a sandboxed environment that blocks access to the default user profile store where private keys are normally loaded.
After some troubleshooting, I found the solution has two parts: preventing key corruption during storage, and using the right flags when loading the certificate in the restricted environment.
Step 1: Stop Corrupting Your Keys in Key Vault
First things first: stop copying and pasting keys directly into the Azure portal. The UI can sometimes mess with whitespace and corrupt your key, which is a surprisingly common issue.
Base64 encode the files first to avoid this issue. Use PowerShell to convert them:
# For the public certificate (.cer)
[Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\path\to\your\certificate.cer")) | Set-Clipboard
# For the private key (.key)
[Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\path\to\your\private.key")) | Set-Clipboard
Now, paste the resulting single-line strings into your Key Vault secrets. No more corrupted keys.
Step 2: Load the Certificate with the Right Flags
Once you fetch the Base64 strings from Key Vault, you need to load them with specific flags to work around the sandbox limitations.
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
public static class CertificateHelper
{
public static X509Certificate2 LoadCertificateFromKeyVaultSecrets(
string base64Certificate,
string base64PrivateKey)
{
// Decode the secrets from Base64
byte[] certBytes = Convert.FromBase64String(base64Certificate);
byte[] privateKeyBytes = Convert.FromBase64String(base64PrivateKey);
// Create the certificate object from the public part
var certificate = new X509Certificate2(certBytes);
// Load the private key
// This example assumes an RSA key. Adjust for other algorithms if needed.
using (var rsa = RSA.Create())
{
// Import the private key bytes.
// The method (e.g., ImportPkcs8PrivateKey, ImportRSAPrivateKey)
// depends on the format of your original key file.
rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
// Combine the certificate with the private key
var certificateWithKey = certificate.CopyWithPrivateKey(rsa);
// Export the combined certificate to a PFX byte array (in memory)
byte[] pfxBytes = certificateWithKey.Export(X509ContentType.Pfx);
// Create the final X509Certificate2 object from the PFX data
// with the required flags. This is the crucial step.
return new X509Certificate2(
pfxBytes,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
}
}
}
Why This Works
- Base64 Encoding: Keeps your key data intact, avoiding corruption from whitespace or line ending issues in the Azure portal.
- In-Memory PFX: We create a temporary PFX container in memory to bundle the certificate and key together without saving files.
X509KeyStorageFlags.MachineKeySet
: This is the key part. It tells the runtime to use the machine-level key store, which Function Apps can access, instead of the restricted user store.X509KeyStorageFlags.Exportable
: Allows the key to be exported later if needed, preventing other potential issues.
That's it - the "Keyset does not exist" error should be gone.