[英]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.Keys
和Azure.Identity
nuget 包)
這是行不通的。 令牌的前兩部分 - 即。 header 和 payload 與有效的 JWT 相同。 唯一不同的是最后的簽名。
我沒主意了! 請注意,這與這個 Stackoverflow 問題密切相關,其中的答案似乎表明我正在做的事情應該是正確的。
您的代碼大部分是正確的,但您應該使用Encoding.UTF8
或Encoding.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.