Skip to content

Commit 669273e

Browse files
committed
2 parents 47940e5 + 9df3569 commit 669273e

File tree

6 files changed

+229
-7
lines changed

6 files changed

+229
-7
lines changed

src/ITfoxtec.Identity.Saml2/Configuration/Saml2IdentityConfiguration.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using ITfoxtec.Identity.Saml2.Util;
1111
using Microsoft.IdentityModel.Tokens;
1212
using System.Security.Claims;
13+
using System.Security.Cryptography.X509Certificates;
1314
using System.IdentityModel.Selectors;
1415
#endif
1516

@@ -25,6 +26,8 @@ public class Saml2IdentityConfiguration :
2526

2627
#if !NETFULL
2728
public X509CertificateValidator CertificateValidator { get; set; }
29+
30+
public X509Certificate2 DecryptionCertificate { get; set; }
2831
#endif
2932

3033
public static Saml2IdentityConfiguration GetIdentityConfiguration(Saml2Configuration config)
@@ -72,6 +75,7 @@ public static Saml2IdentityConfiguration GetIdentityConfiguration(Saml2Configura
7275
CertificateValidationMode = config.CertificateValidationMode,
7376
RevocationMode = config.RevocationMode,
7477
};
78+
configuration.DecryptionCertificate = config.DecryptionCertificate;
7579
SetCustomCertificateValidator(configuration, config);
7680
#endif
7781

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#if !NETFULL
2+
using System;
3+
using System.Security.Cryptography;
4+
5+
namespace ITfoxtec.Identity.Saml2.Cryptography
6+
{
7+
/// <summary>
8+
/// SymmetricAlgorithm decrypting implementation for http://www.w3.org/2009/xmlenc11#aes128-gcm and http://www.w3.org/2009/xmlenc11#aes256-gcm.
9+
/// This is class is not a general implementation and can only do decryption.
10+
/// </summary>
11+
public class AesGcmAlgorithm : SymmetricAlgorithm
12+
{
13+
public const string AesGcm128Identifier = "http://www.w3.org/2009/xmlenc11#aes128-gcm";
14+
public const string AesGcm256Identifier = "http://www.w3.org/2009/xmlenc11#aes256-gcm";
15+
16+
// "For the purposes of this specification, AES-GCM shall be used with a 96 bit Initialization Vector (IV) and a 128 bit Authentication Tag (T)."
17+
// Source: https://www.w3.org/TR/xmlenc-core1/#sec-AES-GCM
18+
public const int NonceSizeInBits = 96;
19+
private const int AuthenticationTagSizeInBits = 128;
20+
21+
public AesGcmAlgorithm()
22+
{
23+
LegalKeySizesValue = new[] {
24+
new KeySizes(128, 256, 128),
25+
};
26+
27+
//iv setter checks that iv is the size of a block. Not sure if there should be other block sizes
28+
LegalBlockSizesValue = new[] { new KeySizes(NonceSizeInBits, NonceSizeInBits, 0) };
29+
BlockSizeValue = NonceSizeInBits;
30+
//dummy iv value since it is accessed first in EncryptedXml.DecryptData
31+
IV = new byte[NonceSizeInBits / 8];
32+
}
33+
34+
public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[] rgbIV)
35+
{
36+
return new AesGcmDecryptor(rgbKey, rgbIV, AuthenticationTagSizeInBits);
37+
}
38+
39+
public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[] rgbIV)
40+
{
41+
throw new NotImplementedException();
42+
}
43+
44+
public override void GenerateIV()
45+
{
46+
throw new NotImplementedException();
47+
}
48+
49+
public override void GenerateKey()
50+
{
51+
throw new NotImplementedException();
52+
}
53+
}
54+
}
55+
#endif
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#if !NETFULL
2+
using System;
3+
using System.Security.Cryptography;
4+
5+
namespace ITfoxtec.Identity.Saml2.Cryptography
6+
{
7+
internal class AesGcmDecryptor : ICryptoTransform
8+
{
9+
10+
private readonly byte[] key;
11+
private readonly byte[] nonce;
12+
private readonly int authenticationTagSizeInBits;
13+
14+
public AesGcmDecryptor(byte[] key, byte[] nonce, int authenticationTagSizeInBits)
15+
{
16+
this.key = key;
17+
this.nonce = nonce;
18+
this.authenticationTagSizeInBits = authenticationTagSizeInBits;
19+
}
20+
21+
public bool CanReuseTransform => throw new NotImplementedException();
22+
23+
public bool CanTransformMultipleBlocks => throw new NotImplementedException();
24+
25+
public int InputBlockSize => throw new NotImplementedException();
26+
27+
public int OutputBlockSize => throw new NotImplementedException();
28+
29+
public void Dispose()
30+
{
31+
}
32+
33+
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
34+
{
35+
throw new NotImplementedException();
36+
}
37+
38+
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
39+
{
40+
//inspired by https://stackoverflow.com/a/60891115
41+
42+
var tagSize = authenticationTagSizeInBits / 8;
43+
var cipherSize = inputCount - tagSize;
44+
45+
// "The cipher text contains the IV first, followed by the encrypted octets and finally the Authentication tag."
46+
// https://www.w3.org/TR/xmlenc-core1/#sec-AES-GCM
47+
var encryptedData = inputBuffer.AsSpan().Slice(inputOffset, inputCount);
48+
var tag = encryptedData.Slice(encryptedData.Length - tagSize);
49+
50+
var cipherBytes = encryptedData.Slice(0, cipherSize);
51+
52+
var plainBytes = cipherSize < 1024
53+
? stackalloc byte[cipherSize]
54+
: new byte[cipherSize];
55+
56+
using var aesgcm = new AesGcm(key);
57+
aesgcm.Decrypt(nonce, cipherBytes, tag, plainBytes);
58+
59+
return plainBytes.ToArray();
60+
}
61+
}
62+
}
63+
#endif

src/ITfoxtec.Identity.Saml2/Cryptography/Saml2EncryptedXml.cs

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System;
1+
using ITfoxtec.Identity.Saml2.Schemas;
2+
using System;
23
using System.Security.Cryptography;
34
using System.Security.Cryptography.Xml;
45
using System.Xml;
@@ -7,9 +8,20 @@ namespace ITfoxtec.Identity.Saml2.Cryptography
78
{
89
public class Saml2EncryptedXml : EncryptedXml
910
{
11+
public const string XmlEncKeyAlgorithmRSAOAEPUrl = "http://www.w3.org/2009/xmlenc11#rsa-oaep";
12+
1013
public RSA EncryptionPublicKey { get; set; }
1114
public RSA EncryptionPrivateKey { get; set; }
1215

16+
#if !NETFULL
17+
static Saml2EncryptedXml()
18+
{
19+
// Register AES-GCM wrapper on .NET Core targets where AES-GCM algorithm is available
20+
CryptoConfig.AddAlgorithm(typeof(AesGcmAlgorithm), AesGcmAlgorithm.AesGcm256Identifier);
21+
CryptoConfig.AddAlgorithm(typeof(AesGcmAlgorithm), AesGcmAlgorithm.AesGcm128Identifier);
22+
}
23+
#endif
24+
1325
public Saml2EncryptedXml(RSA encryptionPublicKey) : base()
1426
{
1527
EncryptionPublicKey = encryptionPublicKey;
@@ -29,20 +41,20 @@ public Saml2EncryptedXml(XmlDocument document, RSA encryptionPrivateKey) : this(
2941

3042
public virtual XmlElement EncryptAassertion(XmlElement assertionElement)
3143
{
32-
using (var encryptionAlgorithm = new AesCryptoServiceProvider())
44+
using (var encryptionAlgorithm = Aes.Create())
3345
{
3446
encryptionAlgorithm.KeySize = 256;
3547

3648
var encryptedData = new EncryptedData
3749
{
38-
Type = EncryptedXml.XmlEncElementUrl,
39-
EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES256Url),
50+
Type = XmlEncElementUrl,
51+
EncryptionMethod = new EncryptionMethod(XmlEncAES256Url),
4052
KeyInfo = new KeyInfo()
4153
};
4254
encryptedData.KeyInfo.AddClause(new KeyInfoEncryptedKey(new EncryptedKey
4355
{
44-
EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncRSAOAEPUrl),
45-
CipherData = new CipherData(EncryptedXml.EncryptKey(encryptionAlgorithm.Key, EncryptionPublicKey, true))
56+
EncryptionMethod = new EncryptionMethod(XmlEncRSAOAEPUrl),
57+
CipherData = new CipherData(EncryptKey(encryptionAlgorithm.Key, EncryptionPublicKey, true))
4658
}));
4759

4860
var encryptedXml = new EncryptedXml();
@@ -52,9 +64,64 @@ public virtual XmlElement EncryptAassertion(XmlElement assertionElement)
5264
}
5365
}
5466

67+
public override byte[] GetDecryptionIV(EncryptedData encryptedData, string symmetricAlgorithmUri)
68+
{
69+
if (encryptedData is null)
70+
{
71+
throw new ArgumentNullException(nameof(encryptedData));
72+
}
73+
74+
#if !NETFULL
75+
76+
var aesGcmSymmetricAlgorithmUri = symmetricAlgorithmUri ?? encryptedData.EncryptionMethod?.KeyAlgorithm;
77+
if (aesGcmSymmetricAlgorithmUri == AesGcmAlgorithm.AesGcm128Identifier || aesGcmSymmetricAlgorithmUri == AesGcmAlgorithm.AesGcm256Identifier)
78+
{
79+
int initBytesSize = 12;
80+
byte[] iv = new byte[initBytesSize];
81+
Buffer.BlockCopy(encryptedData.CipherData.CipherValue, 0, iv, 0, iv.Length);
82+
return iv;
83+
}
84+
#endif
85+
86+
return base.GetDecryptionIV(encryptedData, symmetricAlgorithmUri);
87+
}
88+
5589
public override byte[] DecryptEncryptedKey(EncryptedKey encryptedKey)
5690
{
57-
return DecryptKey(encryptedKey.CipherData.CipherValue, EncryptionPrivateKey, (encryptedKey.EncryptionMethod != null) && (encryptedKey.EncryptionMethod.KeyAlgorithm == XmlEncRSAOAEPUrl));
91+
if (encryptedKey.EncryptionMethod.KeyAlgorithm == XmlEncKeyAlgorithmRSAOAEPUrl)
92+
{
93+
return EncryptionPrivateKey.Decrypt(encryptedKey.CipherData.CipherValue, GetEncryptionPadding(encryptedKey));
94+
}
95+
else
96+
{
97+
return DecryptKey(encryptedKey.CipherData.CipherValue, EncryptionPrivateKey, (encryptedKey.EncryptionMethod != null) && (encryptedKey.EncryptionMethod.KeyAlgorithm == XmlEncRSAOAEPUrl));
98+
}
99+
}
100+
101+
private static RSAEncryptionPadding GetEncryptionPadding(EncryptedKey encryptedKey)
102+
{
103+
var xmlElement = encryptedKey.GetXml();
104+
var nsm = new XmlNamespaceManager(xmlElement.OwnerDocument.NameTable);
105+
nsm.AddNamespace("enc", XmlEncNamespaceUrl);
106+
nsm.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl);
107+
var digestMethodElement = xmlElement.SelectSingleNode("enc:EncryptionMethod/ds:DigestMethod", nsm) as XmlElement;
108+
if (digestMethodElement != null)
109+
{
110+
var method = digestMethodElement.GetAttribute("Algorithm");
111+
switch (method)
112+
{
113+
case Saml2SecurityAlgorithms.Sha1Digest:
114+
return RSAEncryptionPadding.OaepSHA1;
115+
case Saml2SecurityAlgorithms.Sha256Digest:
116+
return RSAEncryptionPadding.OaepSHA256;
117+
case Saml2SecurityAlgorithms.Sha384Digest:
118+
return RSAEncryptionPadding.OaepSHA384;
119+
case Saml2SecurityAlgorithms.Sha512Digest:
120+
return RSAEncryptionPadding.OaepSHA512;
121+
}
122+
}
123+
124+
return RSAEncryptionPadding.OaepSHA256;
58125
}
59126
}
60127
}

src/ITfoxtec.Identity.Saml2/Tokens/Saml2ResponseSecurityTokenHandler.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ public static Saml2ResponseSecurityTokenHandler GetSaml2SecurityTokenHandler(Sam
4646
handler.SamlSecurityTokenRequirement.NameClaimType = ClaimTypes.NameIdentifier;
4747
#else
4848
handler.TokenValidationParameters = configuration;
49+
if (configuration.DecryptionCertificate != null)
50+
{
51+
handler.Serializer = new Saml2TokenSerializer(configuration.DecryptionCertificate);
52+
}
4953
#endif
5054
return handler;
5155
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#if !NETFULL
2+
using ITfoxtec.Identity.Saml2.Cryptography;
3+
using Microsoft.IdentityModel.Tokens.Saml2;
4+
using System.Security.Cryptography.X509Certificates;
5+
using System.Xml;
6+
7+
namespace ITfoxtec.Identity.Saml2.Tokens
8+
{
9+
internal class Saml2TokenSerializer : Saml2Serializer
10+
{
11+
private readonly X509Certificate2 decryptionCertificate;
12+
13+
public Saml2TokenSerializer(X509Certificate2 decryptionCertificate) : base()
14+
{
15+
this.decryptionCertificate = decryptionCertificate;
16+
}
17+
18+
protected override Saml2NameIdentifier ReadEncryptedId(XmlDictionaryReader reader)
19+
{
20+
var xmlDocument = reader.ReadOuterXml().ToXmlDocument();
21+
22+
new Saml2EncryptedXml(xmlDocument, decryptionCertificate.GetSamlRSAPrivateKey()).DecryptDocument();
23+
24+
var decryptedReader = XmlDictionaryReader.CreateDictionaryReader(new XmlNodeReader(xmlDocument.DocumentElement.FirstChild));
25+
return ReadNameIdentifier(decryptedReader, null);
26+
}
27+
}
28+
}
29+
#endif

0 commit comments

Comments
 (0)