Skip to content

Commit

Permalink
Use modern API to access private keys
Browse files Browse the repository at this point in the history
- Don't do SHA256 support magic on modern providers
- Fixes #1306
- Fixes #1298
  • Loading branch information
AndersAbel committed Apr 19, 2023
1 parent 883d7cd commit 3293061
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 153 deletions.
267 changes: 152 additions & 115 deletions Sustainsys.Saml2/Internal/CryptographyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,88 +12,88 @@

namespace Sustainsys.Saml2.Internal
{
internal static class CryptographyExtensions
{
internal static void Encrypt(this XmlElement elementToEncrypt, EncryptingCredentials encryptingCredentials)
{
if (elementToEncrypt == null)
{
throw new ArgumentNullException(nameof(elementToEncrypt));
}
if (encryptingCredentials == null)
{
throw new ArgumentNullException(nameof(encryptingCredentials));
}

string enc;
int keySize;
switch (encryptingCredentials.Enc)
{
case SecurityAlgorithms.Aes128CbcHmacSha256:
enc = EncryptedXml.XmlEncAES128Url;
keySize = 128;
break;
case SecurityAlgorithms.Aes192CbcHmacSha384:
enc = EncryptedXml.XmlEncAES192Url;
keySize = 192;
break;
case SecurityAlgorithms.Aes256CbcHmacSha512:
enc = EncryptedXml.XmlEncAES256Url;
keySize = 256;
break;
default:
throw new CryptographicException(
$"Unsupported cryptographic algorithm {encryptingCredentials.Enc}");
}

var encryptedData = new EncryptedData
{
Type = EncryptedXml.XmlEncElementUrl,
EncryptionMethod = new EncryptionMethod(enc)
};

string alg;
switch (encryptingCredentials.Alg)
{
case SecurityAlgorithms.RsaOAEP:
alg = EncryptedXml.XmlEncRSAOAEPUrl;
break;
case SecurityAlgorithms.RsaPKCS1:
alg = EncryptedXml.XmlEncRSA15Url;
break;
default:
throw new CryptographicException(
$"Unsupported cryptographic algorithm {encryptingCredentials.Alg}");
}
var encryptedKey = new EncryptedKey
{
EncryptionMethod = new EncryptionMethod(alg),
};

var encryptedXml = new EncryptedXml();
byte[] encryptedElement;
using (var symmetricAlgorithm = new RijndaelManaged())
{
X509SecurityKey x509SecurityKey = encryptingCredentials.Key as X509SecurityKey;
if (x509SecurityKey == null)
{
throw new CryptographicException(
"The encrypting credentials have an unknown key of type {encryptingCredentials.Key.GetType()}");
}
symmetricAlgorithm.KeySize = keySize;
encryptedKey.CipherData = new CipherData(EncryptedXml.EncryptKey(symmetricAlgorithm.Key,
(RSA)x509SecurityKey.PublicKey, alg == EncryptedXml.XmlEncRSAOAEPUrl));
encryptedElement = encryptedXml.EncryptData(elementToEncrypt, symmetricAlgorithm, false);
}
encryptedData.CipherData.CipherValue = encryptedElement;

encryptedData.KeyInfo = new KeyInfo();
encryptedData.KeyInfo.AddClause(new KeyInfoEncryptedKey(encryptedKey));
EncryptedXml.ReplaceElement(elementToEncrypt, encryptedData, false);
}

internal static void Encrypt(this XmlElement elementToEncrypt, bool useOaep, X509Certificate2 certificate)
internal static class CryptographyExtensions
{
internal static void Encrypt(this XmlElement elementToEncrypt, EncryptingCredentials encryptingCredentials)
{
if (elementToEncrypt == null)
{
throw new ArgumentNullException(nameof(elementToEncrypt));
}
if (encryptingCredentials == null)
{
throw new ArgumentNullException(nameof(encryptingCredentials));
}

string enc;
int keySize;
switch (encryptingCredentials.Enc)
{
case SecurityAlgorithms.Aes128CbcHmacSha256:
enc = EncryptedXml.XmlEncAES128Url;
keySize = 128;
break;
case SecurityAlgorithms.Aes192CbcHmacSha384:
enc = EncryptedXml.XmlEncAES192Url;
keySize = 192;
break;
case SecurityAlgorithms.Aes256CbcHmacSha512:
enc = EncryptedXml.XmlEncAES256Url;
keySize = 256;
break;
default:
throw new CryptographicException(
$"Unsupported cryptographic algorithm {encryptingCredentials.Enc}");
}

var encryptedData = new EncryptedData
{
Type = EncryptedXml.XmlEncElementUrl,
EncryptionMethod = new EncryptionMethod(enc)
};

string alg;
switch (encryptingCredentials.Alg)
{
case SecurityAlgorithms.RsaOAEP:
alg = EncryptedXml.XmlEncRSAOAEPUrl;
break;
case SecurityAlgorithms.RsaPKCS1:
alg = EncryptedXml.XmlEncRSA15Url;
break;
default:
throw new CryptographicException(
$"Unsupported cryptographic algorithm {encryptingCredentials.Alg}");
}
var encryptedKey = new EncryptedKey
{
EncryptionMethod = new EncryptionMethod(alg),
};

var encryptedXml = new EncryptedXml();
byte[] encryptedElement;
using (var symmetricAlgorithm = new RijndaelManaged())
{
X509SecurityKey x509SecurityKey = encryptingCredentials.Key as X509SecurityKey;
if (x509SecurityKey == null)
{
throw new CryptographicException(
"The encrypting credentials have an unknown key of type {encryptingCredentials.Key.GetType()}");
}

symmetricAlgorithm.KeySize = keySize;
encryptedKey.CipherData = new CipherData(EncryptedXml.EncryptKey(symmetricAlgorithm.Key,
(RSA)x509SecurityKey.PublicKey, alg == EncryptedXml.XmlEncRSAOAEPUrl));
encryptedElement = encryptedXml.EncryptData(elementToEncrypt, symmetricAlgorithm, false);
}
encryptedData.CipherData.CipherValue = encryptedElement;

encryptedData.KeyInfo = new KeyInfo();
encryptedData.KeyInfo.AddClause(new KeyInfoEncryptedKey(encryptedKey));
EncryptedXml.ReplaceElement(elementToEncrypt, encryptedData, false);
}

internal static void Encrypt(this XmlElement elementToEncrypt, bool useOaep, X509Certificate2 certificate)
{
if (certificate == null) throw new ArgumentNullException(nameof(certificate));

Expand Down Expand Up @@ -143,10 +143,41 @@ internal static XmlElement Decrypt(this XmlElement element, AsymmetricAlgorithm
return xmlDoc.DocumentElement;
}

internal static RSACryptoServiceProvider GetSha256EnabledRSACryptoServiceProvider(
this RSACryptoServiceProvider original)
internal static AsymmetricAlgorithm GetSha256EnabledAsymmetricAlgorithm(this X509Certificate2 x509Certificate2)
{
var ecDsa = x509Certificate2.GetECDsaPrivateKey();

if (ecDsa != null)
{
return ecDsa;
}

var rsa = x509Certificate2.GetRSAPrivateKey();

if (rsa != null)
{
return rsa.GetSha256EnabledRSACryptoServiceProvider();
}

throw new NotImplementedException();
}

internal static RSA GetSha256EnabledRSACryptoServiceProvider(this RSA rsa)
{
// The provider is probably using the default ProviderType. That's
// For .NET Core, everything supports SHA256 out of the box.
if (EnvironmentHelpers.IsNetCore)
{
return rsa;
}

var original = rsa as RSACryptoServiceProvider;
if (original == null)
{
return rsa;
}

// It's a legacy RSACryptoServiceProvider.
// The provider might be using the legacy ProviderType. That's
// a problem, because it doesn't support SHA256. Let's do some
// black magic and create a new provider of a type that supports
// SHA256 without the user ever knowing we fix this. This is what
Expand All @@ -155,15 +186,21 @@ internal static RSACryptoServiceProvider GetSha256EnabledRSACryptoServiceProvide
// a known algorithm, so users kind of expect this to be handled
// for them magically.

var cspParams = new CspParameters();
cspParams.ProviderType = 24; //PROV_RSA_AES
cspParams.KeyContainerName = original.CspKeyContainerInfo.KeyContainerName;
cspParams.KeyNumber = (int)original.CspKeyContainerInfo.KeyNumber;
SetMachineKeyFlag(original, cspParams);
if ((original.CspKeyContainerInfo.ProviderType == 1 || original.CspKeyContainerInfo.ProviderType == 12)
&& !original.CspKeyContainerInfo.HardwareDevice)
{
var cspParams = new CspParameters();
cspParams.ProviderType = 24; //PROV_RSA_AES
cspParams.KeyContainerName = original.CspKeyContainerInfo.KeyContainerName;
cspParams.KeyNumber = (int)original.CspKeyContainerInfo.KeyNumber;
SetMachineKeyFlag(original, cspParams);

cspParams.Flags |= CspProviderFlags.UseExistingKey;

cspParams.Flags |= CspProviderFlags.UseExistingKey;
return new RSACryptoServiceProvider(cspParams);
}

return new RSACryptoServiceProvider(cspParams);
return original;
}

// We don't want to use Machine Key store during tests, so let's
Expand All @@ -177,28 +214,28 @@ private static void SetMachineKeyFlag(RSACryptoServiceProvider provider, CspPara
}
}

// CryptoConfig.CreateFromName doesn't know about these
static Dictionary<string, Type> s_extraAlgorithms = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
{
{ SecurityAlgorithms.RsaSha256Signature, typeof(ManagedRSASHA256SignatureDescription) },
{ SecurityAlgorithms.RsaSha384Signature, typeof(ManagedRSASHA384SignatureDescription) },
{ SecurityAlgorithms.RsaSha512Signature, typeof(ManagedRSASHA512SignatureDescription) }
};

public static object CreateAlgorithmFromName(string name, params object[] args)
{
var result = CryptoConfig.CreateFromName(name);
if (result != null)
{
return result;
}

Type type;
if (!s_extraAlgorithms.TryGetValue(name, out type))
{
throw new CryptographicException($"Unknown crypto algorithm '{name}'");
}
return Activator.CreateInstance(type, args);
}
// CryptoConfig.CreateFromName doesn't know about these
static Dictionary<string, Type> s_extraAlgorithms = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
{
{ SecurityAlgorithms.RsaSha256Signature, typeof(ManagedRSASHA256SignatureDescription) },
{ SecurityAlgorithms.RsaSha384Signature, typeof(ManagedRSASHA384SignatureDescription) },
{ SecurityAlgorithms.RsaSha512Signature, typeof(ManagedRSASHA512SignatureDescription) }
};

public static object CreateAlgorithmFromName(string name, params object[] args)
{
var result = CryptoConfig.CreateFromName(name);
if (result != null)
{
return result;
}

Type type;
if (!s_extraAlgorithms.TryGetValue(name, out type))
{
throw new CryptographicException($"Unknown crypto algorithm '{name}'");
}
return Activator.CreateInstance(type, args);
}
}
}
4 changes: 1 addition & 3 deletions Sustainsys.Saml2/ManagedSha256SignatureDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ public override AsymmetricSignatureFormatter CreateFormatter(AsymmetricAlgorithm
throw new ArgumentNullException(nameof(key));
}

var provider = EnvironmentHelpers.IsNetCore ? key :
((RSACryptoServiceProvider)key)
.GetSha256EnabledRSACryptoServiceProvider();
var provider = ((RSA)key).GetSha256EnabledRSACryptoServiceProvider();

var formatter = new RSAPKCS1SignatureFormatter(provider);
formatter.SetHashAlgorithm(HashAlgorithm);
Expand Down
4 changes: 1 addition & 3 deletions Sustainsys.Saml2/WebSSO/Saml2RedirectBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ private static string AddSignature(string queryString, ISaml2Message message)
hashAlg.ComputeHash(Encoding.UTF8.GetBytes(queryString));
AsymmetricSignatureFormatter asymmetricSignatureFormatter =
signatureDescription.CreateFormatter(
EnvironmentHelpers.IsNetCore ? message.SigningCertificate.PrivateKey :
((RSACryptoServiceProvider)message.SigningCertificate.PrivateKey)
.GetSha256EnabledRSACryptoServiceProvider());
message.SigningCertificate.GetSha256EnabledAsymmetricAlgorithm());
byte[] signatureValue = asymmetricSignatureFormatter.CreateSignature(hashAlg);
queryString += "&Signature=" + Uri.EscapeDataString(Convert.ToBase64String(signatureValue));
return queryString;
Expand Down
16 changes: 7 additions & 9 deletions Sustainsys.Saml2/XmlHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,15 +216,13 @@ public static void Sign(

var signedXml = new SignedXmlWithIdFix(xmlElement.OwnerDocument);

// The transform XmlDsigExcC14NTransform and canonicalization method XmlDsigExcC14NTransformUrl is important for partially signed XML files
// see: http://msdn.microsoft.com/en-us/library/system.security.cryptography.xml.signedxml.xmldsigexcc14ntransformurl(v=vs.110).aspx
// The reference URI has to be set correctly to avoid assertion injections
// For both, the ID/Reference and the Transform/Canonicalization see as well:
// https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf section 5.4.2 and 5.4.3

signedXml.SigningKey = EnvironmentHelpers.IsNetCore ? cert.PrivateKey :
((RSACryptoServiceProvider)cert.PrivateKey)
.GetSha256EnabledRSACryptoServiceProvider();
// The transform XmlDsigExcC14NTransform and canonicalization method XmlDsigExcC14NTransformUrl is important for partially signed XML files
// see: http://msdn.microsoft.com/en-us/library/system.security.cryptography.xml.signedxml.xmldsigexcc14ntransformurl(v=vs.110).aspx
// The reference URI has to be set correctly to avoid assertion injections
// For both, the ID/Reference and the Transform/Canonicalization see as well:
// https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf section 5.4.2 and 5.4.3

signedXml.SigningKey = cert.GetSha256EnabledAsymmetricAlgorithm();
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
signedXml.SignedInfo.SignatureMethod = signingAlgorithm;

Expand Down
Binary file not shown.
3 changes: 3 additions & 0 deletions Tests/TestHelpers/TestHelpers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
</ItemGroup>

<ItemGroup>
<None Update="Sustainsys.Saml2.Tests.CngProvider.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Sustainsys.Saml2.Tests.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
21 changes: 15 additions & 6 deletions Tests/Tests.Shared/XmlHelpersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@ public void XmlHelpers_Sign()
signedXml.CheckSignature(TestCert, true).Should().BeTrue();
}

[TestMethod]
public void XmlHelpers_Sign_WithCngProvider()
{
var xmlDoc = XmlHelpers.XmlDocumentFromString(
"<root ID=\"rootElementId\"><content>Some Content</content></root>");

var cert = new X509Certificate2("Sustainsys.Saml2.Tests.CngProvider.pfx");

// Using a certificate with a modern CNG provider threw exceptions, this
// test validates that it works.
xmlDoc.Sign(cert);
}

const string xmlString = "<xml a=\"b\">\n <indented>content</indented>\n</xml>";
readonly XmlDocument xmlDocument = XmlHelpers.XmlDocumentFromString(xmlString);

Expand Down Expand Up @@ -406,9 +419,7 @@ public void XmlHelpers_IsSignedBy_ChecksSigningAlgorithmStrength()
reference.AddTransform(new XmlDsigExcC14NTransform());
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
sx.AddReference(reference);
sx.SigningKey = EnvironmentHelpers.IsNetCore ? SignedXmlHelper.TestCert.PrivateKey :
((RSACryptoServiceProvider)SignedXmlHelper.TestCert.PrivateKey)
.GetSha256EnabledRSACryptoServiceProvider();
sx.SigningKey = SignedXmlHelper.TestCert.GetSha256EnabledAsymmetricAlgorithm();

sx.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA1Url;
sx.ComputeSignature();
Expand Down Expand Up @@ -438,9 +449,7 @@ public void XmlHelpers_IsSignedBy_ChecksDigestAlgorithmStrength()
reference.AddTransform(new XmlDsigExcC14NTransform());
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
sx.AddReference(reference);
sx.SigningKey = EnvironmentHelpers.IsNetCore ? SignedXmlHelper.TestCert.PrivateKey :
((RSACryptoServiceProvider)SignedXmlHelper.TestCert.PrivateKey)
.GetSha256EnabledRSACryptoServiceProvider();
sx.SigningKey = SignedXmlHelper.TestCert.GetSha256EnabledAsymmetricAlgorithm();

sx.SignedInfo.SignatureMethod = SecurityAlgorithms.RsaSha256Signature;
sx.ComputeSignature();
Expand Down
Loading

0 comments on commit 3293061

Please sign in to comment.