簡體   English   中英

使用 Azure Key Vault 簽署 JWT 令牌

[英]Sign JWT token using Azure Key Vault

我正在使用私鑰簽署 JWT 令牌,它按預期工作。 但是,我想利用 Azure Key Vault 為我進行簽名,這樣私鑰就不會離開 KeyVault。 我正在努力讓它發揮作用,但不確定為什么。

這是使用 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 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);

這有效,如果我將 JWT 復制到jwt.io中,並粘貼公鑰 - 它表示簽名已驗證......

在此處輸入圖像描述

該令牌也適用於我正在調用的 API。

但是,如果使用 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)}";

(使用Azure.Security.KeyVault.KeysAzure.Identity nuget 包)

這是行不通的。 令牌的前兩部分 - 即。 header 和 payload 與有效的 JWT 相同。 唯一不同的是最后的簽名。

我沒主意了! 請注意,這與這個 Stackoverflow 問題密切相關,其中的答案似乎表明我正在做的事情應該是正確的。

在此處輸入圖像描述

您的代碼大部分是正確的,但您應該使用Encoding.UTF8Encoding.ASCII (因為 base64url 字符都是有效的 ASCII,並且您消除了任何 BOM 問題)來獲取headerAndPayload的字節。

我能夠讓它工作,發現https://jwt.io說你可以粘貼公鑰或證書時相當模糊。 它必須是 PEM 編碼的,如果發布 RSA 公鑰,則必須使用不太常見的“BEGIN RSA PUBLIC KEY”標簽,而不是更常見的“BEGIN PUBLIC KEY”標簽。

我嘗試了一些應該都有效的方法,當我發現使用來自 Key Vault 的證書對“BEGIN CERTIFICATE”有效時,我又回去嘗試“BEGIN PUBLIC KEY”。 直到心血來潮,改成“BEGIN RSA PUBLIC KEY”,JWT才驗證成功。

下面是我嘗試使用證書 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}");

對於僅使用密鑰,以下應該有效:

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}");

要生成令牌,您可以在SigningCredentials中創建自己的CryptoProviderFactory實現。

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實現:

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實現

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);
        }
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM