Skip to content

Commit 4967267

Browse files
CADBIMDeveloperabatishchev
authored andcommitted
add a new algorithm factory
to create an algorithms based on JSON Web key sets
1 parent 9c2f05a commit 4967267

File tree

5 files changed

+170
-11
lines changed

5 files changed

+170
-11
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace JWT.Jwk;
2+
3+
public interface IJwtWebKeysCollectionFactory
4+
{
5+
JwtWebKeysCollection CreateKeys();
6+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System;
2+
using System.Security.Cryptography;
3+
using JWT.Algorithms;
4+
using JWT.Exceptions;
5+
using JWT.Serializers;
6+
7+
namespace JWT.Jwk
8+
{
9+
public sealed class JwtJsonWebKeySetAlgorithmFactory : IAlgorithmFactory
10+
{
11+
private readonly JwtWebKeysCollection _webKeysCollection;
12+
13+
public JwtJsonWebKeySetAlgorithmFactory(JwtWebKeysCollection webKeysCollection)
14+
{
15+
_webKeysCollection = webKeysCollection;
16+
}
17+
18+
public JwtJsonWebKeySetAlgorithmFactory(Func<JwtWebKeysCollection> getJsonWebKeys)
19+
{
20+
_webKeysCollection = getJsonWebKeys();
21+
}
22+
23+
public JwtJsonWebKeySetAlgorithmFactory(IJwtWebKeysCollectionFactory webKeysCollectionFactory)
24+
{
25+
_webKeysCollection = webKeysCollectionFactory.CreateKeys();
26+
}
27+
28+
public JwtJsonWebKeySetAlgorithmFactory(string keySet, IJsonSerializer serializer)
29+
{
30+
_webKeysCollection = new JwtWebKeysCollection(keySet, serializer);
31+
}
32+
33+
public JwtJsonWebKeySetAlgorithmFactory(string keySet, IJsonSerializerFactory jsonSerializerFactory)
34+
{
35+
_webKeysCollection = new JwtWebKeysCollection(keySet, jsonSerializerFactory);
36+
}
37+
38+
public IJwtAlgorithm Create(JwtDecoderContext context)
39+
{
40+
if (string.IsNullOrEmpty(context.Header.KeyId))
41+
throw new SignatureVerificationException("The key id is missing in the token header");
42+
43+
var key = _webKeysCollection.Find(context.Header.KeyId);
44+
45+
if (key == null)
46+
throw new SignatureVerificationException("The key id is not presented in the JSON Web key set");
47+
48+
if (key.KeyType != "RSA")
49+
throw new NotSupportedException($"JSON Web key type {key.KeyType} currently is not supported");
50+
51+
#if NETSTANDARD2_1 || NET6_0_OR_GREATER
52+
var rsaParameters = new RSAParameters
53+
{
54+
Modulus = JwtWebKeyPropertyValuesEncoder.Base64UrlDecode(key.Modulus),
55+
Exponent = JwtWebKeyPropertyValuesEncoder.Base64UrlDecode(key.Exponent)
56+
};
57+
58+
var rsa = RSA.Create(rsaParameters);
59+
60+
var rsaAlgorithmFactory = new RSAlgorithmFactory(rsa);
61+
62+
return rsaAlgorithmFactory.Create(context);
63+
#else
64+
throw new NotImplementedException("Not implemented yet");
65+
#endif
66+
}
67+
}
68+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
3+
namespace JWT.Jwk;
4+
5+
/// <summary>
6+
/// Based on Microsoft.AspNetCore.WebUtilities.WebEncoders
7+
/// </summary>
8+
internal static class JwtWebKeyPropertyValuesEncoder
9+
{
10+
public static byte[] Base64UrlDecode(string input)
11+
{
12+
if (input == null)
13+
return null;
14+
15+
var inputLength = input.Length;
16+
17+
var paddingCharsCount = GetNumBase64PaddingCharsToAddForDecode(inputLength);
18+
19+
var buffer = new char[inputLength + paddingCharsCount];
20+
21+
for (var i = 0; i < inputLength; ++i)
22+
{
23+
var symbol = input[i];
24+
25+
switch (symbol)
26+
{
27+
case '-':
28+
buffer[i] = '+';
29+
break;
30+
case '_':
31+
buffer[i] = '/';
32+
break;
33+
default:
34+
buffer[i] = symbol;
35+
break;
36+
}
37+
}
38+
39+
for (var i = input.Length; i < buffer.Length; ++i)
40+
buffer[i] = '=';
41+
42+
return Convert.FromBase64CharArray(buffer, 0, buffer.Length);
43+
}
44+
45+
private static int GetNumBase64PaddingCharsToAddForDecode(int inputLength)
46+
{
47+
switch (inputLength % 4)
48+
{
49+
case 0:
50+
return 0;
51+
case 2:
52+
return 2;
53+
case 3:
54+
return 1;
55+
default:
56+
throw new FormatException($"Malformed input: {inputLength} is an invalid input length.");
57+
}
58+
}
59+
}

tests/JWT.Tests.Common/Jwk/JwtWebKeysCollectionTests.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,21 @@
33
using JWT.Tests.Models;
44
using Microsoft.VisualStudio.TestTools.UnitTesting;
55

6-
namespace JWT.Tests.Jwk;
7-
8-
[TestClass]
9-
public class JwtWebKeysCollectionTests
6+
namespace JWT.Tests.Jwk
107
{
11-
[TestMethod]
12-
public void Should_Find_Json_Web_Key_By_KeyId()
8+
[TestClass]
9+
public class JwtWebKeysCollectionTests
1310
{
14-
var serializerFactory = new DefaultJsonSerializerFactory();
11+
[TestMethod]
12+
public void Should_Find_Json_Web_Key_By_KeyId()
13+
{
14+
var serializerFactory = new DefaultJsonSerializerFactory();
1515

16-
var collection = new JwtWebKeysCollection(TestData.JsonWebKeySet, serializerFactory);
16+
var collection = new JwtWebKeysCollection(TestData.JsonWebKeySet, serializerFactory);
1717

18-
var jwk = collection.Find(TestData.ServerRsaPublicThumbprint1);
18+
var jwk = collection.Find(TestData.ServerRsaPublicThumbprint1);
1919

20-
Assert.IsNotNull(jwk);
20+
Assert.IsNotNull(jwk);
21+
}
2122
}
2223
}

tests/JWT.Tests.Common/JwtDecoderTests.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using JWT.Algorithms;
66
using JWT.Builder;
77
using JWT.Exceptions;
8+
using JWT.Jwk;
89
using JWT.Serializers;
910
using JWT.Tests.Models;
1011
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -571,7 +572,31 @@ public void DecodeToObject_Should_Throw_Exception_On_Null_NotBefore_Claim()
571572
.Throw<SignatureVerificationException>()
572573
.WithMessage("Claim 'nbf' must be a number.", "because the invalid 'nbf' must result in an exception on decoding");
573574
}
574-
575+
576+
#if NETSTANDARD2_1 || NET6_0_OR_GREATER
577+
[TestMethod]
578+
public void Should_Decode_With_Json_Web_Keys()
579+
{
580+
var serializer = CreateSerializer();
581+
582+
var validator = new JwtValidator(serializer, new UtcDateTimeProvider());
583+
584+
var urlEncoder = new JwtBase64UrlEncoder();
585+
586+
var algorithmFactory = new JwtJsonWebKeySetAlgorithmFactory(TestData.JsonWebKeySet, serializer);
587+
588+
var decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithmFactory);
589+
590+
var customer = decoder.DecodeToObject<Customer>(TestData.TokenByAsymmetricAlgorithm);
591+
592+
Assert.IsNotNull(customer);
593+
594+
customer
595+
.Should()
596+
.BeEquivalentTo(TestData.Customer);
597+
}
598+
#endif
599+
575600
private static IJsonSerializer CreateSerializer() =>
576601
new DefaultJsonSerializerFactory().Create();
577602
}

0 commit comments

Comments
 (0)