Last active
March 29, 2016 04:45
-
-
Save sunsided/b57cdb487442c68a53cb to your computer and use it in GitHub Desktop.
CustomAsymmetricSignatureProvider fails with "keyset does not exist" on Mono
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.IdentityModel.Tokens; | |
using Foobar.Common.Contracts; | |
namespace Foobar.Authentication.Security.SignatureProvider | |
{ | |
/// <summary> | |
/// https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/e6d903b4f8d3c6d7697dc70cd54ae905b18b65fe/src/System.IdentityModel.Tokens.Jwt/AsymmetricSignatureProvider.cs | |
/// </summary> | |
public class CustomAsymmetricSignatureProvider : Microsoft.IdentityModel.Tokens.SignatureProvider | |
{ | |
#if HAS_ECDSA | |
private ECDsaCng _ecdsaCng; | |
#endif | |
private string _hashAlgorithm; | |
private RSACryptoServiceProvider _rsaCryptoServiceProvider; | |
private bool _disposed; | |
private readonly IReadOnlyDictionary<string, int> _minimumAsymmetricKeySizeInBitsForSigningMap; | |
private readonly IReadOnlyDictionary<string, int> _minimumAsymmetricKeySizeInBitsForVerifyingMap; | |
private RSACryptoServiceProviderProxy _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.RSA_SHA256, 2048 }, | |
{ SecurityAlgorithms.RSA_SHA384, 2048 }, | |
{ SecurityAlgorithms.RSA_SHA512, 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.RSA_SHA256, 1024 }, | |
{ SecurityAlgorithms.RSA_SHA384, 1024 }, | |
{ SecurityAlgorithms.RSA_SHA512, 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="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="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"> | |
/// <see cref="AsymmetricSecurityKey"/>.KeySize is less than the size corresponding to the algorithm in <see cref="SignatureProviderFactory.MinimumAsymmetricKeySizeInBitsForVerifyingMap"/>. Note: this is always checked. | |
/// </exception> | |
/// <exception cref="ArgumentException">if 'algorithm" is not supported.</exception> | |
/// <exception cref="ArgumentOutOfRangeException">if 'key' is not <see cref="RsaSecurityKey"/> or <see cref="X509SecurityKey"/>.</exception> | |
public CustomAsymmetricSignatureProvider(AsymmetricSecurityKey key, string algorithm, bool willCreateSignatures = false) | |
: base(key, algorithm) | |
{ | |
Enforce.NotNull(key, nameof(key)); | |
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 => _minimumAsymmetricKeySizeInBitsForSigningMap; | |
/// <summary> | |
/// Gets the mapping from algorithm to the minimum <see cref="AsymmetricSecurityKey"/>.KeySize for verifying signatures. | |
/// </summary> | |
public IReadOnlyDictionary<string, int> MinimumAsymmetricKeySizeInBitsForVerifyingMap => _minimumAsymmetricKeySizeInBitsForVerifyingMap; | |
protected virtual string GetHashAlgorithmString(string algorithm) | |
{ | |
Enforce.NotNullOrWhiteSpace(algorithm, nameof(algorithm)); | |
switch (algorithm) | |
{ | |
case SecurityAlgorithms.SHA256: | |
#if HAS_ECDSA | |
case SecurityAlgorithms.ECDSA_SHA256: | |
#endif | |
case SecurityAlgorithms.RSA_SHA256: | |
case SecurityAlgorithms.RsaSha256Signature: | |
return SecurityAlgorithms.SHA256; | |
case SecurityAlgorithms.SHA384: | |
#if HAS_ECDSA | |
case SecurityAlgorithms.ECDSA_SHA384: | |
#endif | |
case SecurityAlgorithms.RSA_SHA384: | |
case SecurityAlgorithms.RsaSha384Signature: | |
return SecurityAlgorithms.SHA384; | |
case SecurityAlgorithms.SHA512: | |
#if HAS_ECDSA | |
case SecurityAlgorithms.ECDSA_SHA512: | |
#endif | |
case SecurityAlgorithms.RSA_SHA512: | |
case SecurityAlgorithms.RsaSha512Signature: | |
return SecurityAlgorithms.SHA512; | |
} | |
throw new ArgumentOutOfRangeException(nameof(algorithm), algorithm); | |
} | |
private void ResolveDotNetDesktopAsymmetricAlgorithm(AsymmetricSecurityKey key, string algorithm, bool willCreateSignatures) | |
{ | |
_hashAlgorithm = GetHashAlgorithmString(algorithm); | |
RsaSecurityKey rsaKey = key as RsaSecurityKey; | |
if (rsaKey != null) | |
{ | |
_rsaCryptoServiceProvider = new RSACryptoServiceProvider(); | |
((RSA)_rsaCryptoServiceProvider).ImportParameters(rsaKey.Parameters); | |
return; | |
} | |
X509SecurityKey x509Key = key as X509SecurityKey; | |
if (x509Key != null) | |
{ | |
if (willCreateSignatures) | |
{ | |
var csp = x509Key.PrivateKey as RSACryptoServiceProvider; | |
_rsaCryptoServiceProviderProxy = new RSACryptoServiceProviderProxy(csp); | |
} | |
else | |
{ | |
var csp = x509Key.PublicKey.Key as RSACryptoServiceProvider; | |
_rsaCryptoServiceProviderProxy = new RSACryptoServiceProviderProxy(csp); | |
} | |
return; | |
} | |
#if HAS_ECDSA | |
ECDsaSecurityKey ecdsaKey = key as ECDsaSecurityKey; | |
if (ecdsaKey != null) | |
{ | |
_ecdsaCng = new ECDsaCng(ecdsaKey.CngKey); | |
_ecdsaCng.HashAlgorithm = new CngAlgorithm(_hashAlgorithm); | |
return; | |
} | |
#endif | |
throw new ArgumentOutOfRangeException(nameof(key), key, "Unable to create security key."); | |
} | |
public override 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.RSA_SHA256: | |
case SecurityAlgorithms.RSA_SHA384: | |
case SecurityAlgorithms.RSA_SHA512: | |
case SecurityAlgorithms.RsaSha256Signature: | |
case SecurityAlgorithms.RsaSha384Signature: | |
case SecurityAlgorithms.RsaSha512Signature: | |
return true; | |
default: | |
return false; | |
} | |
} | |
/// <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="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="HashAlgorithm"/> is null. This can occur if a derived type deletes it or does not create it.</exception> | |
public override byte[] Sign(byte[] input) | |
{ | |
Enforce.NotNull(input, nameof(input)); | |
Enforce.CountIsAtLeast(input, nameof(input), 1); | |
if (_disposed) | |
throw new ObjectDisposedException(GetType().ToString()); | |
if (_rsaCryptoServiceProvider != null) | |
return _rsaCryptoServiceProvider.SignData(input, _hashAlgorithm); | |
else if (_rsaCryptoServiceProviderProxy != null) | |
return _rsaCryptoServiceProviderProxy.SignData(input, _hashAlgorithm); | |
#if HAS_ECDSA | |
else if (_ecdsaCng != null) | |
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="ArgumentNullException">'input' is null.</exception> | |
/// <exception cref="ArgumentNullException">'signature' is null.</exception> | |
/// <exception cref="ArgumentException">'input.Length' == 0.</exception> | |
/// <exception cref="ArgumentException">'signature.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="HashAlgorithm"/> is null. This can occur if a derived type deletes it or does not create it.</exception> | |
public override bool Verify(byte[] input, byte[] signature) | |
{ | |
Enforce.NotNull(input, nameof(input)); | |
Enforce.NotNull(signature, nameof(signature)); | |
Enforce.CountIsAtLeast(input, nameof(input), 1); | |
Enforce.CountIsAtLeast(signature, nameof(signature), 1); | |
if (_disposed) | |
throw new ObjectDisposedException(GetType().ToString()); | |
if (_hashAlgorithm == null) | |
throw new InvalidOperationException("signature"); | |
if (_rsaCryptoServiceProvider != null) | |
return _rsaCryptoServiceProvider.VerifyData(input, _hashAlgorithm, signature); | |
else if (_rsaCryptoServiceProviderProxy != null) | |
return _rsaCryptoServiceProviderProxy.VerifyData(input, _hashAlgorithm, signature); | |
#if HAS_ECDSA | |
else if (_ecdsaCng != null) | |
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> | |
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) | |
{ | |
_disposed = true; | |
if (disposing) | |
{ | |
if (_rsaCryptoServiceProvider != null) | |
_rsaCryptoServiceProvider.Dispose(); | |
#if HAS_ECDSA | |
if (_ecdsaCng != null) | |
_ecdsaCng.Dispose(); | |
#endif | |
if (_rsaCryptoServiceProviderProxy != null) | |
_rsaCryptoServiceProviderProxy.Dispose(); | |
} | |
} | |
} | |
} | |
} |
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.Security.Cryptography; | |
using Microsoft.IdentityModel.Logging; | |
namespace Foobar.Authentication.Security.SignatureProvider | |
{ | |
/// <summary> | |
/// The purpose of this class is to ensure that we obtain an RsaCryptoServiceProvider that supports SHA-256 signatures. | |
/// If the original RsaCryptoServiceProvider doesn't support SHA-256, we create a new one using the same KeyContainer. | |
/// </summary> | |
public class CustomRsaCryptoServiceProviderProxy : IDisposable | |
{ | |
private const int PROV_RSA_AES = 24; // CryptoApi provider type for an RSA provider supporting sha-256 digital signatures | |
private bool _disposed; | |
// Only dispose of the RsaCryptoServiceProvider object if we created a new instance that supports SHA-256, | |
// otherwise do not disposed of the referenced RsaCryptoServiceProvider | |
private bool _disposeRsa; | |
private RSACryptoServiceProvider _rsa; | |
public CustomRsaCryptoServiceProviderProxy(RSACryptoServiceProvider rsa) | |
{ | |
if (rsa == null) | |
throw LogHelper.LogArgumentNullException("rsa"); | |
// | |
// If the provider does not understand SHA256, | |
// replace it with one that does. | |
// | |
if (rsa.CspKeyContainerInfo.ProviderType != PROV_RSA_AES) | |
{ | |
CspParameters csp = new CspParameters(); | |
csp.ProviderType = PROV_RSA_AES; | |
csp.KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName; | |
csp.KeyNumber = (int)rsa.CspKeyContainerInfo.KeyNumber; | |
if (rsa.CspKeyContainerInfo.MachineKeyStore) | |
{ | |
csp.Flags = CspProviderFlags.UseMachineKeyStore; | |
} | |
// | |
// If UseExistingKey is not specified, the CLR will generate a key for a non-existent group. | |
// With this flag, a CryptographicException is thrown instead. | |
// | |
csp.Flags &= ~CspProviderFlags.UseExistingKey; | |
_rsa = new RSACryptoServiceProvider(csp); | |
// since we created a new RsaCryptoServiceProvider we need to dispose it | |
_disposeRsa = true; | |
} | |
else | |
{ | |
// no work-around necessary | |
_rsa = rsa; | |
} | |
} | |
~CustomRsaCryptoServiceProviderProxy() | |
{ | |
this.Dispose(false); | |
} | |
public void Dispose() | |
{ | |
this.Dispose(true); | |
GC.SuppressFinalize(this); | |
} | |
public byte[] SignData(byte[] signingInput, object hash) | |
{ | |
return _rsa.SignData(signingInput, hash); | |
} | |
public bool VerifyData(byte[] signingInput, object hash, byte[] signature) | |
{ | |
return _rsa.VerifyData(signingInput, hash, signature); | |
} | |
private void Dispose(bool disposing) | |
{ | |
if (!_disposed) | |
{ | |
if (disposing) | |
{ | |
if (_disposeRsa && _rsa != null) | |
{ | |
_rsa.Dispose(); | |
_rsa = null; | |
} | |
} | |
_disposed = true; | |
} | |
} | |
} | |
} |
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 JetBrains.Annotations; | |
using Microsoft.Extensions.Logging; | |
using Microsoft.IdentityModel.Tokens; | |
using Foobar.Common.Contracts; | |
namespace Foobar.Authentication.Security.SignatureProvider | |
{ | |
public class CustomSignatureProviderFactory : SignatureProviderFactory | |
{ | |
[NotNull] | |
private readonly ILogger _log; | |
public CustomSignatureProviderFactory([NotNull] ILogger<CustomSignatureProviderFactory> log) | |
{ | |
Enforce.NotNull(log, nameof(log)); | |
_log = log; | |
} | |
public override Microsoft.IdentityModel.Tokens.SignatureProvider CreateForSigning(SecurityKey key, string algorithm) | |
{ | |
var asymmetricKey = key as AsymmetricSecurityKey; | |
return asymmetricKey == null | |
? base.CreateForSigning(key, algorithm) | |
: new CustomAsymmetricSignatureProvider(asymmetricKey, algorithm, willCreateSignatures: true); | |
} | |
public override Microsoft.IdentityModel.Tokens.SignatureProvider CreateForVerifying(SecurityKey key, string algorithm) | |
{ | |
var asymmetricKey = key as AsymmetricSecurityKey; | |
return asymmetricKey == null | |
? base.CreateForVerifying(key, algorithm) | |
: new CustomAsymmetricSignatureProvider(asymmetricKey, algorithm, willCreateSignatures: false); | |
} | |
public override void ReleaseProvider(Microsoft.IdentityModel.Tokens.SignatureProvider signatureProvider) | |
{ | |
base.ReleaseProvider(signatureProvider); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment