Skip to content

Commit

Permalink
Refactoring RS256Algorithm, tests (#223)
Browse files Browse the repository at this point in the history
* Making GetPublicKey and GetPrivateKey consistent in RS256Algorithm
* Renaming Test to Tests
  • Loading branch information
abatishchev authored Sep 29, 2019
1 parent 7dd26d3 commit 8b42681
Show file tree
Hide file tree
Showing 13 changed files with 272 additions and 251 deletions.
36 changes: 17 additions & 19 deletions src/JWT/Algorithms/RS256Algorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,31 @@ namespace JWT.Algorithms
/// </summary>
public sealed class RS256Algorithm : IJwtAlgorithm
{
private readonly RSACryptoServiceProvider _publicKey;
private readonly RSA _publicKey;
private readonly RSA _privateKey;

/// <summary>
/// Creates an instance using the provided pair of public and private keys.
/// Creates an instance of <see cref="RS256Algorithm" /> using the provided pair of public and private keys.
/// </summary>
/// <param name="publicKey">The RSA service provider for verifying the data.</param>
/// <param name="privateKey">The RSA key for signing the data.</param>
public RS256Algorithm(RSACryptoServiceProvider publicKey, RSA privateKey)
/// <param name="publicKey">The public key for verifying the data.</param>
/// <param name="privateKey">The private key for signing the data.</param>
public RS256Algorithm(RSA publicKey, RSA privateKey)
{
_publicKey = publicKey ?? throw new ArgumentNullException(nameof(publicKey));
_privateKey = privateKey ?? throw new ArgumentNullException(nameof(privateKey));
}

/// <summary>
/// Creates an instance using the provided pair of public and private keys.
/// Creates an instance of <see cref="RS256Algorithm" /> using the provided public key only.
/// </summary>
/// <param name="publicKey">The RSA service provider for verifying the data.</param>
public RS256Algorithm(RSACryptoServiceProvider publicKey)
/// <remarks>
/// An instance created using this constructor can only be used for verifying the data, not for signing it.
/// </remarks>
/// <param name="publicKey">The public key for verifying the data.</param>
public RS256Algorithm(RSA publicKey)
{
_publicKey = publicKey ?? throw new ArgumentNullException(nameof(publicKey));
_privateKey = null;
}

/// <summary>
Expand All @@ -49,7 +53,7 @@ public RS256Algorithm(X509Certificate2 cert)

/// <inheritdoc />
public byte[] Sign(byte[] key, byte[] bytesToSign) =>
Sign(bytesToSign);
_privateKey is object ? Sign(bytesToSign) : throw new InvalidOperationException("Can't sign data without private key");

/// <summary>
/// Signs the provided bytes.
Expand All @@ -62,14 +66,10 @@ public byte[] Sign(byte[] bytesToSign) =>
/// <summary>
/// Verifies provided byte array with provided signature.
/// </summary>
/// <remarks>
/// 2.16.840.1.101.3.4.2.1 is the object id for the sha256NoSign algorithm.
/// See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnap/a48b02b2-2a10-4eb0-bed4-1807a6d2f5ad for further details.
/// </remarks>
/// <param name="bytesToSign">The data to verify</param>
/// <param name="signature">The signature to verify with</param>
public bool Verify(byte[] bytesToSign, byte[] signature) =>
_publicKey.VerifyData(bytesToSign, "2.16.840.1.101.3.4.2.1", signature);
_publicKey.VerifyData(bytesToSign, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

private static RSA GetPrivateKey(X509Certificate2 cert)
{
Expand All @@ -80,15 +80,13 @@ private static RSA GetPrivateKey(X509Certificate2 cert)
#endif
}

private static RSACryptoServiceProvider GetPublicKey(X509Certificate2 cert)
private static RSA GetPublicKey(X509Certificate2 cert)
{
AsymmetricAlgorithm alg;
#if NETSTANDARD1_3
alg = cert.GetRSAPublicKey();
return cert.GetRSAPublicKey();
#else
alg = cert.PublicKey.Key;
return (RSA)cert.PublicKey.Key;
#endif
return (RSACryptoServiceProvider)alg;
}
}
}
2 changes: 1 addition & 1 deletion src/JWT/JWT.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<!-- Include PDB in the built .nupkg -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>

<Version>5.2.3</Version>
<Version>5.3.0-beta1</Version>
<FileVersion>5.0.0.0</FileVersion>
<AssemblyVersion>5.0.0.0</AssemblyVersion>
</PropertyGroup>
Expand Down
10 changes: 0 additions & 10 deletions tests/JWT.Tests.Common/JWT.Tests.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Compile Remove="JwtDecoderTest.cs" />
<Compile Remove="JwtEncoderTest.cs" />
</ItemGroup>

<ItemGroup>
<None Include="JwtDecoderTest.cs" />
<None Include="JwtEncoderTest.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.11.0" />
<PackageReference Include="FluentAssertions" Version="5.8.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace JWT.Tests.Common
{
public class JwtBuilderDecodeTest
public class JwtBuilderDecodeTests
{
private const string _sampleToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGFpbTEiOjAsImNsYWltMiI6ImNsYWltMi12YWx1ZSJ9.8pwBI_HtXqI3UgQHQ_rDRnSQRxFL1SR8fbQoS-5kM5s";
private const string _sampleSecret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
Expand All @@ -28,26 +28,26 @@ public void DecodeToken_WithoutToken_Should_Throw_Exception()
{
var builder = new JwtBuilder();

Action decodingANullJwt = ()
=> builder.Decode(null);
Action decodingANullJwt =
() => builder.Decode(null);

decodingANullJwt.Should()
.Throw<ArgumentException>("because null is not a valid value for a token");
.Throw<ArgumentException>("because null is not a valid value for a token");
}

[Fact]
public void DecodeToken_WithoutSerializer_Should_Throw_Exception()
{
var builder = new JwtBuilder();
var serializer = (IJsonSerializer) null;
var serializer = (IJsonSerializer)null;
const string token = _sampleToken;

Action decodingAJwtWithANullSerializer = ()
=> builder.WithSerializer(serializer)
.Decode(token);
Action decodeJwtWithNullSerializer =
() => builder.WithSerializer(serializer)
.Decode(token);

decodingAJwtWithANullSerializer.Should()
.Throw<InvalidOperationException>("because a token can't be decoded without a valid serializer");
decodeJwtWithNullSerializer.Should()
.Throw<InvalidOperationException>("because a token can't be decoded without a valid serializer");
}

[Fact]
Expand All @@ -62,22 +62,22 @@ public void DecodeToken_WithSerializer()
.Decode(token);

payload.Should()
.NotBeEmpty("because the token should be correctly decoded and its data extracted");
.NotBeEmpty("because the token should be correctly decoded and its data extracted");
}

[Fact]
public void DecodeToken_WithoutUrlEncoder_Should_Throw_Exception()
{
var builder = new JwtBuilder();
var urlEncoder = (IBase64UrlEncoder) null;
var urlEncoder = (IBase64UrlEncoder)null;
const string token = _sampleToken;

Action decodingAJwtWithANullEncoder = ()
=> builder.WithUrlEncoder(urlEncoder)
.Decode(token);
Action decodeJwtWithNullEncoder =
() => builder.WithUrlEncoder(urlEncoder)
.Decode(token);

decodingAJwtWithANullEncoder.Should()
.Throw<InvalidOperationException>("because a token can't be decoded without a valid UrlEncoder");
decodeJwtWithNullEncoder.Should()
.Throw<InvalidOperationException>("because a token can't be decoded without a valid UrlEncoder");
}

[Fact]
Expand All @@ -92,23 +92,23 @@ public void DecodeToken_WithUrlEncoder()
.Decode(token);

payload.Should()
.NotBeEmpty("because the token should have been correctly decoded with the valid base 64 encoder");
.NotBeEmpty("because the token should have been correctly decoded with the valid base 64 encoder");
}

[Fact]
public void DecodeToken_WithoutTimeProvider_Should_Throw_Exception()
{
var builder = new JwtBuilder();
var dateTimeProvider = (IDateTimeProvider) null;
var dateTimeProvider = (IDateTimeProvider)null;
const string token = _sampleToken;

Action decodingAJwtWithANullDateTimeProvider = ()
=> builder
.WithDateTimeProvider(dateTimeProvider)
.Decode(token);
Action decodingJwtWithNullDateTimeProvider =
() => builder
.WithDateTimeProvider(dateTimeProvider)
.Decode(token);

decodingAJwtWithANullDateTimeProvider.Should()
.Throw<InvalidOperationException>("because a token can't be decoded without a valid DateTimeProvider");
decodingJwtWithNullDateTimeProvider.Should()
.Throw<InvalidOperationException>("because a token can't be decoded without a valid DateTimeProvider");
}

[Fact]
Expand All @@ -124,22 +124,22 @@ public void DecodeToken_WithDateTimeProvider()
.Decode(token);

payload.Should()
.NotBeEmpty("because the decoding process must be successful with a valid DateTimeProvider");
.NotBeEmpty("because the decoding process must be successful with a valid DateTimeProvider");
}

[Fact]
public void DecodeToken_WithoutValidator()
{
var builder = new JwtBuilder();
const string token = _sampleToken;
var validator = (IJwtValidator) null;
var validator = (IJwtValidator)null;

var payload = builder
.WithValidator(validator)
.Decode(token);

payload.Should()
.NotBeEmpty("because a JWT should not necessary have a validator to be decoded");
.NotBeEmpty("because a JWT should not necessary have a validator to be decoded");
}

[Fact]
Expand All @@ -156,7 +156,7 @@ public void DecodeToken_WithExplicitValidator()
.Decode(token);

payload.Should()
.NotBeEmpty("because a JWT should be correctly decoded, even with a validator");
.NotBeEmpty("because a JWT should be correctly decoded, even with a validator");
}

[Fact]
Expand All @@ -172,7 +172,7 @@ public void DecodeToken_WithVerifySignature()
.Decode(token);

payload.Should()
.NotBeEmpty("because the signature must have been verified successfully and the JWT correctly decoded");
.NotBeEmpty("because the signature must have been verified successfully and the JWT correctly decoded");
}

[Fact]
Expand All @@ -188,7 +188,7 @@ public void DecodeToken_WithVerifySignature_MultipleSecrets()
.Decode(token);

payload.Should()
.NotBeEmpty("because one of the provided signatures must have been verified successfully and the JWT correctly decoded");
.NotBeEmpty("because one of the provided signatures must have been verified successfully and the JWT correctly decoded");
}

[Fact]
Expand All @@ -202,7 +202,7 @@ public void DecodeToken_WithoutVerifySignature()
.Decode(token);

payload.Should()
.NotBeEmpty("because the token should have been decoded without errors if asked so");
.NotBeEmpty("because the token should have been decoded without errors if asked so");
}

[Fact]
Expand All @@ -218,13 +218,13 @@ public void DecodeToken_ToDictionary()
.Decode<Dictionary<string, string>>(token);

payload.Should()
.BeOfType<Dictionary<string, string>>("because the result should be of the requested type");
.BeOfType<Dictionary<string, string>>("because the result should be of the requested type");

payload.Should()
.HaveCount(2, "because there is two encoded claims that should be resulting in two keys");
.HaveCount(2, "because there is two encoded claims that should be resulting in two keys");

payload["claim1"].Should()
.Be(0.ToString(), "because the key of the first claim should give its original value");
.Be(0.ToString(), "because the key of the first claim should give its original value");
}

[Fact]
Expand All @@ -240,32 +240,32 @@ public void DecodeToken_ToDictionary_MultipleSecrets()
.Decode<Dictionary<string, string>>(token);

payload.Should()
.BeOfType<Dictionary<string, string>>("because the result should be of the requested type");
.BeOfType<Dictionary<string, string>>("because the result should be of the requested type");

payload.Should()
.HaveCount(2, "because there is two encoded claims that should be resulting in two keys");
.HaveCount(2, "because there is two encoded claims that should be resulting in two keys");

payload["claim1"].Should()
.Be(0.ToString(), "because the key of the first claim should give its original value");
.Be(0.ToString(), "because the key of the first claim should give its original value");
}

[Fact]
public void DecodeToken_ToDictionary_WithoutSerializer_Should_Throw_Exception()
{
var builder = new JwtBuilder();
const string secret = _sampleSecret;
var serializer = (IJsonSerializer) null;
var serializer = (IJsonSerializer)null;
const string token = _sampleToken;

Action decodingAJwtWithANullSerializerInADict = ()
=> builder
.WithSerializer(serializer)
.WithSecret(secret)
.MustVerifySignature()
.Decode<Dictionary<string, string>>(token);
Action decodeJwtWithNullSerializer =
() => builder
.WithSerializer(serializer)
.WithSecret(secret)
.MustVerifySignature()
.Decode<Dictionary<string, string>>(token);

decodingAJwtWithANullSerializerInADict.Should()
.Throw<InvalidOperationException>("because a token can't be decoded without a valid serializer");
decodeJwtWithNullSerializer.Should()
.Throw<InvalidOperationException>("because a token can't be decoded without a valid serializer");
}
}
}
}
Loading

0 comments on commit 8b42681

Please sign in to comment.