-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
LFT-1285 - Ignore use=enc JWKs in JWKS parsing #364
Changes from all commits
102be6f
dc0abd2
0ec887f
ae7e2a3
38f020c
d77cd67
14ecd11
96b0e84
90d852f
3d91c22
ae36031
b0bc922
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
|
@@ -60,81 +63,153 @@ | |
/// <param name="json">The json JWK</param> | ||
/// <returns>A <see cref="JsonWebKey"/></returns> | ||
public static JsonWebKey FromJson( string json ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maintaining this API + behaviour for people that call this directly (> 0 in our code) |
||
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 | ||
) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mostly changed to TryX from throwing, but also added this separate |
||
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; | ||
} | ||
Comment on lines
93
to
99
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would maybe make this ignored in general (and not just "enc")? Perhaps a It would ultimately be nice to be able to say "the key {kid} is not intended for singnatures" rather than "the key {kid} was not found" though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, so I mentioned in the PR description basically I think our parser should just parse and deciding if a key was valid or not ought to be up to the user. All of our users currently only want signing keys so I think it's OK practically to just filter them (vs. exploding) but I only exposed that on JWKS because the API for getting one key implies it will never return null (it throws currently) and it gets used e.g. parsing keys from the database, and I didn't want to add sig=enc logic for right now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In other words rather than returning ignore from here I'd prefer if we just successfully parsed all valid JWKs and then moved the policy decisions elsewhere |
||
|
||
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 GitHub Actions / Build and test (Windows)
Check warning on line 117 in src/D2L.Security.OAuth2/Keys/JsonWebKey.cs GitHub Actions / Build and test (Windows)
|
||
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; | ||
|
||
} | ||
} | ||
|
@@ -163,6 +238,6 @@ | |
/// <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 ) { } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
namespace System.Diagnostics.CodeAnalysis; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Note filepath: .net4x.cs is treated specially via our build.) |
||
|
||
[AttributeUsage( AttributeTargets.Parameter )] | ||
internal sealed class NotNullWhenAttribute : Attribute { | ||
public bool ReturnValue { get; } | ||
|
||
public NotNullWhenAttribute( bool returnValue ) { | ||
ReturnValue = returnValue; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This filtering could apply more generally but for now I just want to do the minimal thing to get the integrations working. I think the API needs a redesign overall.