Skip to content

Instantly share code, notes, and snippets.

@sunsided
Last active March 29, 2016 04:45
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sunsided/b57cdb487442c68a53cb to your computer and use it in GitHub Desktop.
Save sunsided/b57cdb487442c68a53cb to your computer and use it in GitHub Desktop.
CustomAsymmetricSignatureProvider fails with "keyset does not exist" on Mono
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();
}
}
}
}
}
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;
}
}
}
}
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