Skip to content

Commit

Permalink
Added support for SHA384 and SHA512 hashing algorithms with RSASSA-PK…
Browse files Browse the repository at this point in the history
…CS1-v1_5 (#305)

* Added RSAAlgorithm abstract base class which all RSA algorithms derive from
* Added RS384Algorithm, RS512Algorithm
* Refactored RSAlgorithmFactory 
* Updated, added tests
* Bumped version to 8.0.0
  • Loading branch information
simon-pearson authored Jan 31, 2021
1 parent 93d9139 commit 263d6d1
Show file tree
Hide file tree
Showing 15 changed files with 573 additions and 145 deletions.
7 changes: 7 additions & 0 deletions src/JWT/Algorithms/HMACSHA256Algorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,12 @@ public byte[] Sign(byte[] key, byte[] bytesToSign)

/// <inheritdoc />
public string Name => JwtAlgorithmName.HS256.ToString();

public string HashAlgorithm =>
#if NET35 || NET40
HashAlgorithmName.SHA256;
#else
HashAlgorithmName.SHA256.Name;
#endif
}
}
7 changes: 7 additions & 0 deletions src/JWT/Algorithms/HMACSHA384Algorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,12 @@ public byte[] Sign(byte[] key, byte[] bytesToSign)

/// <inheritdoc />
public string Name => JwtAlgorithmName.HS384.ToString();

public string HashAlgorithm =>
#if NET35 || NET40
HashAlgorithmName.SHA384;
#else
HashAlgorithmName.SHA384.Name;
#endif
}
}
7 changes: 7 additions & 0 deletions src/JWT/Algorithms/HMACSHA512Algorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,12 @@ public byte[] Sign(byte[] key, byte[] bytesToSign)

/// <inheritdoc />
public string Name => JwtAlgorithmName.HS512.ToString();

public string HashAlgorithm =>
#if NET35 || NET40
HashAlgorithmName.SHA512;
#else
HashAlgorithmName.SHA512.Name;
#endif
}
}
5 changes: 5 additions & 0 deletions src/JWT/Algorithms/IJwtAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ public interface IJwtAlgorithm
/// Gets algorithm name.
/// </summary>
string Name { get; }

/// <summary>
/// Gets name of the hashing algorithm (e.g. SHA-256/SHA-384/SHA-512).
/// </summary>
string HashAlgorithm { get; }
}

/// <summary>
Expand Down
12 changes: 11 additions & 1 deletion src/JWT/Algorithms/JwtAlgorithmName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ public enum JwtAlgorithmName
/// <summary>
/// RSASSA-PKCS1-v1_5 using SHA-256
/// </summary>
RS256
RS256,

/// <summary>
/// RSASSA-PKCS1-v1_5 using SHA-384
/// </summary>
RS384,

/// <summary>
/// RSASSA-PKCS1-v1_5 using SHA-512
/// </summary>
RS512
}
}
70 changes: 8 additions & 62 deletions src/JWT/Algorithms/RS256Algorithm.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
using System;
using System.Security.Cryptography;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace JWT.Algorithms
{
/// <summary>
/// RSASSA-PKCS1-v1_5 using SHA-256
/// </summary>
public sealed class RS256Algorithm : IAsymmetricAlgorithm
public sealed class RS256Algorithm : RSAlgorithm
{
private readonly RSA _publicKey;
private readonly RSA _privateKey;

/// <summary>
/// Creates an instance of <see cref="RS256Algorithm" /> using the provided pair of public and private keys.
/// </summary>
/// <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)
: base(publicKey, privateKey)
{
_publicKey = publicKey ?? throw new ArgumentNullException(nameof(publicKey));
_privateKey = privateKey ?? throw new ArgumentNullException(nameof(privateKey));
}

/// <summary>
Expand All @@ -31,75 +26,26 @@ public RS256Algorithm(RSA publicKey, RSA privateKey)
/// </remarks>
/// <param name="publicKey">The public key for verifying the data.</param>
public RS256Algorithm(RSA publicKey)
: base(publicKey)
{
_publicKey = publicKey ?? throw new ArgumentNullException(nameof(publicKey));
_privateKey = null;
}

/// <summary>
/// Creates an instance using the provided certificate.
/// </summary>
/// <param name="cert">The certificate having a public key and an optional private key.</param>
public RS256Algorithm(X509Certificate2 cert)
: base(cert)
{
_publicKey = GetPublicKey(cert) ?? throw new Exception("Certificate's PublicKey cannot be null.");
_privateKey = GetPrivateKey(cert);
}

/// <inheritdoc />
public string Name => JwtAlgorithmName.RS256.ToString();

/// <inheritdoc />
public byte[] Sign(byte[] key, byte[] bytesToSign)
{
if (_privateKey is null)
throw new InvalidOperationException("Can't sign data without private key");
public override string Name => JwtAlgorithmName.RS256.ToString();

return Sign(bytesToSign);
}

/// <summary>
/// Signs the provided bytes.
/// </summary>
/// <param name="bytesToSign">The bytes to sign.</param>
/// <returns>The signed bytes.</returns>
public byte[] Sign(byte[] bytesToSign) =>
#if NET35 || NET40
((RSACryptoServiceProvider)_privateKey).SignData(bytesToSign, HashAlgorithmName.SHA256);
#else
_privateKey.SignData(bytesToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
#endif

/// <inheritdoc />
public bool Verify(byte[] bytesToSign, byte[] signature) =>
#if NET35 || NET40
((RSACryptoServiceProvider)_publicKey).VerifyData(bytesToSign, HashAlgorithmName.SHA256, signature);
protected override string HashAlgorithmInternal => HashAlgorithmName.SHA256;
#else
_publicKey.VerifyData(bytesToSign, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
protected override HashAlgorithmName HashAlgorithmInternal => HashAlgorithmName.SHA256;
#endif

private static RSA GetPrivateKey(X509Certificate2 cert)
{
if (cert is null)
throw new ArgumentNullException(nameof(cert));

#if NETSTANDARD1_3
return cert.GetRSAPrivateKey();
#else
return (RSA)cert.PrivateKey;
#endif
}

private static RSA GetPublicKey(X509Certificate2 cert)
{
if (cert is null)
throw new ArgumentNullException(nameof(cert));

#if NETSTANDARD1_3
return cert.GetRSAPublicKey();
#else
return (RSA)cert.PublicKey.Key;
#endif
}
}
}
51 changes: 51 additions & 0 deletions src/JWT/Algorithms/RS384Algorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace JWT.Algorithms
{
/// <summary>
/// RSASSA-PKCS1-v1_5 using SHA-384
/// </summary>
public sealed class RS384Algorithm : RSAlgorithm
{
/// <summary>
/// Creates an instance of <see cref="RS384Algorithm" /> using the provided pair of public and private keys.
/// </summary>
/// <param name="publicKey">The public key for verifying the data.</param>
/// <param name="privateKey">The private key for signing the data.</param>
public RS384Algorithm(RSA publicKey, RSA privateKey)
: base(publicKey, privateKey)
{
}

/// <summary>
/// Creates an instance of <see cref="RS384Algorithm" /> using the provided public key only.
/// </summary>
/// <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 RS384Algorithm(RSA publicKey)
: base(publicKey)
{
}

/// <summary>
/// Creates an instance using the provided certificate.
/// </summary>
/// <param name="cert">The certificate having a public key and an optional private key.</param>
public RS384Algorithm(X509Certificate2 cert)
: base(cert)
{
}

/// <inheritdoc />
public override string Name => JwtAlgorithmName.RS384.ToString();

#if NET35 || NET40
protected override string HashAlgorithmInternal => HashAlgorithmName.SHA384;
#else
protected override HashAlgorithmName HashAlgorithmInternal => HashAlgorithmName.SHA384;
#endif
}
}
51 changes: 51 additions & 0 deletions src/JWT/Algorithms/RS512Algorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace JWT.Algorithms
{
/// <summary>
/// RSASSA-PKCS1-v1_5 using SHA-512
/// </summary>
public sealed class RS512Algorithm : RSAlgorithm
{
/// <summary>
/// Creates an instance of <see cref="RS512Algorithm" /> using the provided pair of public and private keys.
/// </summary>
/// <param name="publicKey">The public key for verifying the data.</param>
/// <param name="privateKey">The private key for signing the data.</param>
public RS512Algorithm(RSA publicKey, RSA privateKey)
: base(publicKey, privateKey)
{
}

/// <summary>
/// Creates an instance of <see cref="RS512Algorithm" /> using the provided public key only.
/// </summary>
/// <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 RS512Algorithm(RSA publicKey)
: base(publicKey)
{
}

/// <summary>
/// Creates an instance using the provided certificate.
/// </summary>
/// <param name="cert">The certificate having a public key and an optional private key.</param>
public RS512Algorithm(X509Certificate2 cert)
: base(cert)
{
}

/// <inheritdoc />
public override string Name => JwtAlgorithmName.RS512.ToString();

#if NET35 || NET40
protected override string HashAlgorithmInternal => HashAlgorithmName.SHA512;
#else
protected override HashAlgorithmName HashAlgorithmInternal => HashAlgorithmName.SHA512;
#endif
}
}
Loading

0 comments on commit 263d6d1

Please sign in to comment.