Verifique JWT con RS256 (asimétrico) en C #

Aug 19 2020

Tengo un código como este que creo que está fallando porque usa un RS256 asimétrico pero tiene "SymmetricSecurityKey ()". Los tokens fueron generados a mano a partir dehttps://jwt.io/

  1. ¿Cómo convierto esto para usar mi clave pública asimétrica?
  2. Además, soy nuevo en C # y me gustaría apuntar al estándar dotnet, por lo que también me pregunto si estoy usando las bibliotecas incorrectas. (Depende de la versión preliminar)
λ 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
}

Respuestas

2 Topaco Aug 19 2020 at 13:26
  • Con respecto a su primera pregunta: de
    acuerdo con su seguimiento de pila publicado, parece que está utilizando .NET Core 3.1. Esto le permite importar fácilmente su clave pública X.509 / SPKI de la siguiente manera:

    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á disponible desde .NET Core 3.0.

    Editar inicio: en versiones anteriores de .NET Core (antes de 3.0) o en .NET Framework ImportSubjectPublicKeyInfo()no está disponible, por lo que se requiere al menos .NET Standard 2.1 .

    Para versiones anteriores, por ejemplo .NET Standard 2.0, una posibilidad es usar BouncyCastle , más precisamente su Org.BouncyCastle.OpenSsl.PemReaderclase, que permite la importación de claves públicas en formato X509 / SPKI (y, irrelevante para usted, también en formato PKCS # 1). En esta respuesta encontrará un ejemplo de cómo utilizar PemReader. PemReaderprocesa, como su nombre indica, una codificación PEM, es decir, la conversión a una codificación DER (es decir, la eliminación de encabezado, pie de página y saltos de línea, así como la decodificación Base64 del resto) como lo requiere ImportSubjectPublicKeyInfo() no debe realizarse . También tenga en cuenta que PemReaderespera al menos un salto de línea inmediatamente después del encabezado ( -----BEGIN PUBLIC KEY-----\n) y un segundo inmediatamente antes del pie de página ( \n-----END PUBLIC KEY-----), los saltos de línea en el cuerpo codificado en Base64 después de cada 64 caracteres son opcionales para PemReader.

    Otra posibilidad es el paquete opensslkey que proporciona el método opensslkey.DecodeX509PublicKey(), que puede procesar una clave X509 / SPKI en codificación DER análoga a ImportSubjectPublicKeyInfo. Editar fin

  • Con respecto a su segunda pregunta:
    Hay varias versiones estándar de .NET , por ejemplo .NET Core 3.0 implementa .NET Standard 2.1. El paquete System.IdentityModel.Tokens.Jwt 6.7.2-preview-10803222715 que está utilizando requiere .NET Standard 2.0.

    System.IdentityModel.Tokens.Jwtes un paquete que admite la creación y validación de JSON Web Tokens (JWT). En el caso del token publicado, la validación se podría implementar de la siguiente manera:

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

    La validación se puede controlar mediante los parámetros de validación, es decir, mediante el segundo parámetro de ValidateToken(). Desde el token Divulgados no contiene las reivindicaciones ISS , aud y exp (esto puede ser verificado por ejemplo, enhttps://jwt.io/), están excluidos de la validación en mi ejemplo.

    En el tutorial Creación y validación de tokens JWT en ASP.NET Core encontrará una explicación más detallada, especialmente en el capítulo Validación de un token .

    ValidateToken()esencialmente encapsula el proceso de verificación de la firma JWT. Un JWT es una estructura de datos que consta de tres partes: encabezado, carga útil y firma, las partes individuales están codificadas en Base64url y separadas entre sí por un punto.
    La firma se crea usando varios algoritmos, por ejemplo, en su caso RS256 , lo que significa que los datos (encabezado codificado en Base64url y carga útil, incluido el separador) se firman usando el algoritmo RSA con relleno PKCS # 1 v1.5 y resumen SHA256.
    La verificación de un token corresponde a la verificación de la firma, que también se puede hacer únicamente con APIs criptográficas (es decir, sin participación de System.IdentityModel.Tokens.Jwt ), como se hace en la respuesta aceptada de la pregunta vinculada en el comentario de @zaitsman.