Created
September 6, 2016 19:26
-
-
Save andycmaj/4c1034dc8e2bc99bb939752cad6990fa to your computer and use it in GitHub Desktop.
Working Mono AsymmetricCryptoProvider
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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