Code Monkey home page Code Monkey logo

jwt's Introduction

JSON Web Token for .Net

Provides support for JWT. This library aims to propose performant JWT primitives.

.NET Core CI/CD

Packages

Package Version Description
JsonWebToken NuGet Nuget package with JWT primitives. Use this package for common JWT usages.
JsonWebToken.OAuth2 NuGet Nuget package with OAuth2 & OIDC primitives. Use this package for more specifics usages like ID tokens, access tokens, state parameter, software statement, vector of trust or client assertion.
JsonWebToken.SecurityEventTokens NuGet Nuget package with specifics SecEvents primitives.
JsonWebToken.KeyVault NuGet Nuget package with Key Vault support. Use this package if your keys are stored into an Azure KeyVault.

Installation

Install the JsonWebToken NuGet Package.

Package Manager Console

Install-Package JsonWebToken -Version 1.9.3

For the latest beta version:

Install-Package JsonWebToken -Version 2.0.0-beta.4

.NET CLI

dotnet add package JsonWebToken

Usage

See the samples for more details.

The Jwt class is used for reading and validating tokens:

if(Jwt.TryParse("eyJhbGc[...]sWBedk", policy, out var jwt)
{
  // Use the JWT
  // ...
  // Then dispose the object
  jwt.Dispose();
}

The JwtWriter is used for writing tokens:

var writer = new JwtWriter();
var token = writer.WriteTokenString(descriptor);

JWT validation

var key = SymmetricJwk.FromBase64Url("R9MyWaEoyiMYViVWo8Fk4TUGWiSoaW6U1nOqXri8_XU");
var policy = new TokenValidationPolicyBuilder()
                .RequireIssuer("https://idp.example.com/", key, SignatureAlgorithm.HmacSha256)
                .RequireAudience("636C69656E745F6964")
                .Build();

if (Jwt.TryParse("eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MDAwMDcyMDAsImlhdCI6MjAwMDAwNzIwMCwiaXNzIjoiaHR0cHM6Ly9pZHAuZXhhbXBsZS5jb20vIiwiYXVkIjoiNjM2QzY5NjU2RTc0NUY2OTY0In0.YrrT1Ddp1ampsDd2GwYZoTz_bUnLt_h--f16wsWBedk", policy, out Jwt jwt))
{
    Console.WriteLine("The token is " + jwt);
}
else
{
    Console.WriteLine("Failed to read the token. Reason: " + Environment.NewLine + jwt.Error.Status);
}

// Do not forget to dispose the Jwt, or you may suffer of GC impacts
jwt.Dispose();

JWT creation

// Creates a symmetric key defined for the 'HS256' algorithm
var signingKey = SymmetricJwk.FromBase64Url("R9MyWaEoyiMYViVWo8Fk4TUGWiSoaW6U1nOqXri8_XU");

// Creates a JWS descriptor with all its properties
var descriptor = new JwsDescriptor(signingKey, SignatureAlgorithm.HmacSha256)
{
    Payload = new JwtPayload
    {
        { Claims.Iat, EpochTime.UtcNow },
        { Claims.Exp, EpochTime.UtcNow + EpochTime.OneHour },
        { Claims.Iss, "https://idp.example.com/" },
        { Claims.Aud, "636C69656E745F6964" }
    }
};
   

// Generates the UTF-8 string representation of the JWT
var writer = new JwtWriter();
var token = writer.WriteTokenString(descriptor);

Console.WriteLine("The JWT is:");
Console.WriteLine(descriptor);
Console.WriteLine();
Console.WriteLine("Its compact form is:");
Console.WriteLine(token);

Performances

See benchmarks for details. This library is about 12x faster than the Microsoft.IdentityModel.Tokens.Jwt when decoding and validating the token, with less than 5-10% memory allocation. (6x faster including signature validation or for encrypted tokens)

In case of invalid token, is is about 25x faster for detecting an invalid signature.

It is about 4x faster when writing a JWS of common size, with less than 1-2% memory allocation. (3x faster including signature generation, 5x faster for encrypted tokens)

The main reason of the efficiency of this library is the usage of the new API provided in .NET Core 2.0, 2.1 & 3.0, like the new Span API, the new JSON API, and the intrisics SIMD API.

Supported JWT

Supported algorithms

JWS signing algorithms

"alg" Param Value Digital Signature or MAC Algorithm Target Framework
HS256 HMAC using SHA-256 netstandard2.0
HS384 HMAC using SHA-384 netstandard2.0
HS512 HMAC using SHA-512 netstandard2.0
RS256 RSASSA-PKCS1-v1_5 using SHA-256 netstandard2.0
RS384 RSASSA-PKCS1-v1_5 using SHA-384 netstandard2.0
RS512 RSASSA-PKCS1-v1_5 using SHA-512 netstandard2.0
ES256 ECDSA using curve P-256 and SHA-256 netcoreapp2.1
ES384 ECDSA using curve P-384 and SHA-384 netcoreapp2.1
ES512 ECDSA using curve P-521 and SHA-512 netcoreapp2.1
ES256K ECDSA using curve secp256k1 and SHA-256 netcoreapp2.1 (not available on MacOS)
PS256 RSASSA-PSS using SHA-256 and MGF1 with SHA-256 netstandard2.0
PS384 RSASSA-PSS using SHA-384 and MGF1 with SHA-384 netstandard2.0
PS512 RSASSA-PSS using SHA-512 and MGF1 with SHA-512 netstandard2.0
none No digital signature or MAC performed netstandard2.0

JWE encryption algorithms

"enc" Param Value Content Encryption Algorithm Target Framework
A128CBC-HS256 AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm netstandard2.0
A192CBC-HS384 AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm netstandard2.0
A256CBC-HS512 AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm netstandard2.0
A128GCM AES GCM using 128-bit key netcoreapp3.0
A192GCM AES GCM using 192-bit key netcoreapp3.0
A256GCM AES GCM using 256-bit key netcoreapp3.0

JWE content encryption key algorithm

"alg" Param Value Key Management Algorithm Target Framework
RSA1_5 RSAES-PKCS1-v1_5 netstandard2.0
RSA-OAEP RSAES OAEP using default parameters netstandard2.0
RSA-OAEP-256 RSAES OAEP using SHA-256 and MGF1 with SHA-256 netstandard2.0
A128KW AES Key Wrap with default initial value using 128-bit key netstandard2.0
A192KW AES Key Wrap with default initial value using 192-bit key netstandard2.0
A256KW AES Key Wrap with default initial value using 256-bit key netstandard2.0
dir Direct use of a shared symmetric key as the CEK netstandard2.0
ECDH-ES Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF netcoreapp2.1
ECDH-ES+A128KW ECDH-ES using Concat KDF and CEK wrapped with "A128KW" netcoreapp2.1
ECDH-ES+A192KW ECDH-ES using Concat KDF and CEK wrapped with "A192KW" netcoreapp2.1
ECDH-ES+A256KW ECDH-ES using Concat KDF and CEK wrapped with "A256KW" netcoreapp2.1
A128GCMKW Key wrapping with AES GCM using 128-bit key netcoreapp3.0
A192GCMKW Key wrapping with AES GCM using 192-bit key netcoreapp3.0
A256GCMKW Key wrapping with AES GCM using 256-bit key netcoreapp3.0
PBES2-HS256+A128KW PBES2 with HMAC SHA-256 and "A128KW" wrapping netstandard2.0
PBES2-HS384+A192KW PBES2 with HMAC SHA-384 and "A192KW" wrapping netstandard2.0
PBES2-HS512+A256KW PBES2 with HMAC SHA-512 and "A256KW" wrapping netstandard2.0

jwt's People

Contributors

azure-pipelines[bot] avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar lochgeo avatar mortalflesh avatar olivier-spinelli avatar ycrumeyrolle avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jwt's Issues

JwsDescriptor.Payload setter weird behavior.

This unit test fails:

        [Fact]
        public void DescriptorPayloadIsAStandardReferenceObject()
        {
            var descriptor = new JwsDescriptor(Jwk.None, SignatureAlgorithm.None);

            var p1 = new JwtPayload { { "One", "Member" } };
            var p1Content = p1.ToString();
            descriptor.Payload = p1;

            var p2 = new JwtPayload { { "Something", "else" } };
            var p2Content = p2.ToString();            
            descriptor.Payload = p2;

            Assert.Equal(p1.ToString(),p1Content);
            Assert.Equal(p2.ToString(),p2Content);
        }

With this:

    Assert.Equal() Failure
                                     ↓ (pos 24)
    Expected: ··· "Something": "else",\r\n  "One": "Member"\r\n}
    Actual:   ··· "Something": "else"\r\n}
                                     ↑ (pos 24)

This is a rather surprising side effect. Considering the implementation (see below _payload.CopyTo(value);), I'm wondering if this is intentional or not...

public override JwtPayload? Payload
{
    get => _payload;
    set
    {
        if (value is null)
        {
            ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
        }

        _payload.CopyTo(value);
        _payload = value;
    }
}

If its not, I can make a PR to fix this (including the above test).

SymmetricJwk string key decoding fail

Hello,
I'm trying to create a SymmetricJwk with user password - so only him can verify the JWT again.

But I got an Exception.

System.FormatException: The input is not a valid Base-64 URL string as it contains a non-base 64 character.
   at JsonWebToken.ThrowHelper.ThrowOperationNotDoneException(OperationStatus status)
   at JsonWebToken.Base64Url.Decode(ReadOnlySpan`1 base64Url, Span`1 data)
   at JsonWebToken.Base64Url.Decode(ReadOnlySpan`1 base64Url)
   at JsonWebToken.Base64Url.Decode(String data)
   at JsonWebToken.SymmetricJwk..ctor(String k)

I found this https://stackoverflow.com/questions/44866847/convert-frombase64string-does-not-work-in-code-but-works-in-online-tool/44867049 so it explained me, why I got the error exactly.

But I think it might be a bug for creating a JWK, since when I create a token here https://jwt.io/#debugger-io with that password, it is created without any problem.

So I'm not sure, why there is _k = Base64Url.Decode(k); in the first place, could it be done by System.Text.Encoding.UTF8.GetBytes instead? I guess it would be more user friendly.

But maybe I see it all wrong (I'm kinda new to JWT world).

Adds a CLI

  • Generates JWK
  • Encrypt JWK
  • Decrypt JWK
  • Convert PEM & X509 to JWK
  • Check the validity of a JWK

Fix warning on CI build

##[warning]/opt/hostedtoolcache/dotnet/sdk/3.0.100/Roslyn/Microsoft.CSharp.Core.targets(59,5): Warning MSB3052: The parameter to the compiler is invalid, '/define:$(BUILDCONFIGURATION)' will be ignored.

Better support multiple issuers

It may be common to have a lots of issuers for secevent.
The current way to resolve this is to try to validate against each issuer policy.

A better way to achieve this is to lookup the issuer policy based on the 'iss' claim.
This require to review the TokenValidationPolicy usage.

Inconsistency in casing of "iat" claim between descriptor and header ToString methods

This is just a tiny inconsistency I noticed in some testing. It probably wouldn't affect anyone unless they're abusing the ToString methods of JwsDescriptor and Jwt.

The following code:

var descriptor = new JwsDescriptor
{
    Algorithm = SignatureAlgorithm.None,
    IssuedAt = DateTime.UtcNow,
    ExpirationTime = DateTime.UtcNow.AddHours(1),
    Issuer = "https://idp.example.com/",
    Audience = "636C69656E745F6964"
};

Console.WriteLine("Descriptor ToString():");
Console.WriteLine(descriptor.ToString());
Console.WriteLine();

var tokenBytes = new JwtWriter().WriteToken(descriptor);

Console.WriteLine("Jwt ToString():");
Console.WriteLine(new JwtReader().TryReadToken(tokenBytes, TokenValidationPolicy.NoValidation).Token?.ToString());

Produces the following output:

Descriptor ToString():
{
  "alg": "none"
}
.
{
  "iat": 1588358068,
  "exp": 1588361668,
  "iss": "https://idp.example.com/",
  "aud": "636C69656E745F6964"
}

Jwt ToString():
{
  "alg": "none"
}.{
  "aud": "636C69656E745F6964",
  "iss": "https://idp.example.com/",
  "exp": 1588361668,
  "Iat": 1588358068
}

Note that the descriptor ToString output correctly has the "iat" claim in lowercase but the Jwt ToString output has it with a capital "I" ("Iat").

This line seems to be the source of the problem.

Writer freezes while writing a token

Hello,

I'm trying to generate a token, but it freezes at writing... am I doing something wrong?

I'm using F# with .netcore 3.1 on Mac OS (10.15.4).

printfn "JWT.create ..."
use key = new SymmetricJwk("R9MyWaEoyiMYViVWo8Fk4TUGWiSoaW6U1nOqXri8_XU", SignatureAlgorithm.HmacSha256)
printfn " -> key ok"

let descriptor =
    JwsDescriptor(
        KeyId = key.ToString(),
        JwtId = Guid.NewGuid().ToString(),
        IssuedAt = (DateTime.UtcNow |> Nullable),
        ExpirationTime = (DateTime.UtcNow.AddMinutes(30.0) |> Nullable),
        Issuer = (currentApp |> SoftwareComponent.value),
        Audience = (currentApp |> SoftwareComponent.value)
    )
    |> tee (fun d -> d.ToString() |> printfn " -> descriptor ok \n %A")

printfn " -> writer"
let writer = JwtWriter()
printfn " -> writing token ..."
let token = writer.WriteTokenString(descriptor)
printfn " -> Token:\n%A" token

Output is:

JWT.create ...
 -> key ok
 -> descriptor ok 
 "{
  "kid": "{\n  \u0022kty\u0022: \u0022oct\u0022,\n  \u0022alg\u0022: \u0022HS256\u0022,\n  \u0022k\u0022: \u0022R9MyWaEoyiMYViVWo8Fk4TUGWiSoaW6U1nOqXri8_XU\u0022\n}"
}
.
{
  "jti": "2e9996df-b8ca-4e7a-a369-8ae1b8d843ff",
  "iat": 1588085668,
  "exp": 1588087468,
  "iss": "sc-eligibilityConfigurator-common-local@dev1-services",
  "aud": "sc-eligibilityConfigurator-common-local@dev1-services"
}"
 -> writer
 -> writing token ...

JWE generation using A128CBC_HS256 produces the wrong length authentication tag

RFC 7518 JSON Web Algothrims specify that the authentication tag should be truncated to the first 16 octets.

The AES_CBC_HMAC_SHA2 parameters specific to AES_128_CBC_HMAC_SHA_256 are:
The input key K is 32 octets long.
ENC_KEY_LEN is 16 octets.
MAC_KEY_LEN is 16 octets.
The SHA-256 hash algorithm is used for the HMAC.
The HMAC-SHA-256 output is truncated to T_LEN=16 octets, by
stripping off the final 16 octets.

However when JWE tokens are generated the full 32 octets generated by HMAC SHA256 are being appended. This is generating invalid tokens.

Some Errors on Validation BinaryPayloadJwt

`static void Main()
{
var signatureKey = SymmetricJwk.FromBase64Url("R9MyWaEoyiMYViVWo8Fk4TUGWiSoaW6U1nOqXri8_XU");
var encryptionKey = new SymmetricJwk("R9MyWaEoyiMYViVWo8Fk4T");
var payload = new byte[] { 76, 105, 102, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, 112, 114, 111, 115, 112, 101, 114, 46 };
var descriptor = new BinaryJweDescriptor(payload)
{
EncryptionKey = encryptionKey,
EncryptionAlgorithm = EncryptionAlgorithm.Aes128CbcHmacSha256,
Algorithm = KeyManagementAlgorithm.Aes128KW
};

        var writer = new JwtWriter();
        var token = writer.WriteTokenString(descriptor);

        Console.WriteLine("The JWT is:");
        Console.WriteLine(descriptor);
        Console.WriteLine("payload:"+Encoding.UTF8.GetString(payload));
        Console.WriteLine("Its compact form is:");
        Console.WriteLine(token);
                    
        var policy = new TokenValidationPolicyBuilder()
                       .RequireSignature(signatureKey, SignatureAlgorithm.HmacSha256)
                       .RequireAudience("636C69656E745F6964")//this is not exist in payload,why result still succed?
                       .RequireIssuer("https://idp.example.com/")//this is not exist in payload,why result still succed?
                       .Build();

        var reader = new JwtReader(encryptionKey);
        var result = reader.TryReadToken(token, policy);
        if (result.Succedeed)
        {
            Console.WriteLine("Decypt token is " + result.Token.Payload.ToString());//payload should be a byteArray,why here is null?
        }
        else
        {
            Console.WriteLine("Failed to read the token. Reason: " + Environment.NewLine + result.Status);
        }
    }`

some not reasonable.
If throw a byteArray or string Payload, that will be good.

Operation is not supported on this platform.

When the code hits JwtWriter.WriteTokenString() on a Windows Server 2012 R2, I get the following error but no other information.

Operation is not supported on this platform.

Stack trace doesn't seam to be much help,

The code is running fine on a number of other servers, all server have the "Microsoft .NET Core 5.00 Windows Server Hosting" installed all other functions appear to working fine.

Other Windows server including a 2012 R2 , 2016 and 2019 appear to be fine, any ideas how to narrow this problem down?

Encrypted key present in ECDH-ES algorithm results in Invalid JWE

Hello, should there be a encrypted key present when using this algorithm ?

According to my readings, this algorithm uses Direct Key Agreement, and when using this it seems that the JWE Encrypted key value should have been empty ? https://datatracker.ietf.org/doc/html/rfc7516#section-5.2 (10)

It's turning out to be a invalid JWE, since in the CEK part we are getting ".AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA." instead of the empty octet ?

Example:

string json = "{\r\n        \"alg\": \"ES256\",\r\n        \"crv\": \"P-256\",\r\n        \"kty\": \"EC\",\r\n        \"use\": \"sig\",\r\n        \"x\": \"OKs1T_4N9Z78RQ87olZ98PW__ROFWL5fw1671XB20zw\",\r\n        \"y\": \"8y5YBG5RY4gK2bObN4Aj5eNmXBoLMrCHKEMwykPSTIg\"\r\n      }";
string payload = "teste";

var descriptor = new PlaintextJweDescriptor(payload)
{
    EncryptionKey = Jwk.FromJson(json),
    EncryptionAlgorithm = EncryptionAlgorithm.Aes256Gcm,
    Algorithm = KeyManagementAlgorithm.EcdhEs
};

var writer = new JwtWriter();
string jwe = writer.WriteTokenString(descriptor);

Console.WriteLine(jwe);

eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiRUNESC1FUyIsImVwayI6eyJjcnYiOiJQLTI1NiIsIngiOiJNQnpzbF9VM2ZFemxLMzVvbWh4TjlOd05yRm11ZXlRRGR2WUVxd2hENWtnIiwieSI6Ilc1TnJvZGxvQXdTbHptUjZ3cVAwZjVxRHBEQ2NqM0Y0YXEteUNwS1p5a3cifX0.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.2JvH70MzlecMiZtG.Fy85KTI.dHuw2Cw7_im2TAP_YpKrfQ

Thanks in advance @ycrumeyrolle

How to Initialize AsymmetricJwk

Hi
I am working on .Net and interested to use JsonWebToken for Signing Purpose. i want to implement Using OAuth 2.0 for Server to Server Applications in google. The only signing algorithm supported by the Google OAuth 2.0 Authorization Server is RSA using SHA-256 hashing algorithm. I have the private key in my hand.i need to initialize AsymmetricJwk, i think. But the example only showing SymmetricJwk.Can you Please help me, how can i initialize AsymmetricJwk using Private Key.

Thanks

ECJwk GetCanonicalizeSize wrong - Kid randomly wrong

Hello,
in EC JWK the size is calculated by:

	protected internal override int GetCanonicalizeSize()
	{
		return 35 + Base64Url.GetArraySizeRequiredToEncode(Crv.Name.EncodedUtf8Bytes.Length) + Base64Url.GetArraySizeRequiredToEncode(X.Length) + Base64Url.GetArraySizeRequiredToEncode(Y.Length);
	}

but it should be

	protected internal override int GetCanonicalizeSize()
	{
		return 35 + Crv.Name.EncodedUtf8Bytes.Length + Base64Url.GetArraySizeRequiredToEncode(X.Length) + Base64Url.GetArraySizeRequiredToEncode(Y.Length);
	}

because Crv.Name.EncodedUtf8Bytes is copied in Canonicalize:

	protected internal override void Canonicalize(Span<byte> buffer)
	{
		int length = StartCanonicalizeValue.Length;
		StartCanonicalizeValue.CopyTo(buffer);
		EllipticalCurve ellipticalCurve = Crv;
		ellipticalCurve.Name.EncodedUtf8Bytes.CopyTo(buffer.Slice(length));
		int num = length;
		ellipticalCurve = Crv;
		length = num + ellipticalCurve.Name.EncodedUtf8Bytes.Length;
		Middle1CanonicalizeValue.CopyTo(buffer.Slice(length));
		length += Middle1CanonicalizeValue.Length;
		length += Base64Url.Encode(X, buffer.Slice(length));
		Middle2CanonicalizeValue.CopyTo(buffer.Slice(length));
		length += Middle2CanonicalizeValue.Length;
		length += Base64Url.Encode(Y, buffer.Slice(length));
		EndCanonicalizeValue.CopyTo(buffer.Slice(length));
	}

This is a problem because ComputeThumbprint uses this Value for the call to ComputeHash:

Sha256.Shared.ComputeHash(buffer.Slice(0, canonicalizeSize), span);

The buffer is either from ArrayPool or from Stackalloc - ArrayPool does not garantuee to null the array. So you get random Kids when the last bytes differ because they dont get initialized.

I think the Renting is also wrong - you use Stackalloc if canonicalSize is bigger than 256 - i think the condition should be reversed - using stack alloc for smaller arrays right?

I didnt check for RSA and the others, potentially they share some of those bugs.

I can do PRs if you like. Right now our project is running on your "2.0.0-beta.4" i would like to have a stable version :-)

If you dont do maintenance on this project i would be ready to fork this - your library is awesome and much better than the crap Microsoft implemented for their authorization. Want to see it fly :-)

What is the meaning of 'defaultAlgorithm'?

I'm having a hard time figuring out the "defaultAlgorithm" here:

TokenValidationPolicyBuilder RequireSignature(string issuer, Jwks keys, SignatureAlgorithm defaultAlgorithm)
Because the "alg" header parameter (https://tools.ietf.org/html/rfc7515#section-4.1.1):

This Header Parameter MUST be present and MUST be understood and processed by implementations.

... default here seems rather strange to me. Is it a way to REQUIRE a given algorithm? In this case, how does this fit with the multiple keys that could be associated to the "kid"?

(This defaultAlgorithm is not optional... just like the "alg" parameter.)

Sorry to ask but I'll appreciate some explanations about this... Thanks in advance.

Originally posted by @olivier-spinelli in #545 (comment)

This API is new in v2.0, still in beta, so there may be some remaining polish on the public API.
The excepted usage is:

            var policy = new TokenValidationPolicyBuilder()
                           .RequireSignature("https://idp.example.com/", key, SignatureAlgorithm.HS256)
                           .RequireAudience("636C69656E745F6964")
                           .Build();

By doing so, there is a link between the issuer, the key, and the signature algorithm. The issuer is used as a lookup for finding the required validation. This is the common usage of a JWS: You have an issuer, a key, and an associated signature algorithm.
So this is not a default algorithm.

It is also possible to do so (legacy usage):

            var policy = new TokenValidationPolicyBuilder()
                           .DefaultIssuer("https://idp.example.com/")
                           .RequireSignatureByDefault(key, SignatureAlgorithm.HS256)
                           .Build();

The default issuer is not used as lookup, but only as the last chance to validate an issuer, as-well as the signature key. This allow some use cases where there is no issuer.

Your question convinces me on 2 points:

  • A review of the name of the methods & parameters should be done. defaultAlgorithm is a perfect example of name without clear intention.
  • Some methods exposed for "legacy usage" are more disturbing than expected. Require***ByDefault, RequireAlgorithm. Marking this methods as obsolete might be an option.

JWE decryption using A128CBC_HS256 fails to decrypt valid tokens

JWE tokens generated using A128CBC_HS256 by other libraries fail to be decrypted correctly as the authentication fails. This seems to be due wrong length authentication tag being checked. It should only check the first 128 bits.

If you try to decrypt the example token given in RFC7516 the decryption fails.

RFC7518 specifies only the first 128 bits to be used in the tag

Example

using System;
using JsonWebToken;			

public class Program
{
	public static void Main()
	{
		// Key and token from https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.3
		
		Jwk encryptionKey = new SymmetricJwk("GawgguFyGrWKav7AX4VKUg");
		string token = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ.AxY8DCtDaGlsbGljb3RoZQ.KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.U0m_YmjN04DJvceFICbCVQ";
		
		JwtReader jwtReader = new JwtReader(encryptionKey);
		var policy = new TokenValidationPolicyBuilder().AcceptUnsecureToken().Build();
		
		var result = jwtReader.TryReadToken(token, policy);
		Console.WriteLine($"Decryption Statue: {result.Status}");
		
		// The token should be decrypted and equal "Live long and prosper." However it fails decryption
	}
}

https://dotnetfiddle.net/YdEJXG

Manual refresh for JwksHttpKeyProvider

We're currently wrapping JwksHttpKeyProvider in a custom key provider factory in order to support the following requirement (supports automatic key rotation for our services):

When a key is missing from the JWKS, the validation service shall attempt to reload the JWKS from the /.well-known/jwks.json endpoint. This shall be attempted at a rate of no more than once per minute.

I propose adding the following to JwksHttpKeyProvider.cs:

namespace JsonWebToken
{
    /// <summary>Represents a <see cref="IKeyProvider"/> that retrieve the key set from an HTTP resource as JWKS.</summary>
    public sealed class JwksHttpKeyProvider : IKeyProvider, IDisposable
    {
...

        // Possible implementation
        public void Refresh()
        {
            _refreshLock.Wait();
            try
            {
                _syncAfter = EpochTime.UtcNow + RefreshInterval;
            }
            finally
            {
                _refreshLock.Release();
            }
        }
    }
}

This would allow us to setup refreshing the JWKS if a key is missing.

Since this is a 2.0 release, perhaps this could be rolled up to IKeyProvider?

Help to create JWT token from JWK

Hello.
How can I use your library to create an JWT token with Elliptic Curve JSON Web Key?

private const string privkey =
"{"kty":"EC","alg":"ES256","crv":"P-256"," + ""x":"lmzBhWRXSZTx5q6b80PK_GL7b94YYXI1hNB3YdJ6bzQ","y":"sXwmQX8sAm5yoybyq0RvMMhQp7Ox4lvhdpy_xPjfs58"," +""d":"P_MepPu0IPJEIQ_WUL5p12qb-phfMBdiDC8vHgdwvJ4"}";

None of the methods does not work:
1.
var privJwk = new ECJwk(256, "P_MepPu0IPJEIQ_WUL5p12qb-phfMBdiDC8vHgdwvJ4","lmzBhWRXSZTx5q6b80PK_GL7b94YYXI1hNB3YdJ6bzQ","sXwmQX8sAm5yoybyq0RvMMhQp7Ox4lvhdpy_xPjfs58");
2.
var privJwk = ECJwk.FromJson(privkey);

Add a claim with JWTArray

Hello again,
I really tried to figure out this myself, but I couldn't.

I need to add a custom claim to payload:

{
  ...,
  "groups": [
     "first",
     "second",
     "third"
  ]
}

but so far I can only add it to look like this:

{
  ...,
  "groups": {
    "groups": [
      "first",
      "second",
      "third"
    ]
  }
}

This code leads to a "double nested" groups:

let array =
    ["first"; "second"; "third"]
    |> List.map (fun v -> JwtValue(v))
    |> System.Linq.Enumerable.ToList // convert to Collection.Generic.List
    |> JwtArray

let property = JwtProperty("groups", array)
descriptor.AddClaim("groups", property)

I'd like to just add an array as claim

descriptor.AddClaim("groups", array)

but there is no such option.

I can create a JwtObject and add an array there, but it also needs a key and it has List<JwtProperty> _properties inside. JwtProperty AFAIK needs to have both name and value.


But if I create a token elsewhere, I can read value exactly the way I want to:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0MTg1YzQ0NC03OWNkLTRmNmUtODYwOC01N2E0NTI1YWFjNWYiLCJleHAiOjE2MTg3NDkyOTYsImlhdCI6MTU4NzY0NTI5NiwiaXNzIjoic2MtZWxpZ2liaWxpdHlDb25maWd1cmF0b3IiLCJncm91cHMiOlsiZmlyc3QiLCJzZWNvbmQiLCJ0aGlyZCJdfQ.gKXcjZkXLOI8MlImxiTRC4TuYa6UBvqw_Gb2ltJdZ4c
match result.Token.Payload.TryGetValue("groups") with
| true, groups -> printfn "groups <%A>:\n%A" (groups.Value.GetType()) groups.Value
| _ -> ()

Output:

groups <JsonWebToken.JwtArray>:
seq [JsonWebToken.JwtValue; JsonWebToken.JwtValue; JsonWebToken.JwtValue]

I even tried to fork it and "fix" it myself to the PR, but I don't know how to create a nameless property or claim.

JWE with arbitrary bytes/string as payload

Hi Yann,

The RFC 7516, while describing the plaintext does not specify that the content needs to be a valid JWS. I am trying to create a JWE using a string as my content, but I get the error that type (string) is not a valid JwsDescriptor. Below is the code that I am using

       var keyEncryptionKey = Jwk.FromX509Certificate(new X509Certificate2(), false);

        // Creates a JWE descriptor with all its properties
        var descriptor = new JweDescriptor<string>()
        {
            EncryptionKey = keyEncryptionKey,
            EncryptionAlgorithm = EncryptionAlgorithm.Aes256Gcm,
            Algorithm = KeyManagementAlgorithm.RsaOaep,
            Payload = "data"
        };

        // Generates the UTF-8 string representation of the JWT
        var writer = new JwtWriter();
        var token = writer.WriteTokenString(descriptor);

Is it possible to produce a such a JWE using the library? Could you help me with a code sample, if its possible?

Read SignatureAlgorithm from X509Certificate if available

We're using X509 certs to sign and verify JWTs, and we have come across an issue where we have to explicitly state the signature algorithm in our policy because Jwk.Alg is null.

I believe this could instead be read from the X509 cert and added to the JWK in FromX509Certificate:

X509Certificate2Collection collection = store.Certificates
                                             .Find(
                                                 X509FindType.FindByThumbprint,
                                                 thumbprint ?? throw new ArgumentNullException(nameof(thumbprint)));
if (collection.Count == 0)
{
    // ...
}

try
{
    Jwk jwk = Jwk.FromX509Certificate(collection[0], withPrivateKey);
    // jwk.Alg == null
    // collection[0].SignatureAlgorithm.FriendlyName == "sha256RSA"
    return jwk;
}
catch (Exception ex)
{
     // ...
}

Basically supporting RFC 3279 and family (https://tools.ietf.org/html/rfc3279#section-2.2).

Signature validation fails when no signature algorithm is specified

I am attempting to validate an access token coming from Azure AD B2C, and the validation is failing even though jwt.io successfully validates the signature. This is version 1.9.0.

In SignatureValidationPolicy.cs, line 101, Jwk.CanUseForSignature() is called with "RS256" as signatureAlgorithm, and it returns true, because the SignatureAlgorithm property returns null. Then, on line 103, alg ends up null because both algorithm and key.SignatureAlgorithm are null. That means that the code never tries to validate the signature, and InvalidSignature is the result.

add custom claims with object value

i couldn't find a way to add custom claims to JwsDescriptor which accepts claim value as object, where it get automatically json serialized when jwt is written.

e.g.

descriptor.AddClaim("https://hasura.io/jwt/claims", new HasuraClaim
{
UserId = user.Id,
DefaultRole = "user",
Roles = new[] { "user" }
});

Token fails validation with status = NotYetValid

I have a token that was retrieved from Auth0, and issued a few minutes ago. When I validate the token with .EnableLifetimeValidation(), the validation fails, with Status being NotYetValid. The code that does the validation is as follows:

var policy = new TokenValidationPolicyBuilder()
	.RequireAudience("https://tv-dev-test/")
	.RequireSignature(m_keys)
	.RequireIssuer(m_domain)
	.EnableLifetimeValidation()
	.Build();

var reader = new JwtReader();
var header = AuthenticationHeaderValue.Parse(req.Headers[HeaderNames.Authorization]);
var result = reader.TryReadToken(header.Parameter, policy);

Console.WriteLine(result.Succedeed);
Console.WriteLine(result.Status);
Console.WriteLine(result.Exception?.ToString());
Console.WriteLine($"now is {DateTime.UtcNow} {DateTime.UtcNow.Kind}");
Console.WriteLine($"not before {result.Token.NotBefore} {result.Token.NotBefore?.Kind}");
Console.WriteLine($"issued at {result.Token.IssuedAt} {result.Token.IssuedAt?.Kind}");
Console.WriteLine($"expiration time {result.Token.ExpirationTime} {result.Token.ExpirationTime?.Kind}");

The output of this validation is as follows:

False
NotYetValid

now is 9/17/2020 9:49:05 PM Utc
not before
issued at 9/17/2020 9:09:54 PM Utc
expiration time 9/18/2020 9:09:54 PM Utc

The current time is definitely between the "issued at" and the "expires at" times, but the validation still fails. Is there something I am doing wrong?

Hash-Algorithm for key derivation (ECDH-ES)

Hey,
i tried your library and the one from Microsoft (Microsoft.IdentityModel.JsonWebTokens) and the interoperability between both.
https://datatracker.ietf.org/doc/html/rfc7518#section-4.6.2 seems to state that the Hash should be calculated with SHA256

In Microsoft code it looks like this:

// JWA's spec https://datatracker.ietf.org/doc/html/rfc7518#section-4.6.2 specifies SHA256, saml might be different
byte[] derivedKey = _ecdhPrivate.DeriveKeyFromHash(_ecdhPublic.PublicKey, HashAlgorithmName.SHA256, prepend, append);

In your code the hash algorithm is defined through it's encryption algorithm:

_hashAlgorithm = GetHashAlgorithm(encryptionAlgorithm);
...
 var hashAlgorithm = encryptionAlgorithm.SignatureAlgorithm.HashAlgorithm
...
exchangeHash = new ReadOnlySpan<byte>(ephemeralKey.DeriveKeyFromHash(otherPartyKey.PublicKey, _hashAlgorithm, _secretPreprend, secretAppend), 0, _keySizeInBytes);>

If you now use a combination of EcdhEsA128kw and Aes128CbcHmacSha256 it works because here SHA256 is used.
But if you use a combination of EcdhEsA256kw and Aes256CbcHmacSha512 the tokens from the MS-Lib and the tokens of your lib cannot be understood by the other party, because you would use SHA512 in that case.

Who is right - who is wrong? I dont have clue but hope you have an answer to this because i would like to connect two applications using different frameworks ;-)

Re-evaluate usage of Utf8JsonReader.ValueSpan for property name equality via SequenceEqual of raw bytes

https://github.com/ycrumeyrolle/Jwt/blob/59bace31afecca486d80cfb87f40db5fdbec4cd8/src/OpenIdConnect/Address.cs#L39-L45
This type of comparison of the raw bytes (using ValueSpan) will not always work and miss property names that are "equal" but contain escaped characters.

Is this expected/fine? If not, consider using ValueTextEqual APIs t do the property name comparisons. Generally speaking, getting the raw bytes via ValueSpan and then using SequenceEqual probably isn't what you want to do unless you control the input or only want to support the exact bytes.

https://github.com/dotnet/corefx/blob/d3911035f2ba3eb5c44310342cc1d654e42aa316/src/System.Text.Json/ref/System.Text.Json.cs#L316-L318
https://docs.microsoft.com/en-us/dotnet/api/system.text.json.utf8jsonreader.valuetextequals?view=netcore-3.0#System_Text_Json_Utf8JsonReader_ValueTextEquals_System_ReadOnlySpan_System_Byte__

For instance:
If the json payload contained formatted this comparison will work fine, but it wouldn't work for \\u0066ormatted (f == \u0066).
Something to consider.

I would double check usage of reader.ValueSpan property in other instances as well (where the comparison is being deferred (when saved in JwtProperty, etc.).
https://github.com/ycrumeyrolle/Jwt/search?utf8=%E2%9C%93&q=ValueSpan&type=
Like: https://github.com/ycrumeyrolle/Jwt/blob/90431793b0419333d1f47467192b3361f6c76e39/src/JsonWebToken/JwtObject.cs#L188

Also, is the JSON input here trusted or known to be valid UTF-8?

cc @ycrumeyrolle

Probable bug in Jwk.FromX509Certificate

Shouldn't the boolean parameter for ExportParameters be true if withPrivateKey is true? I was trying a JWE validation with asymmetric key and it kept on failing with "Encryption Key Not found" error -which I think maybe due to this. In file src\JsonWebToken\Jwk.cs

           if (withPrivateKey)
            {
                using var rsa = certificate.GetRSAPrivateKey();
                if (!(rsa is null))
                {
                    var rsaParameters = rsa.ExportParameters(false);
                    key = new RsaJwk(rsaParameters);
                }
            }

Also, it might be better to add a assert in test\JsonWebToken.Tests\JsonWebKeyTests.cs to check for jwk.HasPrivateKey property if the input certificate has a private key.

        [Theory]
        [MemberData(nameof(GetCertificates))]
        public void CreateFromCertificate(X509Certificate2 certificate, bool hasPrivateKey, int keySize)
        {
            var jwk = Jwk.FromX509Certificate(certificate, hasPrivateKey);
            Assert.Equal(keySize, jwk.KeySizeInBits); 
            if(hasPrivateKey)
            {
                Assert.True(jwk.HasPrivateKey);
            }
        }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.