Skip to content

Commit

Permalink
LFT-1285 - Ignore use=enc JWKs in JWKS parsing (#364)
Browse files Browse the repository at this point in the history
  • Loading branch information
j3parker authored Jul 31, 2024
1 parent 49301a7 commit 0bad498
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 20 deletions.
12 changes: 11 additions & 1 deletion src/D2L.Security.OAuth2/Keys/Default/JsonWebKeySet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,19 @@ public JsonWebKeySet( string json, Uri src ) {
#endif

var builder = ImmutableArray.CreateBuilder<JsonWebKey>();

foreach( object keyObject in keyObjects ) {
string keyJson = JsonSerializer.Serialize( keyObject );
JsonWebKey key = JsonWebKey.FromJson( keyJson );

if( !JsonWebKey.TryParseJsonWebKey( keyJson, out var key, out var error, out var exception, out var useEncKey ) ) {
if( useEncKey ) {
// Just filter out keys meant for encryption
continue;
} else {
throw new JsonWebKeyParseException( error, exception );
}
}

builder.Add( key );
}
m_keys = builder.ToImmutable();
Expand Down
113 changes: 94 additions & 19 deletions src/D2L.Security.OAuth2/Keys/JsonWebKey.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using D2L.Security.OAuth2.Keys.Default;

#if NET6_0
Expand Down Expand Up @@ -60,81 +63,153 @@ public virtual DateTimeOffset? ExpiresAt {
/// <param name="json">The json JWK</param>
/// <returns>A <see cref="JsonWebKey"/></returns>
public static JsonWebKey FromJson( string json ) {
if( !TryParseJsonWebKey( json, out var result, out var error, out var e, out _ ) ) {
throw new JsonWebKeyParseException( error, e );
}

return result;
}

public static bool TryParseJsonWebKey(
string json,
[NotNullWhen( true )]
out JsonWebKey? result,
[NotNullWhen( false )]
out string? error,
out Exception? exception,
out bool useEncKey
) {
Dictionary<string, object> data;
try {
data = JsonSerializer.Deserialize<Dictionary<string, object>>( json );
} catch ( JsonException e ) {
throw new JsonWebKeyParseException( "error deserializing JSON web key string", e );
result = null;
error = "error deserializing JSON web key string";
exception = e;
useEncKey = false;
return false;
}

if( data.ContainsKey( "use" ) && data[ "use" ] != null && data[ "use" ].ToString() != "sig" ) {
string msg = String.Format( "invalid 'use' value in JSON web key: {0}", data[ "use" ] );
throw new JsonWebKeyParseException( msg );
result = null;
error = "invalid 'use' value in JSON web key: " + data[ "use" ];
exception = null;
useEncKey = data[ "use" ].ToString() == "enc";
return false;
}

if( !data.ContainsKey( "kty" ) ) {
throw new JsonWebKeyParseException( "missing 'kty' parameter in JSON web key" );
result = null;
error = "missing 'kty' parameter in JSON web key";
exception = null;
useEncKey = false;
return false;
}

if( !data.ContainsKey( "kid" ) ) {
throw new JsonWebKeyParseException( "missing 'kid' parameter in JSON web key" );
result = null;
error = "missing 'kid' parameter in JSON web key";
exception = null;
useEncKey = false;
return false;
}

string id = data[ "kid" ].ToString();

Check warning on line 117 in src/D2L.Security.OAuth2/Keys/JsonWebKey.cs

View workflow job for this annotation

GitHub Actions / Build and test (Windows)

Converting null literal or possible null value to non-nullable type.

Check warning on line 117 in src/D2L.Security.OAuth2/Keys/JsonWebKey.cs

View workflow job for this annotation

GitHub Actions / Build and test (Windows)

Converting null literal or possible null value to non-nullable type.

Check warning on line 117 in src/D2L.Security.OAuth2/Keys/JsonWebKey.cs

View workflow job for this annotation

GitHub Actions / Build and test (Linux)

Converting null literal or possible null value to non-nullable type.

Check warning on line 117 in src/D2L.Security.OAuth2/Keys/JsonWebKey.cs

View workflow job for this annotation

GitHub Actions / Build and test (Linux)

Converting null literal or possible null value to non-nullable type.

Check warning on line 117 in src/D2L.Security.OAuth2/Keys/JsonWebKey.cs

View workflow job for this annotation

GitHub Actions / Build and test (Windows)

Converting null literal or possible null value to non-nullable type.

Check warning on line 117 in src/D2L.Security.OAuth2/Keys/JsonWebKey.cs

View workflow job for this annotation

GitHub Actions / Build and test (Windows)

Converting null literal or possible null value to non-nullable type.
DateTimeOffset? expiresAt = null;
if( data.ContainsKey( "exp" ) ) {
if( !long.TryParse( data[ "exp" ].ToString(), out long ts ) ) {
string msg = String.Format( "invalid 'exp' value in JSON web key: {0}", data[ "exp" ] );
throw new JsonWebKeyParseException( msg );
result = null;
error = "invalid 'exp' value in JSON web key: " + data[ "exp" ];
exception = null;
useEncKey = false;
return false;
}
expiresAt = DateTimeOffset.FromUnixTimeSeconds( ts );
}

switch( data[ "kty" ].ToString() ) {
case "RSA":
if( !data.ContainsKey( "n" ) ) {
throw new JsonWebKeyParseException( "missing 'n' parameter in RSA JSON web key" );
result = null;
error = "missing 'n' parameter in RSA JSON web key";
exception = null;
useEncKey = false;
return false;
}

if( !data.ContainsKey( "e" ) ) {
throw new JsonWebKeyParseException( "missing 'e' parameter in RSA JSON web key" );
result = null;
error = "missing 'e' parameter in RSA JSON web key";
exception = null;
useEncKey = false;
return false;
}

if( HasRsaPrivateKeyMaterial( data ) ) {
throw new JsonWebKeyParseException( "RSA JSON web key has private key material" );
result = null;
error = "RSA JSON web key has private key material";
exception = null;
useEncKey = false;
return false;
}

return new RsaJsonWebKey(
result = new RsaJsonWebKey(
id: id,
expiresAt: expiresAt,
n: data[ "n" ].ToString(),
e: data[ "e" ].ToString()
);

error = null;
exception = null;
useEncKey = false;

return true;

case "EC":
if( !data.ContainsKey( "crv" ) ) {
throw new JsonWebKeyParseException( "missing 'crv' parameter in EC JSON web key" );
result = null;
error = "missing 'crv' parameter in EC JSON web key";
exception = null;
useEncKey = false;
return false;
}

if( !data.ContainsKey( "x" ) ) {
throw new JsonWebKeyParseException( "missing 'x' parameter in EC JSON web key" );
result = null;
error = "missing 'x' parameter in EC JSON web key";
exception = null;
useEncKey = false;
return false;
}

if( !data.ContainsKey( "y" ) ) {
throw new JsonWebKeyParseException( "missing 'y' parameter in EC JSON web key" );
result = null;
error = "missing 'y' parameter in EC JSON web key";
exception = null;
useEncKey = false;
return false;
}

return new EcDsaJsonWebKey(
result = new EcDsaJsonWebKey(
id: id,
expiresAt: expiresAt,
curve: data[ "crv" ].ToString(),
x: data[ "x" ].ToString(),
y: data[ "y" ].ToString()
);

error = null;
exception = null;
useEncKey = false;
return true;

default:
string msg = String.Format( "'{0}' is not a supported JSON eb key type", data[ "kty" ] );
throw new JsonWebKeyParseException( msg );
result = null;
error = $"'{data["kty"]}' is not a supported JSON web key type";
exception = null;
useEncKey = false;
return false;

}
}
Expand Down Expand Up @@ -163,6 +238,6 @@ public JsonWebKeyParseException( string msg ) : base( msg ) { }
/// <summary>
/// Constructs a new <see cref="JsonWebKeyParseException"/>
/// </summary>
public JsonWebKeyParseException( string msg, Exception inner ) : base( msg, inner ) { }
public JsonWebKeyParseException( string msg, Exception? inner ) : base( msg, inner ) { }
}
}
10 changes: 10 additions & 0 deletions src/D2L.Security.OAuth2/NotNullWhenAttribute.net4x.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace System.Diagnostics.CodeAnalysis;

[AttributeUsage( AttributeTargets.Parameter )]
internal sealed class NotNullWhenAttribute : Attribute {
public bool ReturnValue { get; }

public NotNullWhenAttribute( bool returnValue ) {
ReturnValue = returnValue;
}
}

0 comments on commit 0bad498

Please sign in to comment.