Search code examples
c#jwtsignazure-keyvaultecdsa

Sign Custom created JWT format token using SignAsync method of azure and verify offline


 var byteData = Encoding.Unicode.GetBytes( encodedSerializedHeader + "." + encodedPayload);
            var hasher = new SHA256CryptoServiceProvider();
            var digest = hasher.ComputeHash(byteData);



           var  signature = await keyVaultClient.SignAsync("https://offline-token-api.vault.azure.net/keys/offlinetokenprime256v1cert/3e99b8ce5dkkk3d4fb8bc6a645e7c6aa5de",
                     SecurityAlgorithms.EcdsaSha256, digest);
var encodedSignature = Base64UrlEncoder.Encode(signature.Result);

Now to fetch the public key for verification i am using following:- 

 var secret = keyVaultClient.GetSecretAsync(vaultAddress, "offlinetokenprime256v1cert").GetAwaiter().GetResult();
  X509Certificate2Collection exportedCertCollection = new X509Certificate2Collection();
            exportedCertCollection.Import(Convert.FromBase64String(secret.Value));

            X509Certificate2 certFromSecret = exportedCertCollection.Cast<X509Certificate2>().Single(s => s.HasPrivateKey);
            var publickeybyte = certFromSecret.GetPublicKey();
            var publicekeybyte = certFromSecret.GetPublicKeyString();
            var privatek = certFromSecret.GetECDsaPrivateKey();
            var publicECDsa = LoadPublicKey(FromHexString(publicekeybyte));
            var pubpri = certFromSecret.GetECDsaPublicKey();

            var privateECDsa = LoadPrivateKey(FromHexString(privateKey));
            var publiccECDsa = LoadPublicKey(FromHexString(publicKey));


for offline verification -

var securityToken = new JwtSecurityToken(token);
                var securityTokenHandler = new JwtSecurityTokenHandler();
                IdentityModelEventSource.ShowPII = true;
              var validationParameters = new TokenValidationParameters()
                {
                    ValidIssuer = securityToken.Issuer,
                    ValidAudience = securityToken.Audiences.First(),
                    IssuerSigningKey = new ECDsaSecurityKey(eCDsa)
                };

                SecurityToken stoken;
                var claims = securityTokenHandler.ValidateToken(token, validationParameters, out stoken);
                return true;

But am getting an error , this is not working anyone having another solution for the same ? I want to sign my token without fetching the private key out from the vault and want to sign the information encded inside the jwt token. any way to sign the token using the signasync and verify offline?


Solution

  • Based on my knowledge, users cannot get private keys from Azure key vault key. Only the public key can be retrieved.

    Here is a sample I used to sign and verify:

        KeyVaultClient kvc = new KeyVaultClient(AuthenticationCallback);
    
        var key = kvc.GetKeyAsync(baseUrl, "ECDSA").GetAwaiter().GetResult();
        var parameters = key.Key.ToECDsa().ExportParameters(false);
    
        var pubKeyX = parameters.Q.X;
        var pubKeyY = parameters.Q.Y;
    
        // You can store X and Y, so that you may not need to get them from Azure Key Vault again
        // Use X and Y to create ECDsa instance
        var ecdsa = ECDsa.Create(new System.Security.Cryptography.ECParameters
        {
            Curve = ECCurve.NamedCurves.nistP256,
            Q = new ECPoint
            {
                X = pubKeyX,
                Y = pubKeyY
            }
        });
    
        // Generate JWT 
        var now = DateTime.UtcNow;
    
        var claims = new List<Claim>()
        {
            new Claim(JwtRegisteredClaimNames.Sub, "jack"),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Email, "[email protected]", ClaimValueTypes.String)
        };
    
        var jwt = new JwtSecurityToken(
            issuer: "aaa",
            audience: "bbb",
            claims: claims,
            notBefore: now,
            expires: now.AddHours(1)
        );
    
        var header = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(new Dictionary<string, string>()
        {
            { JwtHeaderParameterNames.Alg, "ES256" },
            { JwtHeaderParameterNames.Kid, key.KeyIdentifier.ToString() },
            { JwtHeaderParameterNames.Typ, "JWT" }
        }));
    
        // Sign
        var byteData = Encoding.UTF8.GetBytes($"{header}.{jwt.EncodedPayload}");
        var hasher = new SHA256CryptoServiceProvider();
        var digest = hasher.ComputeHash(byteData);
        var result = kvc.SignAsync(key.KeyIdentifier.ToString(), "ES256", digest).GetAwaiter().GetResult().Result;
    
        var signature = Base64UrlEncoder.Encode(result);
    
        // Verify
        var tokenHandler = new JwtSecurityTokenHandler();
        var claimsPrincipal = tokenHandler.ValidateToken($"{header}.{jwt.EncodedPayload}.{signature}", new TokenValidationParameters
        {
            ValidIssuer = "aaa",
            ValidAudience = "bbb",
            IssuerSigningKey = new ECDsaSecurityKey(ecdsa)
        }, out var parsedToken);