Verifica JWT con RS256 (asimmetrico) in C #

Aug 19 2020

Ho un codice come questo che credo stia fallendo perché utilizza un RS256 asimmetrico ma ha "SymmetricSecurityKey ()". I gettoni sono stati generati a mano dahttps://jwt.io/

  1. Come posso convertirlo per utilizzare la mia chiave pubblica asimmetrica?
  2. Inoltre, sono nuovo in C # e mi piacerebbe scegliere come target lo standard dotnet, quindi mi chiedo anche se sto usando le librerie sbagliate? (Dipendo dalla versione di anteprima)
λ 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
}

Risposte

2 Topaco Aug 19 2020 at 13:26
  • Per quanto riguarda la tua prima domanda: in
    base alla traccia dello stack pubblicata, sembra che tu stia utilizzando .NET Core 3.1. Ciò ti consente di importare facilmente la tua chiave X.509 / SPKI pubblica come segue:

    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() è disponibile da .NET Core 3.0.

    Inizio modifica : nelle versioni precedenti di .NET Core (prima della 3.0) o in .NET Framework ImportSubjectPublicKeyInfo()non è disponibile, quindi è richiesto almeno .NET Standard 2.1 .

    Per le versioni precedenti, es. .NET Standard 2.0, una possibilità è quella di utilizzare BouncyCastle , più precisamente la sua Org.BouncyCastle.OpenSsl.PemReaderclasse, che permette l'importazione di chiavi pubbliche in formato X509 / SPKI (e, per te irrilevante, anche in formato PKCS # 1). In questa risposta troverai un esempio di come utilizzare PemReader. PemReaderelabora, come suggerisce il nome, una codifica PEM, ovvero la conversione in una codifica DER (ovvero la rimozione di intestazione, piè di pagina e interruzioni di riga, nonché la decodifica Base64 del resto) come richiesto da ImportSubjectPublicKeyInfo() non deve essere eseguita . Nota anche che si PemReaderaspetta almeno un'interruzione di riga immediatamente dopo l'intestazione ( -----BEGIN PUBLIC KEY-----\n) e una seconda immediatamente prima del piè di pagina ( \n-----END PUBLIC KEY-----), le interruzioni di riga nel corpo codificato Base64 dopo ogni 64 caratteri sono facoltative per PemReader.

    Un'altra possibilità è il pacchetto opensslkey che fornisce il metodo opensslkey.DecodeX509PublicKey(), che può elaborare una chiave X509 / SPKI nella codifica DER analoga a ImportSubjectPublicKeyInfo. Fine modifica

  • Per quanto riguarda la seconda domanda:
    Esistono diverse versioni .NET standard , ad esempio .NET Core 3.0 implementa .NET Standard 2.1. Il pacchetto System.IdentityModel.Tokens.Jwt 6.7.2-preview-10803222715 che stai utilizzando richiede .NET Standard 2.0.

    System.IdentityModel.Tokens.Jwtè un pacchetto che supporta la creazione e la convalida di JSON Web Tokens (JWT). Nel caso del token pubblicato, la convalida potrebbe essere implementata come segue:

    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 convalida può essere controllata tramite i parametri di convalida, ovvero tramite il 2 ° parametro di ValidateToken(). Poiché il token pubblicato non contiene le attestazioni iss , aud ed exp (questo può essere verificato ad eshttps://jwt.io/), sono esclusi dalla convalida nel mio esempio.

    Nel tutorial Creazione e convalida di token JWT in ASP.NET Core troverai una spiegazione più dettagliata, soprattutto nel capitolo Convalida di un token .

    ValidateToken()essenzialmente incapsula il processo di verifica della firma JWT. Un JWT è una struttura dati composta da tre parti: intestazione, payload e firma, le singole parti sono codificate Base64url e separate l'una dall'altra da un punto.
    La firma viene creata utilizzando vari algoritmi, ad esempio nel tuo caso RS256 , il che significa che i dati (intestazione codificata Base64url e payload incluso il separatore) vengono firmati utilizzando l'algoritmo RSA con padding PKCS # 1 v1.5 e digest SHA256.
    La verifica di un token corrisponde alla verifica della firma, che può essere fatta anche esclusivamente con API crittografiche (ovvero senza partecipazione di System.IdentityModel.Tokens.Jwt ), come avviene nella risposta accettata della domanda collegata nel commento di @zaitsman.