diff --git a/src/D2L.Security.OAuth2/Keys/Default/JsonWebKeySet.cs b/src/D2L.Security.OAuth2/Keys/Default/JsonWebKeySet.cs index 78621365..1283eb99 100644 --- a/src/D2L.Security.OAuth2/Keys/Default/JsonWebKeySet.cs +++ b/src/D2L.Security.OAuth2/Keys/Default/JsonWebKeySet.cs @@ -37,9 +37,19 @@ public JsonWebKeySet( string json, Uri src ) { #endif var builder = ImmutableArray.CreateBuilder(); + 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(); diff --git a/src/D2L.Security.OAuth2/Keys/JsonWebKey.cs b/src/D2L.Security.OAuth2/Keys/JsonWebKey.cs index 0f0478e5..48aeaa9a 100644 --- a/src/D2L.Security.OAuth2/Keys/JsonWebKey.cs +++ b/src/D2L.Security.OAuth2/Keys/JsonWebKey.cs @@ -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,32 +63,66 @@ public virtual DateTimeOffset? ExpiresAt { /// The json JWK /// A 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 data; try { data = JsonSerializer.Deserialize>( 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(); 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 ); } @@ -93,38 +130,68 @@ public static JsonWebKey FromJson( string json ) { 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(), @@ -132,9 +199,17 @@ public static JsonWebKey FromJson( string json ) { 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 @@ public JsonWebKeyParseException( string msg ) : base( msg ) { } /// /// Constructs a new /// - public JsonWebKeyParseException( string msg, Exception inner ) : base( msg, inner ) { } + public JsonWebKeyParseException( string msg, Exception? inner ) : base( msg, inner ) { } } } diff --git a/src/D2L.Security.OAuth2/NotNullWhenAttribute.net4x.cs b/src/D2L.Security.OAuth2/NotNullWhenAttribute.net4x.cs new file mode 100644 index 00000000..cce2e307 --- /dev/null +++ b/src/D2L.Security.OAuth2/NotNullWhenAttribute.net4x.cs @@ -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; + } +} \ No newline at end of file