简体   繁体   中英

C#: Keyset does not exist in Azure Functions on consumption plan

Many of us faced "Keyset does not esist" exception when trying to work with X509Certificate2. The symptoms are:

  1. You load .pfx into X509Certificate2

  2. You use it in any operation requiring private key

  3. You get "Keyset does not exist" exception.

In code it looks like the following:

string CreateJWTToken(byte[] certificate, string psw, string serviceUserIss, string serviceUserSub, string serviceUserAud)
    {
        using (X509Certificate2 combinedCertificate = new X509Certificate2(certificate, psw,
              X509KeyStorageFlags.MachineKeySet
            | X509KeyStorageFlags.PersistKeySet 
            | X509KeyStorageFlags.Exportable))
        {
            var signingKey = new X509SecurityKey(combinedCertificate);
            var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256);
            var header = new JwtHeader(credentials);
            var payload = new JwtPayload
            {
               { "iss", serviceUserIss},
               { "sub", serviceUserSub},
               { "aud", serviceUserAud},
               { "exp", $"{unixTimestamp}"}
            };
            var secToken = new JwtSecurityToken(header, payload);
            var handler = new JwtSecurityTokenHandler();
            return handler.WriteToken(secToken); // here exception is thrown
        }
    }

It works fine locally, and even in local Azure Functions host, but for some reason it sometimes (like in 5% cases) throws "Keyset does not exist" when run in Azure Function (timer-triggered) on consumption plan.

The issue appeared to be in some nuances of how Azure Functions on consumption plan works with certificates.

I do not know details, but X509Certificate2 's PrivateKey is sometimes cleared by... By something. Is it aggressive garbage collector? Is it something related to auto-scaling, or to resources shared between different hosts? I do not know.

But issue seems to be resolved by avoiding usage of X509Certificate2, by using BouncyCastle's mechanism to load private key from PFX. See code snippet below.

Snippet below also uses Jose.JWT for creating JWT token.

    private static string CreateJWTTokenBountyCastle(byte[] certificate, string psw, string serviceUserIss, string serviceUserSub, string serviceUserAud)
    {
        string jwt;
        using (RSACryptoServiceProvider rsax = OpenCertificate(certificate, psw)) // open using BouncyCastle and avoid usage of X502Certificate2
        {
            Dictionary<string, object> payload = new Dictionary<string, object>(){
                { "iss", serviceUserIss },
               { "sub", serviceUserSub},
               { "aud", serviceUserAud},
            jwt = Jose.JWT.Encode(payload, rsax, Jose.JwsAlgorithm.RS256);
        }
        return jwt;
    }
    private static RSACryptoServiceProvider OpenCertificate(byte[] certB, string pwd)
    {
        MemoryStream ms = new MemoryStream(certB);

        Pkcs12Store st = new Pkcs12Store(ms, pwd.ToCharArray());

        var alias = st.Aliases.Cast<string>().FirstOrDefault(p => st.IsKeyEntry(p));
        AsymmetricKeyEntry keyEntry = st.GetKey(alias);

        var kkey = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)keyEntry.Key);
        RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
        rsa.ImportParameters(kkey);
        return rsa;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM