-
Notifications
You must be signed in to change notification settings - Fork 416
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Saml2EncryptedAssertion #1025
base: dev
Are you sure you want to change the base?
Saml2EncryptedAssertion #1025
Conversation
@@ -181,6 +184,7 @@ internal static class LogMessages | |||
public const string IDX10685 = "IDX10685: Unable to Sign, Internal SignFunction is not available."; | |||
public const string IDX10686 = "IDX10686: Unable to Verify, Internal VerifyFunction is not available."; | |||
public const string IDX10687 = "IDX10687: Unable to create a AsymmetricAdapter. For NET45 or NET451 only types: '{0}' or '{1}' are supported. RSA is of type: '{2}'.."; | |||
public const string IDX10688 = "IDX10688: The algorithm '{0}' is not currently supported. In order to use the algorithm '{0}' one should override CryptoProviderFactory and AuthenticatedEncryptionProvider to provide support."; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to use the algorithm '{0}' one should override CryptoProviderFactory and AuthenticatedEncryptionProvider to provide support. [](start = 98, length = 133)
suggestion, replace "in order...
with:
It's possible to provide your own implementation of the algorithm by overriding CryptoProviderFactory and AuthenticatedEncryptionProvider.
You can potentially add an aka.ms link which points to the test implementation you have. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I'll create the link and edit the log message #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed with: b9c796d
#Closed
throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX10649, Algorithm))); | ||
|
||
Key = key; | ||
Algorithm = algorithm; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: move setting key/algorithm to after if/else. This means you can make the above just an if (!isAes...)
objective: reduce nesting for readability.
I supose this algo can be considered "SupportedIfYouBringYourOwnImplementation" #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll put a comment on top of AES-GCM algo definitions to improve clarity and set expectations.
Also, I'll refactor the constructor to increase current readability.
When we add AES-GCM support it might be better to return to if (isAesGcm) to separate support for AES-GCM from other authenticated encryption algorithms. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#if NET45 || NET451 | ||
settings.XmlResolver = null; | ||
#endif | ||
using (var reader = XmlReader.Create(stringReader, settings)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XmlReader.Create(stringReader, settings)) [](start = 36, length = 41)
Suggestion: The library could have a utility function to create this one ensuring that everyone creates the XmlReader with the appropriate (secure) XmlReaderSettings #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree - same settings are created more than once throughout the project. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed with: bfaa792 #Closed
reader.MoveToContent(); | ||
return reader.IsStartElement(Saml2Constants.Elements.EncryptedAssertion, Saml2Constants.Namespace); | ||
} | ||
catch (Exception) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it possible to catch only specific exceptions? Ideally some system exceptions should not be swallowed. #Pending
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll think about this. The goal of this method is just to return True if first/start element of provided string is EncryptedAssertion and return False otherwise. I don't really care at that point if the string is valid XML or not.
However, I think that it definitely adds value to include exception message to log information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed with: 6434db7
#Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another reason for me asking is that it's a best practice to only catch exceptions you know what are. as mentioned certain system exceptions should ideally never be captured, it's better from a security perspective to let the code fail with an exception.
In reply to: 221345997 [](ancestors = 221345997)
|
||
// Set Digest method for EncryptedKey | ||
if (SecurityAlgorithms.RsaOaepMgf1pKeyWrap.Equals(encryptingCredentials.Alg, StringComparison.Ordinal)) | ||
encryptedAssertion.EncryptedKey.EncryptionMethod.DigestMethod = SecurityAlgorithms.Sha1Digest; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will thinks just work if the digest method is not set? otherwise perhaps you will have to throw an exception?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other algorithms may not have DigestMethod (e.g. rsa-1_5) under EncryptionMethod element. For the current implementation we are using default rsa-oaep-mgf1p parameters.
encryptedAssertion.EncryptedKey.EncryptionMethod.DigestMethod = SecurityAlgorithms.Sha1Digest; | ||
|
||
// Set X509CertificateData | ||
if (encryptingCredentials.Key is X509SecurityKey) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (encryptingCredentials.Key is X509SecurityKey) [](start = 15, length = 50)
same though as above, what if this is not true, will the method still behave as expected? what about dependencies upstream? consider if an exception should be thrown.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this element is optional, but it's might be useful when when users are providing custom KeyResolving logic using TokenDecryptionKeyResolver delegate.
Current implementation is relying on using TokenDecryptionKey (set on TokenValidationParams) to decrypt an assertion, if TokenDecryptionKeyResolver delegate is not set.
var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit }; | ||
#if NET45 || NET451 | ||
settings.XmlResolver = null; | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as mentioned, a utility method for this and perhaps even for the variations of readers. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree - same settings are created more than once throughout the project. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed with: bfaa792 #Closed
@@ -1516,7 +1752,7 @@ protected virtual void WriteAdvice(XmlWriter writer, Saml2Advice advice) | |||
} | |||
|
|||
/// <summary> | |||
/// Writes the <Assertion> element. | |||
/// Writes the <Assertion> or <EncryptedAssertion> element. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[](start = 36, length = 1)
consider : gt; ? ... same towards the end of the line..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just being consistent - this pattern (< NODE >) is used across the library.
@@ -2250,6 +2518,89 @@ protected virtual void WriteSubjectLocality(XmlWriter writer, Saml2SubjectLocali | |||
writer.WriteEndElement(); | |||
} | |||
|
|||
private EncryptedData CreateEncryptedDataHelper(SymmetricSecurityKey key, string algorithm, byte[] assertionData, EncryptingCredentials encryptingCredentials) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private E [](start = 8, length = 9)
it seems to me like the functions for creating the helper which essentially returns Encrypted data (I'm a bit confused about that), should be in another class? Or the methods names should simply not be called Helper #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll think about refactoring these. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed with: ac21868
#Closed
|
||
reader.ReadStartElement(XmlEncryptionConstants.Elements.EncryptionMethod, XmlEncryptionConstants.Namespace); | ||
|
||
while (reader.IsStartElement()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while (reader.IsStartElement()) [](start = 16, length = 31)
what is reader.IsSTartElement supposed to do? Reading this code I would be afraid that it would either potentially just have one element to read, however if it never reads anything this will be an endless loop
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IsStartElement checks if xml reader is pointing to a start tag or empty element tag.
In case where there is nothing to read, reader will be pointing to an EndElement and exit from the while loop.
This logic is implemented because EncryptionMethod supports any element as a child. For now, we care only about DigestMethod and we are ignoring (but logging) other elements that may appear.
That said, it might be better to use reader.Skip() instead of reader.Read() on ln. 109 as DigestMethod by xenc xsd schema might have child elements (it's usually an empty element)
|
||
public const string Prefix = "xenc"; | ||
|
||
public static class Attributes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this library use embedding of types inside of other types? If not I would have suggested moving to separate files and separate by namespace instead of using embedded types, thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more of a utility class. While coding it's easy to find a specific constant i.e. an Attribute by typing XmlEncCostants.Attribute.
Not really sure if I understand - Are you suggesting that it would be a good idea to create a class hierarchy based on an xenc xsd schema e.g. EncryptionMethod contains only Algorithm as an attribute and KeySize, OAEPparams as elements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed with: 3ebd2b7
@@ -414,23 +414,23 @@ public static TheoryData<CreateTokenTheoryData> RoundTripJWEKeyWrappingTheoryDat | |||
ValidationParameters = Default.TokenValidationParameters(KeyingMaterial.RsaSecurityKey_2048, Default.SymmetricSigningKey256), | |||
Payload = Default.PayloadString, | |||
SigningCredentials = Default.SymmetricSigningCredentials, | |||
EncryptingCredentials = new EncryptingCredentials(KeyingMaterial.RsaSecurityKey_2048, SecurityAlgorithms.RsaOaepKeyWrap, SecurityAlgorithms.Aes128CbcHmacSha256) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RsaOaepKeyWrap [](start = 129, length = 14)
don't you still need the old test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's a good point. We decided to support wrong RsaOaepKeyWrap identifier so we should have tests for that one too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed with: 0b0abbf
public static byte[] DefaultSymmetricKeyBytes_192 = Convert.FromBase64String(DefaultSymmetricKeyEncoded_192); | ||
public static SymmetricSecurityKey DefaultSymmetricSecurityKey_192 = new SymmetricSecurityKey(DefaultSymmetricKeyBytes_192) { KeyId = "DefaultSymmetricSecurityKey_192" }; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: one empty too many
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed with: 86c0809
namespace Microsoft.IdentityModel.Tokens.Saml.Tests | ||
{ | ||
// Helper AuthenticatedEncryptionProvider class made to mimic AES-GCM | ||
// remove when AES-GCM is released and supported |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// [](start = 4, length = 2)
it seems like you are not using xml comments, I suggest using xml comments instead of this. Also in test code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we compiling doc for test code as well?
Is using xml comments in test projects needed by some process or it serves for readability purposes?
@@ -430,5 +431,34 @@ internal static string[] TokenizeInclusiveNamespacesPrefixList(string inclusiveN | |||
return result; | |||
} | |||
} | |||
|
|||
#if NET45 || NET451 | |||
private static readonly XmlReaderSettings SafeSettings = new XmlReaderSettings { XmlResolver = null, DtdProcessing = DtdProcessing.Prohibit }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: SafeXmlReaderSettings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed with: 81f395c
@@ -2250,6 +2466,172 @@ protected virtual void WriteSubjectLocality(XmlWriter writer, Saml2SubjectLocali | |||
writer.WriteEndElement(); | |||
} | |||
|
|||
private EncryptedData CreateEncryptedData(byte[] assertionData, EncryptingCredentials encryptingCredentials, out SymmetricSecurityKey sessionKey) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EncryptedData [](start = 16, length = 13)
suggestion: instead of an out parameter you can also return a tuple (if the platforms this runs on support value tuples that might be even better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking tuples while refactoring this method, and it seemed to me that using out param is a bit cleaner, considering that it works on each platform and that CreateEncryptedData is a private method.
Is there a reason why tuples (not ValueTuples) are preferable over out param, for this specific case?
// AsymmetricSecurityKey is provided: | ||
// New session key will be created to encrypt an assertion | ||
// Session key will be wrapped with provided AsymmetricSecurityKey | ||
if (encryptingCredentials.Key is AsymmetricSecurityKey) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if [](start = 12, length = 2)
consider inverting all your if's moving all the short functions on top (e.g. Symmetric does nothing, and not assymetric does nothing. this will give an easy overview of the various return statements from the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed with: a562057
@@ -39,6 +39,12 @@ public static class SecurityAlgorithms | |||
public const string Aes256Encryption = "http://www.w3.org/2001/04/xmlenc#aes256-cbc"; | |||
public const string DesEncryption = "http://www.w3.org/2001/04/xmlenc#des-cbc"; | |||
|
|||
// Currently not supported. It's possible to provide your own implementation of the algorithm by overriding CryptoProviderFactory and AuthenticatedEncryptionProvider. | |||
// See: https://www.w3.org/TR/xmlenc-core1/#sec-AES-GCM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider adding these as xml comments to all the consts. that way a developer will actually see these comments in the tooltip.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed with: c8e9d09
@@ -180,7 +183,8 @@ internal static class LogMessages | |||
public const string IDX10684 = "IDX10684: Unable to create a AsymmetricAdapter, Algorithm: '{0}', Key: '{1}'."; | |||
public const string IDX10685 = "IDX10685: Unable to Sign, Internal SignFunction is not available."; | |||
public const string IDX10686 = "IDX10686: Unable to Verify, Internal VerifyFunction is not available."; | |||
public const string IDX10687 = "IDX10687: Unable to create a AsymmetricAdapter. For NET45 or NET451 only types: '{0}' or '{1}' are supported. RSA is of type: '{2}'.."; | |||
public const string IDX10687 = "IDX10687: Unable to create a AsymmetricAdapter. For NET45 or NET451 only types: '{0}' or '{1}' are supported. RSA is of type: '{2}'."; | |||
public const string IDX10688 = "IDX10688: The algorithm '{0}' is not currently supported. It's possible to provide your own implementation of the algorithm by overriding CryptoProviderFactory and AuthenticatedEncryptionProvider. Example: aka.ms/identitymodel-aesgcm-support"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aka.ms/identitymodel-aesgcm-support [](start = 246, length = 35)
did you add the wiki page/sample for this and enable the aka.ms link?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aka.ms links points to the code developed to support AES-GCM tests. Do you suggest that code sample doesn't give enough information/guidance to a developer and that I should add a wiki page as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🕐
e09cd2e
to
0b0abbf
Compare
set; | ||
get | ||
{ | ||
if (Encrypted) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could just return null here, and not throw.
The use case is a user created a SAML token with just encrypted assertion, that would be the only value that is not null.
This would allow us to remove the type Saml2SecurityTokenEncryptedAssertionException.
3ebd2b7
to
1947555
Compare
c6a0aec
to
a685ca0
Compare
Looks great! Any clue in which version this would be awailable? |
The plan is to make this feature available in the next major release, probably 6.x. |
Thank you for your prompt reply. Looking forward to 6.x :) |
I would love to have this feature. Encryption is mandatory for my customers :) |
@thuannguy yep, we need this feature. |
a685ca0
to
9789c2c
Compare
We may want to include aes-256cbc as AzureAD supports this. |
659a682
to
e5f522b
Compare
e5f522b
to
8ccee2f
Compare
Key Point:
AES-GCM is still not supported by System.Security.Cryptography. Therefore using IdentityModel extensions project to Encrypt/Decrypt a Saml2 Assertion, using an AES-GCM algorithm, will result with an exception. One should override CryptoProviderFactory and AuthenticationEncryptionProvider (similar to the ones developed for test) in order to use existing logic to Encrypt/Decrypt a Saml2 Assertion (using an AES-GCM algorithm).
Summary:
Saml2 tokens can have encrypted assertions. The goal of this PR is to enable basic support for encrypting and decrypting a Saml2Assertion, based on the latest xml enc standard, using modern authenticated encryption mechanism (AES-GCM) and key wrap (transport) mechanism (RSA-OAEP-MGF1P), and without introducing any breaking changes to the current project infrastructure.
The following scenarios are supported:
Encryption using a pre-shared symmetric key (session key) to encrypt an assertion: Session key will not be serialized and Relying Party must be able to locally determine the decryption key.
New session key will be created to encrypt an assertion and session key will be wrapped using a provided asymmetric public-key (X.509 certificate).
Saml2EncryptedAssertion support (basic profile):
Implementation notes:
Resolves: #734