Verifique JWT com RS256 (assimétrico) em C #
Eu tenho um código como este que acredito estar falhando porque está usando um Asymmetric RS256, mas tem "SymmetricSecurityKey ()". Os tokens foram gerados manualmente a partir dehttps://jwt.io/
- Como faço para converter isso para usar minha chave pública assimétrica?
- Além disso, sou novo no C # e gostaria de direcionar o padrão dotnet, então também estou me perguntando se estou usando as libs erradas. (Estou dependendo da versão de visualização)
λ cat Program.cs
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
using System.Linq;
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
namespace jwttest
{
class Program
{
static void Main(string[] args)
{
string jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMguEIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iYv7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA";
var pubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB";
var rawKey = Encoding.ASCII.GetBytes(pubKey);
var tokenHandler = new JwtSecurityTokenHandler();
// var rsa = ?
tokenHandler.ValidateToken(jwt, new TokenValidationParameters {
IssuerSigningKey = new SymmetricSecurityKey(rawKey)
},
out SecurityToken validatedToken);
}
}
}
C:\src\jwttest (cgt-test-5 -> origin)
λ dotnet run
[2020-08-18T23:41:05.7108585-07:00 Info] raw=System.Byte[] [392]
Unhandled exception. Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10503: Signature validation failed. Keys tried: 'System.Text.StringBuilder'.
Exceptions caught:
'System.Text.StringBuilder'.
token: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'.
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at jwttest.Program.Main(String[] args) in C:\src\jwttest\Program.cs:line 22
λ cat jwttest.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<!-- Using preview release because it only depends on dotnet standard. Prior versions need framework. -->
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.7.2-preview-10803222715" />
</ItemGroup>
</Project>
λ cat jwt.json
{
"alg": "RS256",
"typ": "JWT"
}
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
Respostas
Com relação à sua primeira pergunta: de
acordo com o rastreamento de pilha postado, você parece estar usando o .NET Core 3.1. Isso permite que você importe facilmente sua chave pública X.509 / SPKI da seguinte maneira:var pubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB"; RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(pubKey), out _); // import the public X.509/SPKI DER encoded key
ImportSubjectPublicKeyInfo() está disponível desde o .NET Core 3.0.
Editar início: em versões anteriores do .NET Core (antes de 3.0) ou no .NET Framework, ImportSubjectPublicKeyInfo()não está disponível, portanto, pelo menos .NET Standard 2.1 é necessário.
Para versões anteriores, por exemplo .NET Standard 2.0, uma possibilidade é usar BouncyCastle , mais precisamente sua
Org.BouncyCastle.OpenSsl.PemReader
classe, que permite a importação de chaves públicas no formato X509 / SPKI (e, irrelevante para você, também no formato PKCS # 1). Em esta resposta você vai encontrar um exemplo de como usarPemReader
.PemReader
processa, como o nome sugere, uma codificação PEM, ou seja, a conversão para uma codificação DER (ou seja, a remoção do cabeçalho, rodapé e quebras de linha, bem como a decodificação Base64 do restante), conforme exigido por,ImportSubjectPublicKeyInfo()
não deve ser feita . Observe também quePemReader
espera pelo menos uma quebra de linha imediatamente após o cabeçalho (-----BEGIN PUBLIC KEY-----\n
) e uma segunda imediatamente antes do rodapé (\n-----END PUBLIC KEY-----
), as quebras de linha no corpo codificado em Base64 após cada 64 caracteres são opcionais paraPemReader
.Outra possibilidade é o pacote opensslkey fornecendo o método
opensslkey.DecodeX509PublicKey()
, que pode processar uma chave X509 / SPKI em codificação DER análoga aImportSubjectPublicKeyInfo
. Editar finalEm relação à sua 2ª pergunta:
Existem várias versões do padrão .NET , por exemplo, o .NET Core 3.0 implementa o .NET Standard 2.1. O pacote System.IdentityModel.Tokens.Jwt 6.7.2-preview-10803222715 que você está usando requer o .NET Standard 2.0.System.IdentityModel.Tokens.Jwt
é um pacote que suporta a criação e validação de JSON Web Tokens (JWT). No caso do token postado, a validação pode ser implementada da seguinte forma:string jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMguEIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iYv7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA"; var tokenHandler = new JwtSecurityTokenHandler(); bool verified = false; try { tokenHandler.ValidateToken(jwt, new TokenValidationParameters { ValidateAudience = false, ValidateLifetime = false, ValidateIssuer = false, IssuerSigningKey = new RsaSecurityKey(rsa) }, out _); verified = true; } catch { verified = false; } Console.WriteLine("Verified: " + verified);
A validação pode ser controlada através dos parâmetros de validação, ou seja, através do 2º parâmetro de ValidateToken(). Uma vez que o token postado não contém as declarações iss , aud e exp (isso pode ser verificado, por exemplo, emhttps://jwt.io/), eles são excluídos da validação no meu exemplo.
No tutorial Criando e validando tokens JWT no ASP.NET Core, você encontrará uma explicação mais detalhada, especialmente no capítulo Validando um token .
ValidateToken()
essencialmente encapsula o processo de verificação da assinatura JWT. Um JWT é uma estrutura de dados que consiste em três partes: cabeçalho, carga útil e assinatura, as partes individuais sendo codificadas em Base64url e separadas umas das outras por um ponto.
A assinatura é criada usando vários algoritmos, por exemplo, no seu caso RS256 , o que significa que os dados (cabeçalho codificado em Base64url e carga útil incluindo separador) são assinados usando o algoritmo RSA com preenchimento PKCS # 1 v1.5 e resumo SHA256.
A verificação de um token corresponde à verificação da assinatura, que também pode ser feita exclusivamente com APIs criptográficas (ou seja, sem a participação de System.IdentityModel.Tokens.Jwt ), pois é feita na resposta aceita da questão vinculada no comentário de @zaitsman.