Vérifiez JWT avec RS256 (asymétrique) en C #

Aug 19 2020

J'ai un code comme celui-ci qui, je crois, échoue car il utilise un RS256 asymétrique mais a "SymmetricSecurityKey ()". Les jetons ont été générés à la main à partir dehttps://jwt.io/

  1. Comment puis-je convertir cela pour utiliser ma clé publique asymétrique?
  2. De plus, je suis nouveau en C # et j'aimerais cibler le standard dotnet, donc je me demande également si j'utilise les mauvaises bibliothèques? (Je dépend de la version préliminaire)
λ 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
}

Réponses

2 Topaco Aug 19 2020 at 13:26
  • Concernant votre première question:
    selon votre trace de pile publiée, vous semblez utiliser .NET Core 3.1. Cela vous permet d'importer facilement votre clé publique X.509 / SPKI comme suit:

    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 depuis .NET Core 3.0.

    Modifier le début: dans les versions antérieures de .NET Core (avant 3.0) ou dans .NET Framework ImportSubjectPublicKeyInfo()n'est pas disponible, donc au moins .NET Standard 2.1 est requis.

    Pour les versions antérieures, par exemple .NET Standard 2.0, une possibilité est d'utiliser BouncyCastle , plus précisément sa Org.BouncyCastle.OpenSsl.PemReaderclasse, qui permet l'importation de clés publiques au format X509 / SPKI (et, sans importance pour vous, également au format PKCS # 1). Dans cette réponse, vous trouverez un exemple d'utilisation PemReader. PemReadertraite, comme son nom l'indique, un codage PEM, c'est-à-dire la conversion en un codage DER (c'est-à-dire la suppression de l'en-tête, du pied de page et des sauts de ligne, ainsi que le décodage Base64 du reste) tel que requis par ImportSubjectPublicKeyInfo() ne doit pas être fait . Notez également PemReaderqu'attend au moins un saut de ligne immédiatement après l'en-tête ( -----BEGIN PUBLIC KEY-----\n) et un deuxième immédiatement avant le pied de page ( \n-----END PUBLIC KEY-----), les sauts de ligne dans le corps codé en Base64 après 64 caractères sont facultatifs pour PemReader.

    Une autre possibilité est le package opensslkey fournissant la méthode opensslkey.DecodeX509PublicKey(), qui peut traiter une clé X509 / SPKI en codage DER analogue à ImportSubjectPublicKeyInfo. Modifier la fin

  • Concernant votre 2ème question:
    Il existe plusieurs versions standard .NET , par exemple .NET Core 3.0 implémente .NET Standard 2.1. Le package System.IdentityModel.Tokens.Jwt 6.7.2-preview-10803222715 que vous utilisez nécessite .NET Standard 2.0.

    System.IdentityModel.Tokens.Jwtest un package qui prend en charge la création et la validation de jetons Web JSON (JWT). Dans le cas du jeton publié, la validation pourrait être mise en œuvre comme suit:

    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 validation peut être contrôlée via les paramètres de validation, c'est-à-dire via le 2ème paramètre de ValidateToken(). Puisque le jeton publié ne contient pas les revendications iss , aud et exp (cela peut être vérifié par exemple surhttps://jwt.io/), ils sont exclus de la validation dans mon exemple.

    Dans le didacticiel Créer et valider des jetons JWT dans ASP.NET Core, vous trouverez une explication plus détaillée, en particulier dans le chapitre Validation d'un jeton .

    ValidateToken()encapsule essentiellement le processus de vérification de la signature JWT. Un JWT est une structure de données qui se compose de trois parties: l'en-tête, la charge utile et la signature, les parties individuelles étant encodées en Base64url et séparées les unes des autres par un point.
    La signature est créée à l'aide de divers algorithmes, par exemple dans votre cas RS256 , ce qui signifie que les données (en-tête codé en Base64url et charge utile avec séparateur) sont signées en utilisant l'algorithme RSA avec PKCS # 1 v1.5 padding et digest SHA256.
    La vérification d'un token correspond à la vérification de la signature, qui peut également être effectuée uniquement avec des API cryptographiques (c'est-à-dire sans participation de System.IdentityModel.Tokens.Jwt ), comme cela se fait dans la réponse acceptée de la question liée dans le commentaire de @zaitsman.