Skip to content

Commit ab37a86

Browse files
GeoKbrentschmaltz
authored andcommitted
Support WriteToken - EncryptedAssertion (keywrap)
* Cover assertion encryption with 'one time' session key which is wrapped using an asymmetric key.
1 parent 0b9ec65 commit ab37a86

File tree

10 files changed

+204
-40
lines changed

10 files changed

+204
-40
lines changed

src/Microsoft.IdentityModel.Tokens.Saml/Saml2/LogMessages.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,13 @@ internal static class LogMessages
100100

101101
// Saml2 EncryptedAssertion
102102
internal const string IDX13600 = "IDX13600: Unable to obtain a CryptoProviderFactory, EncryptingCredentials.CryptoProviderFactory and EncryptingCredentials.Key.CrypoProviderFactory are both null.";
103-
internal const string IDX13601 = "IDX13601: SAML2 Encryption failed. No support for: Encryption algorithm: '{0}', SecurityKey: '{1}'.";
103+
internal const string IDX13601 = "IDX13601: SAML2 Encryption failed. No support for algorithm: '{0}', SecurityKey: '{1}'.";
104104
internal const string IDX13602 = "IDX13602: Failed to create the token encryption provider.";
105105
internal const string IDX13603 = "IDX13603: Encryption failed. EncryptionProvider failed for: Algorithm: '{0}', SecurityKey: '{1}'. See inner exception.";
106-
internal const string IDX13604 = "IDX13604: Exception thrown while writing EncrytedData element. See inner exception.";
106+
internal const string IDX13604 = "IDX13604: Exception thrown while writing EncryptedData element. See inner exception.";
107+
internal const string IDX13605 = "IDX13605: Exception thrown while writing EncryptedKey element. See inner exception.";
108+
internal const string IDX13606 = "IDX13606: EncryptionCredentials.Key is not SymmetricSecurityKey or AsymmetricSecurityKey. EncryptionCredentials.Key: '{0}'.";
109+
internal const string IDX13607 = "IDX13607: Session key encryption algorithm: '{0}' is not supported.";
107110

108111
// IDX11900 - AuthorizationDecisionStatement
109112
internal const string IDX13900 = "IDX13900: Unable to write Saml2Assertion: {0} is required, {1} is null or empty.";

src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2Serializer.cs

Lines changed: 116 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,58 +1634,143 @@ private void WritePlaintextAssertion(XmlWriter writer, Saml2Assertion assertion)
16341634
private void EncryptAndWriteAssertionData(XmlWriter writer, byte[] assertionData, Saml2Assertion assertion)
16351635
{
16361636
EncryptingCredentials encryptingCredentials = assertion.EncryptingCredentials;
1637-
var cryptoProviderFactory = encryptingCredentials.CryptoProviderFactory ?? encryptingCredentials.Key.CryptoProviderFactory;
1637+
EncryptedData encryptedData = null;
1638+
EncryptedKey encryptedKey = null;
16381639

1639-
if (cryptoProviderFactory == null)
1640-
throw LogExceptionMessage(new ArgumentException(LogMessages.IDX13600));
1640+
// SymmetricSecurityKey is provided:
1641+
// Pre-shared symmetric key (session key) is used to encrypt an assertion
1642+
// Session key will not be serialized
1643+
if (encryptingCredentials.Key is SymmetricSecurityKey)
1644+
{
1645+
encryptedData = CreateEncryptedData((SymmetricSecurityKey)encryptingCredentials.Key, encryptingCredentials.Enc, assertionData, encryptingCredentials);
1646+
encryptedData.KeyInfo.KeyName = encryptingCredentials.Key.KeyId;
1647+
}
1648+
// AsymmetricSecurityKey is provided:
1649+
// New session key will be created to encrypt an assertion
1650+
// Session key will be wrapped with provided AsymmetricSecurityKey
1651+
else if (encryptingCredentials.Key is AsymmetricSecurityKey)
1652+
{
1653+
SymmetricSecurityKey sessionKey = CreateSessionKey(encryptingCredentials.Enc);
1654+
encryptedData = CreateEncryptedData(sessionKey, encryptingCredentials.Enc, assertionData, encryptingCredentials);
1655+
encryptedData.KeyInfo.RetrievalMethodUri = assertion.EncryptingCredentials.Key.KeyId;
16411656

1642-
if (!cryptoProviderFactory.IsSupportedAlgorithm(encryptingCredentials.Enc, encryptingCredentials.Key))
1643-
throw LogExceptionMessage(new Saml2SecurityTokenEncryptedAssertionEncryptionException(FormatInvariant(LogMessages.IDX13601, encryptingCredentials.Enc, encryptingCredentials.Key)));
1657+
encryptedKey = CreateEncryptedKey((AsymmetricSecurityKey)encryptingCredentials.Key, sessionKey, encryptingCredentials.Alg, encryptingCredentials);
1658+
encryptedKey.AddReference(new DataReference(encryptedData.Id));
1659+
}
1660+
else
1661+
{
1662+
throw LogExceptionMessage(new Saml2SecurityTokenEncryptedAssertionEncryptionException(FormatInvariant(LogMessages.IDX13606, encryptingCredentials.Key)));
1663+
}
16441664

1645-
//pre-shared symmetric key (session key)
1646-
if (encryptingCredentials.Key is SymmetricSecurityKey)
1665+
// write to XML
1666+
try
16471667
{
1648-
AuthenticatedEncryptionResult authenticatedEncryptionResult = null;
1649-
var authenticatedEncryptionProvider = cryptoProviderFactory.CreateAuthenticatedEncryptionProvider(encryptingCredentials.Key, encryptingCredentials.Enc);
1650-
if (authenticatedEncryptionProvider == null)
1651-
throw LogExceptionMessage(new Saml2SecurityTokenEncryptedAssertionEncryptionException(LogMessages.IDX13602));
1668+
encryptedData.WriteXml(writer);
16521669

1670+
}
1671+
catch (Exception ex)
1672+
{
1673+
throw LogExceptionMessage(new Saml2SecurityTokenEncryptedAssertionEncryptionException(FormatInvariant(LogMessages.IDX13604), ex));
1674+
}
1675+
1676+
if (encryptedKey != null)
1677+
{
16531678
try
16541679
{
1655-
authenticatedEncryptionResult = authenticatedEncryptionProvider.Encrypt(assertionData);
1680+
encryptedKey.WriteXml(writer);
1681+
16561682
}
16571683
catch (Exception ex)
16581684
{
1659-
throw LogExceptionMessage(new Saml2SecurityTokenEncryptedAssertionEncryptionException(FormatInvariant(LogMessages.IDX13603, encryptingCredentials.Enc, encryptingCredentials.Key), ex));
1685+
throw LogExceptionMessage(new Saml2SecurityTokenEncryptedAssertionEncryptionException(FormatInvariant(LogMessages.IDX13605), ex));
16601686
}
1687+
}
16611688

1662-
EncryptedData encryptedData = new EncryptedData();
1663-
encryptedData.CipherData.CipherValue = Utility.ConcatByteArrays(authenticatedEncryptionResult.IV, authenticatedEncryptionResult.Ciphertext, authenticatedEncryptionResult.AuthenticationTag);
1664-
encryptedData.EncryptionMethod = new EncryptionMethod(encryptingCredentials.Enc);
1665-
encryptedData.Type = XmlEncryptionConstants.EncryptedDataTypes.Element;
1666-
encryptedData.KeyInfo.KeyName = encryptingCredentials.Key.KeyId;
1689+
}
16671690

1668-
try
1669-
{
1670-
encryptedData.WriteXml(writer);
1691+
private CryptoProviderFactory GetCryptoProviderFactory(SecurityKey key, string algorithm, EncryptingCredentials encryptingCredentials)
1692+
{
1693+
var cryptoProviderFactory = encryptingCredentials.CryptoProviderFactory ?? encryptingCredentials.Key.CryptoProviderFactory;
16711694

1672-
}
1673-
catch (Exception ex)
1674-
{
1675-
throw LogExceptionMessage(new SecurityTokenEncryptionFailedException(FormatInvariant(LogMessages.IDX13604), ex));
1676-
}
1695+
if (cryptoProviderFactory == null)
1696+
throw LogExceptionMessage(new ArgumentException(LogMessages.IDX13600));
1697+
1698+
if (!cryptoProviderFactory.IsSupportedAlgorithm(algorithm, key))
1699+
throw LogExceptionMessage(new Saml2SecurityTokenEncryptedAssertionEncryptionException(FormatInvariant(LogMessages.IDX13601, algorithm, key)));
1700+
1701+
return cryptoProviderFactory;
1702+
}
1703+
1704+
private SymmetricSecurityKey CreateSessionKey(string algorithm)
1705+
{
1706+
if (string.IsNullOrEmpty(algorithm))
1707+
throw LogHelper.LogArgumentNullException(nameof(algorithm));
1708+
1709+
int keySize = -1;
1710+
1711+
if (SecurityAlgorithms.Aes128Gcm.Equals(algorithm, StringComparison.Ordinal))
1712+
keySize = 128;
1713+
else if (SecurityAlgorithms.Aes192Gcm.Equals(algorithm, StringComparison.Ordinal))
1714+
keySize = 192;
1715+
else if (SecurityAlgorithms.Aes256Gcm.Equals(algorithm, StringComparison.Ordinal))
1716+
keySize = 256;
1717+
1718+
if (keySize == -1)
1719+
{
1720+
throw LogExceptionMessage(new Saml2SecurityTokenEncryptedAssertionEncryptionException(FormatInvariant(LogMessages.IDX13607, algorithm)));
16771721
}
1678-
else if (assertion.EncryptingCredentials.Key is AsymmetricSecurityKey)
1722+
1723+
var aes = Aes.Create();
1724+
aes.KeySize = keySize;
1725+
aes.GenerateKey();
1726+
var sessionKey = new SymmetricSecurityKey(aes.Key);
1727+
return sessionKey;
1728+
}
1729+
1730+
private EncryptedData CreateEncryptedData(SymmetricSecurityKey key, string algorithm, byte[] assertionData, EncryptingCredentials encryptingCredentials)
1731+
{
1732+
var cryptoProviderFactory = GetCryptoProviderFactory(key, algorithm, encryptingCredentials);
1733+
AuthenticatedEncryptionResult authenticatedEncryptionResult = null;
1734+
var authenticatedEncryptionProvider = cryptoProviderFactory.CreateAuthenticatedEncryptionProvider(key, algorithm);
1735+
if (authenticatedEncryptionProvider == null)
1736+
throw LogExceptionMessage(new Saml2SecurityTokenEncryptedAssertionEncryptionException(LogMessages.IDX13602));
1737+
1738+
try
16791739
{
1680-
//geok: TODO
1740+
authenticatedEncryptionResult = authenticatedEncryptionProvider.Encrypt(assertionData);
16811741
}
1682-
else
1742+
catch (Exception ex)
16831743
{
1684-
//geok: TODO
1685-
//throw
1744+
throw LogExceptionMessage(new Saml2SecurityTokenEncryptedAssertionEncryptionException(FormatInvariant(LogMessages.IDX13603, algorithm, key), ex));
16861745
}
1746+
1747+
var encryptedData = new EncryptedData();
1748+
encryptedData.CipherData.CipherValue = Utility.ConcatByteArrays(authenticatedEncryptionResult.IV, authenticatedEncryptionResult.Ciphertext, authenticatedEncryptionResult.AuthenticationTag);
1749+
encryptedData.EncryptionMethod = new EncryptionMethod(algorithm);
1750+
encryptedData.Type = XmlEncryptionConstants.EncryptedDataTypes.Element;
1751+
encryptedData.Id = new Saml2Id().Value;
1752+
1753+
return encryptedData;
16871754
}
16881755

1756+
private EncryptedKey CreateEncryptedKey(AsymmetricSecurityKey key, SymmetricSecurityKey sessionKey, string algorithm, EncryptingCredentials encryptingCredentials)
1757+
{
1758+
var cryptoProviderFactory = GetCryptoProviderFactory(key, algorithm, encryptingCredentials);
1759+
var encryptedKey = new EncryptedKey
1760+
{
1761+
Id = key.KeyId,
1762+
EncryptionMethod = new EncryptionMethod(algorithm)
1763+
};
1764+
1765+
var keyWrapProvider = cryptoProviderFactory.CreateKeyWrapProvider(key, algorithm);
1766+
var wrappedKey = keyWrapProvider.WrapKey(sessionKey.Key);
1767+
1768+
encryptedKey.CipherData.CipherValue = wrappedKey;
1769+
1770+
return encryptedKey;
1771+
}
1772+
1773+
16891774
/// <summary>
16901775
/// Writes the &lt;saml:Attribute> element.
16911776
/// </summary>

src/Microsoft.IdentityModel.Xml/DataReference.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
//
2626
//------------------------------------------------------------------------------
2727

28+
using System.Xml;
29+
2830
namespace Microsoft.IdentityModel.Xml
2931
{
3032
/// <summary>
@@ -49,5 +51,15 @@ public DataReference(string uri) : base(uri)
4951
{
5052
ReferenceType = "DataReference";
5153
}
54+
55+
internal override void WriteXml(XmlWriter writer)
56+
{
57+
writer.WriteStartElement(XmlEncryptionConstants.Prefix, XmlEncryptionConstants.Elements.DataReference, null);
58+
59+
if (!string.IsNullOrEmpty(Uri))
60+
writer.WriteAttributeString(XmlEncryptionConstants.Attributes.Uri, Uri);
61+
62+
writer.WriteEndElement();
63+
}
5264
}
5365
}

src/Microsoft.IdentityModel.Xml/EncryptedData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ internal void WriteXml(XmlWriter writer)
4545
if (!string.IsNullOrEmpty(Type))
4646
writer.WriteAttributeString(XmlEncryptionConstants.Attributes.Type, null, Type);
4747

48-
if (!string.IsNullOrEmpty(Id))
48+
if (!string.IsNullOrEmpty(MimeType))
4949
writer.WriteAttributeString(XmlEncryptionConstants.Attributes.MimeType, null, MimeType);
5050

5151
if (!string.IsNullOrEmpty(Encoding))

src/Microsoft.IdentityModel.Xml/EncryptedKey.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,28 @@ public void AddReference(KeyReference keyReference)
9999

100100
internal void WriteXml(XmlWriter writer)
101101
{
102-
writer.WriteStartElement(XmlEncryptionConstants.Prefix, XmlEncryptionConstants.Elements.EncryptedData, XmlEncryptionConstants.Namespace);
103-
//
102+
writer.WriteStartElement(XmlEncryptionConstants.Prefix, XmlEncryptionConstants.Elements.EncryptedKey, XmlEncryptionConstants.Namespace);
103+
104+
if (!string.IsNullOrEmpty(Id))
105+
writer.WriteAttributeString(XmlEncryptionConstants.Attributes.Id, null, Id);
106+
107+
if (EncryptionMethod != null)
108+
EncryptionMethod.WriteXml(writer);
109+
110+
CipherData.WriteXml(writer); //required
111+
112+
if (ReferenceList.Count != 0)
113+
{
114+
writer.WriteStartElement(XmlEncryptionConstants.Prefix, XmlEncryptionConstants.Elements.ReferenceList);
115+
116+
foreach(var reference in ReferenceList)
117+
{
118+
reference.WriteXml(writer);
119+
}
120+
121+
writer.WriteEndElement();
122+
}
123+
104124
writer.WriteEndElement();
105125
}
106126
}

src/Microsoft.IdentityModel.Xml/EncryptedReference.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
//
2626
//------------------------------------------------------------------------------
2727

28+
using System.Xml;
29+
2830
namespace Microsoft.IdentityModel.Xml
2931
{
3032
/// <summary>
@@ -46,6 +48,7 @@ protected EncryptedReference() : this(string.Empty)
4648
/// <param name="uri"></param>
4749
protected EncryptedReference(string uri)
4850
{
51+
Uri = uri;
4952
}
5053

5154
/// <summary>
@@ -57,5 +60,7 @@ protected EncryptedReference(string uri)
5760
/// Gets or sets a reference type.
5861
/// </summary>
5962
protected string ReferenceType { get; set; }
63+
64+
abstract internal void WriteXml(XmlWriter writer);
6065
}
6166
}

src/Microsoft.IdentityModel.Xml/KeyInfo.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,14 @@ internal void WriteXml(XmlWriter writer)
246246
writer.WriteEndElement();
247247
}
248248

249+
if (!string.IsNullOrEmpty(RetrievalMethodUri))
250+
{
251+
writer.WriteStartElement(XmlSignatureConstants.PreferredPrefix, XmlSignatureConstants.Elements.RetrievalMethod, null);
252+
writer.WriteAttributeString(XmlEncryptionConstants.Attributes.Uri, null, RetrievalMethodUri);
253+
writer.WriteAttributeString(XmlEncryptionConstants.Attributes.Type, null, XmlEncryptionConstants.EncryptedDataTypes.EncryptedKey);
254+
writer.WriteEndElement();
255+
}
256+
249257
writer.WriteEndElement();
250258
}
251259
}

src/Microsoft.IdentityModel.Xml/KeyReference.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
//
2626
//------------------------------------------------------------------------------
2727

28+
using System.Xml;
29+
2830
namespace Microsoft.IdentityModel.Xml
2931
{
3032
/// <summary>
@@ -49,5 +51,15 @@ public KeyReference(string uri) : base(uri)
4951
{
5052
ReferenceType = "KeyReference";
5153
}
54+
55+
internal override void WriteXml(XmlWriter writer)
56+
{
57+
writer.WriteStartElement(XmlEncryptionConstants.Prefix, XmlEncryptionConstants.Elements.KeyReference, null);
58+
59+
if (!string.IsNullOrEmpty(Uri))
60+
writer.WriteAttributeString(XmlEncryptionConstants.Attributes.Uri, Uri);
61+
62+
writer.WriteEndElement();
63+
}
5264
}
5365
}

src/Microsoft.IdentityModel.Xml/XMLEncryptionConstants.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ namespace Microsoft.IdentityModel.Xml
1313
internal static class XmlEncryptionConstants
1414
{
1515
#pragma warning disable 1591
16-
public const string Namespace = "http://www.w3.org/2009/xmlenc11#";
16+
public const string Namespace = "http://www.w3.org/2001/04/xmlenc#";
1717

18-
public const string Prefix = "xenc11";
18+
public const string Prefix = "xenc";
1919

2020
public static class Attributes
2121
{
@@ -50,6 +50,7 @@ public static class EncryptedDataTypes
5050
{
5151
public const string Element = Namespace + "Element";
5252
public const string Content = Namespace + "Content";
53+
public const string EncryptedKey = Namespace + "EncryptedKey";
5354
#pragma warning restore 1591
5455
}
5556
}

test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ public static TheoryData<Saml2TheoryData> WriteTokenTheoryData
377377
Subject = new ClaimsIdentity(Default.SamlClaims),
378378
};
379379

380-
var tokenDescriptorWithEncryptingCredentials = new SecurityTokenDescriptor
380+
var tokenDescriptorWithPreSharedEncryptingCredentials = new SecurityTokenDescriptor
381381
{
382382
Audience = Default.Audience,
383383
NotBefore = Default.NotBefore,
@@ -388,6 +388,17 @@ public static TheoryData<Saml2TheoryData> WriteTokenTheoryData
388388
Subject = new ClaimsIdentity(Default.SamlClaims)
389389
};
390390

391+
var tokenDescriptorWithEncryptingCredentials = new SecurityTokenDescriptor
392+
{
393+
Audience = Default.Audience,
394+
NotBefore = Default.NotBefore,
395+
Expires = Default.Expires,
396+
Issuer = Default.Issuer,
397+
EncryptingCredentials = new X509EncryptingCredentials(KeyingMaterial.CertSelfSigned2048_SHA256_Public),
398+
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.Sha256Digest),
399+
Subject = new ClaimsIdentity(Default.SamlClaims)
400+
};
401+
391402
var validationParameters = new TokenValidationParameters
392403
{
393404
AuthenticationType = "Federation",
@@ -412,11 +423,18 @@ public static TheoryData<Saml2TheoryData> WriteTokenTheoryData
412423
ValidationParameters = validationParameters
413424
});
414425

426+
theoryData.Add(new Saml2TheoryData
427+
{
428+
SecurityToken = tokenHandler.CreateToken(tokenDescriptorWithPreSharedEncryptingCredentials) as Saml2SecurityToken,
429+
ExpectedException = new ExpectedException(typeof(ArgumentNullException)),
430+
TestId = "EncryptedAssertion_PreSharedKey",
431+
});
432+
415433
theoryData.Add(new Saml2TheoryData
416434
{
417435
SecurityToken = tokenHandler.CreateToken(tokenDescriptorWithEncryptingCredentials) as Saml2SecurityToken,
418436
ExpectedException = new ExpectedException(typeof(ArgumentNullException)),
419-
TestId = "EncryptedAssertion",
437+
TestId = "EncryptedAssertion_KeyWrap",
420438
});
421439

422440
theoryData.Add(new Saml2TheoryData

0 commit comments

Comments
 (0)