Skip to content

Commit

Permalink
Added base class CertificateAlgorithm (#432)
Browse files Browse the repository at this point in the history
* Added base class CertificateAlgorithm
* Bumped version to 10.0.0-beta6
  • Loading branch information
abatishchev authored Aug 1, 2022
1 parent 95b5631 commit b387094
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 157 deletions.
76 changes: 76 additions & 0 deletions src/JWT/Algorithms/CertificateAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace JWT.Algorithms
{
public abstract class CertificateAlgorithm<T> : IAsymmetricAlgorithm
where T : class
{
protected readonly T _publicKey;
protected readonly T _privateKey;

protected CertificateAlgorithm(T publicKey, T privateKey)
{
_publicKey = publicKey ?? throw new ArgumentNullException(nameof(publicKey));
_privateKey = privateKey ?? throw new ArgumentNullException(nameof(privateKey));
}

protected CertificateAlgorithm(T publicKey)
{
_publicKey = publicKey ?? throw new ArgumentNullException(nameof(publicKey));
_privateKey = null;
}

protected CertificateAlgorithm(X509Certificate2 cert)
{
if (cert is null)
throw new InvalidOperationException(nameof(cert));

_publicKey = GetPublicKey(cert) ?? throw new Exception("Certificate must have public key.");
_privateKey = GetPrivateKey(cert);
}

/// <inheritdoc />
public abstract string Name { get; }

/// <inheritdoc />
public abstract HashAlgorithmName HashAlgorithmName { get; }

/// <inheritdoc />
public byte[] Sign(byte[] key, byte[] bytesToSign) =>
Sign(bytesToSign);

/// <summary>
/// Signs the provided byte array with the private key.
/// </summary>
public byte[] Sign(byte[] bytesToSign)
{
if (bytesToSign is null)
throw new ArgumentNullException(nameof(bytesToSign));
if (_privateKey is null)
throw new InvalidOperationException("Can't sign data without private key");

return SignData(bytesToSign);
}

/// <inheritdoc />
public bool Verify(byte[] bytesToSign, byte[] signature)
{
if (bytesToSign is null)
throw new ArgumentNullException(nameof(bytesToSign));
if (signature is null)
throw new ArgumentNullException(nameof(signature));

return VerifyData(bytesToSign, signature);
}

protected abstract T GetPublicKey(X509Certificate2 cert);

protected abstract T GetPrivateKey(X509Certificate2 cert);

protected abstract byte[] SignData(byte[] bytesToSign);

protected abstract bool VerifyData(byte[] bytesToSign, byte[] signature);
}
}
151 changes: 57 additions & 94 deletions src/JWT/Algorithms/ECDSAAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -1,94 +1,57 @@
#if NETSTANDARD2_0 || NET6_0
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace JWT.Algorithms
{
/// <summary>
/// Abstract base class for all ECDSA algorithms
/// </summary>
public abstract class ECDSAAlgorithm : IAsymmetricAlgorithm
{
private readonly ECDsa _publicKey;
private readonly ECDsa _privateKey;

/// <summary>
/// Creates an instance of <see cref="ECDSAAlgorithm" /> 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>
protected ECDSAAlgorithm(ECDsa publicKey, ECDsa privateKey)
{
_publicKey = publicKey ?? throw new ArgumentNullException(nameof(publicKey));
_privateKey = privateKey ?? throw new ArgumentNullException(nameof(privateKey));
}

/// <summary>
/// Creates an instance of <see cref="ECDSAAlgorithm" /> 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>
protected ECDSAAlgorithm(ECDsa 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>
protected ECDSAAlgorithm(X509Certificate2 cert)
{
_publicKey = GetPublicKey(cert) ?? throw new Exception("Certificate's PublicKey cannot be null.");
_privateKey = GetPrivateKey(cert);
}

/// <inheritdoc />
public abstract string Name { get; }

/// <inheritdoc />
public abstract HashAlgorithmName HashAlgorithmName { get; }

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

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)
=> _privateKey.SignData(bytesToSign, this.HashAlgorithmName);

/// <inheritdoc />
public bool Verify(byte[] bytesToSign, byte[] signature)
=> _publicKey.VerifyData(bytesToSign, signature, this.HashAlgorithmName);

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

return cert.GetECDsaPrivateKey();
}

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

return cert.GetECDsaPublicKey();
}
}
}
#endif
#if NETSTANDARD2_0 || NET6_0
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace JWT.Algorithms
{
/// <summary>
/// Abstract base class for all ECDSA algorithms
/// </summary>
public abstract class ECDSAAlgorithm : CertificateAlgorithm<ECDsa>
{
/// <summary>
/// Creates an instance of <see cref="ECDSAAlgorithm" /> 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>
protected ECDSAAlgorithm(ECDsa publicKey, ECDsa privateKey)
: base(publicKey, privateKey)
{
}

/// <summary>
/// Creates an instance of <see cref="ECDSAAlgorithm" /> 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>
protected ECDSAAlgorithm(ECDsa 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>
protected ECDSAAlgorithm(X509Certificate2 cert)
: base(cert)
{
}

protected override ECDsa GetPublicKey(X509Certificate2 cert) =>
cert.GetECDsaPublicKey();

protected override ECDsa GetPrivateKey(X509Certificate2 cert) =>
cert.GetECDsaPrivateKey();


protected override byte[] SignData(byte[] bytesToSign) =>
_privateKey.SignData(bytesToSign, this.HashAlgorithmName);

protected override bool VerifyData(byte[] bytesToSign, byte[] signature) =>
_publicKey.VerifyData(bytesToSign, signature, this.HashAlgorithmName);
}
}
#endif
84 changes: 23 additions & 61 deletions src/JWT/Algorithms/RSAlgorithm.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 abstract class RSAlgorithm : IAsymmetricAlgorithm
public abstract class RSAlgorithm : CertificateAlgorithm<RSA>
{
private readonly RSA _publicKey;
private readonly RSA _privateKey;

/// <summary>
/// Creates an instance of <see cref="RSAlgorithm" /> 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>
protected RSAlgorithm(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,78 +26,45 @@ protected RSAlgorithm(RSA publicKey, RSA privateKey)
/// </remarks>
/// <param name="publicKey">The public key for verifying the data.</param>
protected RSAlgorithm(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>
protected RSAlgorithm(X509Certificate2 cert)
: base(cert)
{
_publicKey = GetPublicKey(cert) ?? throw new Exception("Certificate's PublicKey cannot be null.");
_privateKey = GetPrivateKey(cert);
}

/// <inheritdoc />
public abstract string Name { get; }

/// <inheritdoc />
public abstract HashAlgorithmName HashAlgorithmName { get; }

/// <inheritdoc />
public byte[] Sign(byte[] key, byte[] bytesToSign)
{
if (_privateKey is null)
throw new InvalidOperationException("Can't sign data without private key");
}

protected override RSA GetPublicKey(X509Certificate2 cert) =>
#if NETSTANDARD || NETCOREAPP || NET462
cert.GetRSAPublicKey();
#else
(RSA)cert.PublicKey.Key;
#endif

return Sign(bytesToSign);
}
protected override RSA GetPrivateKey(X509Certificate2 cert) =>
#if NETSTANDARD || NETCOREAPP || NET462
cert.GetRSAPrivateKey();
#else
(RSA)cert.PrivateKey;
#endif

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

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

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

#if NETSTANDARD || NETCOREAPP || NET462
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 NETSTANDARD || NETCOREAPP || NET462
return cert.GetRSAPublicKey();
#else
return (RSA)cert.PublicKey.Key;
#endif
}
}
}
}
4 changes: 2 additions & 2 deletions src/JWT/JWT.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;net6.0;net35;net40;net462;</TargetFrameworks>
Expand All @@ -20,7 +20,7 @@
<Authors>Alexander Batishchev, John Sheehan, Michael Lehenbauer</Authors>
<PackageTags>jwt;json;authorization</PackageTags>
<PackageLicenseExpression>CC0-1.0</PackageLicenseExpression>
<Version>10.0.0-beta5</Version>
<Version>10.0.0-beta6</Version>
<FileVersion>10.0.0.0</FileVersion>
<AssemblyVersion>10.0.0.0</AssemblyVersion>
<RootNamespace>JWT</RootNamespace>
Expand Down

0 comments on commit b387094

Please sign in to comment.