Skip to content

Instantly share code, notes, and snippets.

@andycmaj
Created September 6, 2016 19:26
Show Gist options
  • Save andycmaj/4c1034dc8e2bc99bb939752cad6990fa to your computer and use it in GitHub Desktop.
Save andycmaj/4c1034dc8e2bc99bb939752cad6990fa to your computer and use it in GitHub Desktop.
Working Mono AsymmetricCryptoProvider
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
namespace TheChunnel.FrontEnd
{
/// <summary>
/// https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/e6d903b4f8d3c6d7697dc70cd54ae905b18b65fe/src/System.IdentityModel.Tokens.Jwt/AsymmetricSignatureProvider.cs
/// </summary>
public class CustomAsymmetricSignatureProvider : SignatureProvider
{
private readonly ILogger<CustomAsymmetricSignatureProvider> _log;
#if HAS_ECDSA
private ECDsaCng _ecdsaCng;
#endif
private string _hashAlgorithm;
private RSACryptoServiceProvider _rsaCryptoServiceProvider;
private bool _shouldDisposeProvider;
private bool _disposed;
private CustomRsaCryptoServiceProviderProxy _rsaCryptoServiceProviderProxy;
/// <summary>
/// Mapping from algorithm to minimum <see cref="AsymmetricSecurityKey"/>.KeySize when creating signatures.
/// </summary>
public static readonly Dictionary<string, int> DefaultMinimumAsymmetricKeySizeInBitsForSigningMap = new Dictionary<string, int>()
{
#if HAS_ECDSA
{ SecurityAlgorithms.ECDSA_SHA256, 256 },
{ SecurityAlgorithms.ECDSA_SHA384, 256 },
{ SecurityAlgorithms.ECDSA_SHA512, 256 },
#endif
{ SecurityAlgorithms.RsaSha256, 2048 },
{ SecurityAlgorithms.RsaSha384, 2048 },
{ SecurityAlgorithms.RsaSha512, 2048 },
{ SecurityAlgorithms.RsaSha256Signature, 2048 },
{ SecurityAlgorithms.RsaSha384Signature, 2048 },
{ SecurityAlgorithms.RsaSha512Signature, 2048 }
};
/// <summary>
/// Mapping from algorithm to minimum <see cref="AsymmetricSecurityKey"/>.KeySize when verifying signatures.
/// </summary>
public static readonly Dictionary<string, int> DefaultMinimumAsymmetricKeySizeInBitsForVerifyingMap = new Dictionary<string, int>()
{
#if HAS_ECDSA
{ SecurityAlgorithms.ECDSA_SHA256, 256 },
{ SecurityAlgorithms.ECDSA_SHA384, 256 },
{ SecurityAlgorithms.ECDSA_SHA512, 256 },
#endif
{ SecurityAlgorithms.RsaSha256, 1024 },
{ SecurityAlgorithms.RsaSha384, 1024 },
{ SecurityAlgorithms.RsaSha512, 1024 },
{ SecurityAlgorithms.RsaSha256Signature, 1024 },
{ SecurityAlgorithms.RsaSha384Signature, 1024 },
{ SecurityAlgorithms.RsaSha512Signature, 1024 }
};
/// <summary>
/// Initializes a new instance of the <see cref="AsymmetricSignatureProvider" /> class used to create and verify signatures.
/// </summary>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="key">The <see cref="AsymmetricSecurityKey" /> that will be used for cryptographic operations.</param>
/// <param name="algorithm">The signature algorithm to apply.</param>
/// <param name="willCreateSignatures">If this <see cref="AsymmetricSignatureProvider" /> is required to create signatures then set this to true.
/// <para>
/// Creating signatures requires that the <see cref="AsymmetricSecurityKey" /> has access to a private key.
/// Verifying signatures (the default), does not require access to the private key.
/// </para></param>
/// <exception cref="System.ArgumentException">Key signing is requested but no private key was set</exception>
/// <exception cref="ArgumentNullException">'key' is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">willCreateSignatures is true and <see cref="AsymmetricSecurityKey" />.KeySize is less than the size corresponding to the given algorithm in <see cref="SignatureProviderFactory.MinimumAsymmetricKeySizeInBitsForSigningMap" />.</exception>
/// <exception cref="ArgumentOutOfRangeException">willCreateSignatures is true and <see cref="AsymmetricSecurityKey" />.KeySize is less than the size corresponding to the given algorithm in <see cref="SignatureProviderFactory.MinimumAsymmetricKeySizeInBitsForSigningMap" />.</exception>
/// <exception cref="ArgumentException">if 'algorithm" is not supported.</exception>
/// <exception cref="ArgumentOutOfRangeException">willCreateSignatures is true and <see cref="AsymmetricSecurityKey" />.KeySize is less than the size corresponding to the given algorithm in <see cref="SignatureProviderFactory.MinimumAsymmetricKeySizeInBitsForSigningMap" />.</exception>
public CustomAsymmetricSignatureProvider(ILoggerFactory loggerFactory, AsymmetricSecurityKey key, string algorithm, bool willCreateSignatures = false)
: base(key, algorithm)
{
_log = loggerFactory.CreateLogger<CustomAsymmetricSignatureProvider>();
if (!IsSupportedAlgorithm(algorithm))
{
throw new ArgumentException("Key algorithm is not supported");
}
MinimumAsymmetricKeySizeInBitsForSigningMap = new Dictionary<string, int>(DefaultMinimumAsymmetricKeySizeInBitsForSigningMap);
MinimumAsymmetricKeySizeInBitsForVerifyingMap = new Dictionary<string, int>(DefaultMinimumAsymmetricKeySizeInBitsForVerifyingMap);
ValidateAsymmetricSecurityKeySize(key, algorithm, willCreateSignatures);
if (willCreateSignatures && !key.HasPrivateKey)
{
throw new ArgumentException("Key signing is requested but no private key was set");
}
ResolveDotNetDesktopAsymmetricAlgorithm(key, algorithm, willCreateSignatures);
}
/// <summary>
/// Gets the mapping from algorithm to the minimum <see cref="AsymmetricSecurityKey"/>.KeySize for creating signatures.
/// </summary>
public IReadOnlyDictionary<string, int> MinimumAsymmetricKeySizeInBitsForSigningMap { get; }
/// <summary>
/// Gets the mapping from algorithm to the minimum <see cref="AsymmetricSecurityKey"/>.KeySize for verifying signatures.
/// </summary>
public IReadOnlyDictionary<string, int> MinimumAsymmetricKeySizeInBitsForVerifyingMap { get; }
/// <summary>
/// Produces a signature over the 'input' using the <see cref="AsymmetricSecurityKey" /> and algorithm passed to <see cref="AsymmetricSignatureProvider( AsymmetricSecurityKey, string, bool )" />.
/// </summary>
/// <param name="input">bytes to be signed.</param>
/// <returns>
/// a signature over the input.
/// </returns>
/// <exception cref="System.ObjectDisposedException"></exception>
/// <exception cref="System.InvalidOperationException">Unable to sign input</exception>
/// <exception cref="ArgumentNullException">'input' is null.</exception>
/// <exception cref="ArgumentException">'input.Length' == 0.</exception>
/// <exception cref="ObjectDisposedException">if <see cref="AsymmetricSignatureProvider.Dispose(bool)" /> has been called.</exception>
/// <exception cref="InvalidOperationException">if the internal <see cref="AsymmetricSignatureFormatter" /> is null. This can occur if the constructor parameter 'willBeUsedforSigning' was not 'true'.</exception>
/// <exception cref="InvalidOperationException">if the internal <see cref="AsymmetricSignatureFormatter" /> is null. This can occur if the constructor parameter 'willBeUsedforSigning' was not 'true'.</exception>
public override byte[] Sign(byte[] input)
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().ToString());
}
if (_rsaCryptoServiceProvider != null)
{
_log.LogDebug(0, "Using the RSA crypto service provider to sign the data.");
return _rsaCryptoServiceProvider.SignData(input, _hashAlgorithm);
}
if (_rsaCryptoServiceProviderProxy != null)
{
_log.LogDebug(0, "Using the RSA crypto service provider proxy to sign the data.");
return _rsaCryptoServiceProviderProxy.SignData(input, _hashAlgorithm);
}
#if HAS_ECDSA
if (_ecdsaCng != null)
{
_log.LogDebug(LogEvents.DataSigning, "Using the ECDSA CNG to sign the data.");
return _ecdsaCng.SignData(input);
}
#endif
throw new InvalidOperationException("Unable to sign input");
}
/// <summary>
/// Verifies that a signature over the' input' matches the signature.
/// </summary>
/// <param name="input">the bytes to generate the signature over.</param>
/// <param name="signature">the value to verify against.</param>
/// <returns>
/// true if signature matches, false otherwise.
/// </returns>
/// <exception cref="System.ObjectDisposedException"></exception>
/// <exception cref="System.InvalidOperationException">signature
/// or
/// Unable to verify signature</exception>
/// <exception cref="ArgumentNullException">'input' is null.</exception>
/// <exception cref="ArgumentNullException">'input' is null.</exception>
/// <exception cref="ArgumentException">'input.Length' == 0.</exception>
/// <exception cref="ArgumentException">'input.Length' == 0.</exception>
/// <exception cref="ObjectDisposedException">if <see cref="AsymmetricSignatureProvider.Dispose(bool)" /> has been called.</exception>
/// <exception cref="InvalidOperationException">if the internal <see cref="AsymmetricSignatureDeformatter" /> is null. This can occur if a derived type does not call the base constructor.</exception>
/// <exception cref="InvalidOperationException">if the internal <see cref="AsymmetricSignatureDeformatter" /> is null. This can occur if a derived type does not call the base constructor.</exception>
public override bool Verify(byte[] input, byte[] signature)
{
if (_disposed)
throw new ObjectDisposedException(GetType().ToString());
if (_hashAlgorithm == null)
throw new InvalidOperationException("signature");
if (_rsaCryptoServiceProvider != null)
{
_log.LogDebug(0, "Using the RSA crypto service provider to validate the signature.");
return _rsaCryptoServiceProvider.VerifyData(input, _hashAlgorithm, signature);
}
if (_rsaCryptoServiceProviderProxy != null)
{
_log.LogDebug(0, "Using the RSA crypto service provider proxy to validate the signature.");
return _rsaCryptoServiceProviderProxy.VerifyData(input, _hashAlgorithm, signature);
}
#if HAS_ECDSA
if (_ecdsaCng != null)
{
_log.LogDebug(0, "Using the ECDSA CNG to validate the signature.");
return _ecdsaCng.VerifyData(input, signature);
}
#endif
throw new InvalidOperationException("Unable to verify signature");
}
/// <summary>
/// Validates that the asymmetric key size is more than the allowed minimum
/// </summary>
/// <param name="key">asymmetric key to validate</param>
/// <param name="algorithm">algorithm for which this key will be used</param>
/// <param name="willCreateSignatures">whether they key will be used for creating signatures</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// key.KeySize;Key size too small for signing
/// or
/// key.KeySize;Key size too small for validation
/// </exception>
public void ValidateAsymmetricSecurityKeySize(SecurityKey key, string algorithm, bool willCreateSignatures)
{
if (willCreateSignatures)
{
if (MinimumAsymmetricKeySizeInBitsForSigningMap.ContainsKey(algorithm) && key.KeySize < MinimumAsymmetricKeySizeInBitsForSigningMap[algorithm])
throw new ArgumentOutOfRangeException("key.KeySize", key.KeySize, "Key size too small for signing");
}
if (MinimumAsymmetricKeySizeInBitsForVerifyingMap.ContainsKey(algorithm) && key.KeySize < MinimumAsymmetricKeySizeInBitsForVerifyingMap[algorithm])
throw new ArgumentOutOfRangeException("key.KeySize", key.KeySize, "Key size too small for validation");
}
/// <summary>
/// Calls <see cref="HashAlgorithm.Dispose()" /> to release this managed resources.
/// </summary>
/// <param name="disposing">true, if called from Dispose(), false, if invoked inside a finalizer.</param>
protected override void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
_disposed = true;
if (disposing && _shouldDisposeProvider)
{
_rsaCryptoServiceProvider?.Dispose();
#if HAS_ECDSA
_ecdsaCng?.Dispose();
#endif
_rsaCryptoServiceProviderProxy?.Dispose();
}
}
/// <summary>
/// Gets the hash algorithm string.
/// </summary>
/// <param name="algorithm">The algorithm.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
protected virtual string GetHashAlgorithmString(string algorithm)
{
switch (algorithm)
{
case SecurityAlgorithms.Sha256:
#if HAS_ECDSA
case SecurityAlgorithms.ECDSA_SHA256:
#endif
case SecurityAlgorithms.RsaSha256:
case SecurityAlgorithms.RsaSha256Signature:
return SecurityAlgorithms.Sha256;
case SecurityAlgorithms.Sha384:
#if HAS_ECDSA
case SecurityAlgorithms.ECDSA_SHA384:
#endif
case SecurityAlgorithms.RsaSha384:
case SecurityAlgorithms.RsaSha384Signature:
return SecurityAlgorithms.Sha384;
case SecurityAlgorithms.Sha512:
#if HAS_ECDSA
case SecurityAlgorithms.ECDSA_SHA512:
#endif
case SecurityAlgorithms.RsaSha512:
case SecurityAlgorithms.RsaSha512Signature:
return SecurityAlgorithms.Sha512;
}
throw new ArgumentOutOfRangeException(nameof(algorithm), algorithm);
}
/// <summary>
/// Resolves the .NET Desktop asymmetric algorithm.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="algorithm">The algorithm.</param>
/// <param name="willCreateSignatures">if set to <c>true</c> [will create signatures].</param>
/// <exception cref="System.ArgumentOutOfRangeException">Unable to create security key.</exception>
private void ResolveDotNetDesktopAsymmetricAlgorithm(AsymmetricSecurityKey key, string algorithm, bool willCreateSignatures)
{
_hashAlgorithm = GetHashAlgorithmString(algorithm);
var rsaKey = key as RsaSecurityKey;
if (rsaKey != null)
{
_log.LogDebug(0, "Using a direct RSA security key.");
_rsaCryptoServiceProvider = new RSACryptoServiceProvider();
((RSA)_rsaCryptoServiceProvider).ImportParameters(rsaKey.Parameters);
_shouldDisposeProvider = true;
return;
}
var x509Key = key as X509SecurityKey;
if (x509Key != null)
{
RSACryptoServiceProvider csp;
if (willCreateSignatures)
{
csp = x509Key.PrivateKey as RSACryptoServiceProvider;
}
else
{
csp = x509Key.PublicKey as RSACryptoServiceProvider;
}
#if NETCORE50 || DNXCORE50
_rsaCryptoServiceProvider = csp;
#else
if (Type.GetType("Mono.Runtime") != null)
{
_log.LogDebug(0, "Detected mono, creating direct RSA crypto service provider.");
_rsaCryptoServiceProvider = csp;
}
else
{
_log.LogDebug(0, "Detected non-mono, creating direct RSA crypto service provider proxy.");
_rsaCryptoServiceProviderProxy = new CustomRsaCryptoServiceProviderProxy(csp);
}
#endif
return;
}
#if HAS_ECDSA
ECDsaSecurityKey ecdsaKey = key as ECDsaSecurityKey;
if (ecdsaKey != null)
{
_log.LogDebug(LogEvents.ProviderSelection, "Using an ECDSA CNG security key.");
_ecdsaCng = new ECDsaCng(ecdsaKey.CngKey);
_ecdsaCng.HashAlgorithm = new CngAlgorithm(_hashAlgorithm);
return;
}
#endif
throw new ArgumentOutOfRangeException(nameof(key), key, "Unable to create security key.");
}
/// <summary>
/// Answers if an algorithm is supported
/// </summary>
/// <param name="algorithm">the algorithm to use</param>
/// <returns></returns>
private bool IsSupportedAlgorithm(string algorithm)
{
if (string.IsNullOrEmpty(algorithm))
{
return false;
}
switch (algorithm)
{
case SecurityAlgorithms.Sha256:
case SecurityAlgorithms.Sha384:
case SecurityAlgorithms.Sha512:
#if HAS_ECDSA
case SecurityAlgorithms.ECDSA_SHA256:
case SecurityAlgorithms.ECDSA_SHA384:
case SecurityAlgorithms.ECDSA_SHA512:
#endif
case SecurityAlgorithms.RsaSha256:
case SecurityAlgorithms.RsaSha384:
case SecurityAlgorithms.RsaSha512:
case SecurityAlgorithms.RsaSha256Signature:
case SecurityAlgorithms.RsaSha384Signature:
case SecurityAlgorithms.RsaSha512Signature:
return true;
default:
return false;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment