-
Notifications
You must be signed in to change notification settings - Fork 421
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
Add X509EncryptedCredentials class #1000
Conversation
/// </remarks> | ||
/// <param name="key"><see cref="SecurityKey"/></param> | ||
/// <param name="enc">Data encryption algorithm to apply when encrypting plaintext.</param> | ||
/// <exception cref="ArgumentException">If the <see cref="SecurityKey"/> is not <see cref="SymmetricSecurityKey"/>.</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.
I would put an "a" right before SymmetricSecurityKey for readability.
} | ||
|
||
/// <summary> | ||
/// Gets the algorithm which used for token encryption. | ||
/// Gets the key wrap encryption algorithm used for a session key encryption. |
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.
Get rid of the "a" before session key in this comment.
get; | ||
private set; | ||
get => _alg; | ||
private set => _alg = string.IsNullOrEmpty(value) ? throw LogHelper.LogArgumentNullException("alg") : value; | ||
} |
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.
Maybe pass in nameof(value) instead of "alg" to LogArgumentNullException()?
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.
Not sure if it makes sense as the resulting log message would look something like this:
IDX10000: The parameter value cannot be a 'null' or an empty object.
Using reflection, we can get names of parameters from a constructor but I don't believe that is a good solution.
/// </summary> | ||
public CryptoProviderFactory CryptoProviderFactory { get; set; } | ||
|
||
/// <summary> | ||
/// Gets the <see cref="SecurityKey"/> which used for signature valdiation. | ||
/// Gets the <see cref="SecurityKey"/> which used for signature validation. | ||
/// </summary> |
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.
Get rid of the "which" in this comment.
namespace Microsoft.IdentityModel.Tokens | ||
{ | ||
/// <summary> | ||
/// An <see cref="X509EncryptingCredentials"/> designed for to construct an <see cref="EncryptingCredentials"/> based on a x509 certificate. |
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.
Either change this to "designed for constructing EncryptingCredentials" or "designed to construct EncryptingCredentials"
Maybe add in <see cref="X509Certificate2"/>?
public class X509EncryptingCredentials : EncryptingCredentials | ||
{ | ||
/// <summary> | ||
/// Constructs an <see cref="EncryptingCredentials"/> based on a x509 certificate. |
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.
Get rid of "an"
/// <param name="certificate">A <see cref="X509Certificate2"/></param> | ||
/// <remarks> | ||
/// <see cref="SecurityAlgorithms.DefaultAsymmetricKeyWrapAlgorithm"/> algorithm will be used by default as a key wrap encryption algorithm | ||
/// <see cref="SecurityAlgorithms.DefaultSymmetricAlgorithm"/> algorithm will be used by default as a data encryption 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.
Change to "The algorithm that will be used"
/// <exception cref="ArgumentNullException">if 'keyWrapEncryptionAlgorithm' is null or empty.</exception> | ||
/// <exception cref="ArgumentNullException">if 'dataEncryptionAlgorithm' is null or empty.</exception> | ||
public X509EncryptingCredentials(X509Certificate2 certificate, string keyWrapEncryptionAlgorithm, string dataEncryptionAlgorithm) | ||
: base(new X509SecurityKey(certificate), keyWrapEncryptionAlgorithm, dataEncryptionAlgorithm) |
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.
Change this to use the constructor that takes in a certificate and not a key
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 catch!
|
||
namespace Microsoft.IdentityModel.Tokens.Tests | ||
{ | ||
public class EncryptingCredentialsTests |
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 would consider refactoring this test to use TheoryData if possible. I think the OpenIdConnectMessageTests.Constructors() test is similar to what you would need to do here.
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 do - it's much cleaner :)
|
||
if (string.IsNullOrWhiteSpace(enc)) | ||
throw LogHelper.LogArgumentNullException(nameof(enc)); | ||
if (key.GetType() != typeof(SymmetricSecurityKey)) |
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.
Instead of checking for SymmetricSecurityKey explicitly, it would be good to enforce this contract by allowing any class that derives from SymmetricSecurityKey to be accepted. This could be accomplished using the 'is' keyword or by calling the Type.IsAssignableFrom() method.
Bonus points if we can enforce this contract using an interface instead. The current implementation is not extensible to classes that do not inherit from SymmetricSecurityKey. (e.g. a cloud-based symmetric key from a service like Key Vault) An interface could similarly be used to determine if the key derives from SymmetricSecurityKey (if that class implments the specified interface) and would also make this class extensible to any other security key that implements the specified contract.
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.
Great input. I decided to use is as Type.IsAssignableFrom() is not supported in netstandard1.4
Good point w.r.t. an interface, will think about that.
dbc3903
to
4728e83
Compare
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.
George, have a look at these suggestions.
return new TheoryData<EncryptingCredentialsTheoryData> | ||
{ | ||
new EncryptingCredentialsTheoryData | ||
{ |
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 don't need this case.
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 remove it from the test case.
Alg = null, | ||
Enc = null, | ||
ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"), | ||
TestId = "null key" |
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.
Best not to have spaces ' a test name ', underscores '_', periods '.' in a TestId. This makes is harder to set a conditional break point when the test fails. Try double clicking on a TestId.
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.
Got it - I'll replace TestId values.
Key = null, | ||
Alg = SecurityAlgorithms.RsaOaepKeyWrap, | ||
Enc = SecurityAlgorithms.Aes128CbcHmacSha256, | ||
ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"), |
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.
It is possible to check the exception contains "enc", this catches typo's in the error message.
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'm going to use values in the following format as expected substrings:
"IDX10000: The parameter enc"
[Theory, MemberData(nameof(ConstructorBTheoryData))] | ||
public void ConstructorB(EncryptingCredentialsTheoryData theoryData) | ||
{ | ||
TestUtilities.WriteHeader($"{this}.ConstructorB", theoryData); |
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.
TestUtilities.WriteHeader returns a CompareContext, so you can write:
var context = TestUtilities.WriteHeader($"{this}.ConstructorB", theoryData);
var encryptingCredentials = new EncryptingCredentials(theoryData.Key, theoryData.Enc); | ||
//Algorithm value should be 'None' | ||
IdentityComparer.AreEqual(encryptingCredentials.Alg, SecurityAlgorithms.None, context); | ||
theoryData.ExpectedException.ProcessNoException(); |
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.
Pass context to ProcessNoException.
theoryData.ExpectedException.ProcessNoException(context);
/// <exception cref="ArgumentNullException">if 'certificate' is null.</exception> | ||
/// <exception cref="ArgumentNullException">if 'keyWrapEncryptionAlgorithm' is null or empty.</exception> | ||
/// <exception cref="ArgumentNullException">if 'dataEncryptionAlgorithm' is null or empty.</exception> | ||
public X509EncryptingCredentials(X509Certificate2 certificate, string keyWrapEncryptionAlgorithm, string dataEncryptionAlgorithm) |
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.
suggest:
keyWrapEncryptionAlgorithm -> keyWrapAlgorithm
KeyWrap always implies some sort of encryption.
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.
it makes sense - I'll rename.
@@ -113,6 +113,9 @@ public static class SecurityAlgorithms | |||
public const string Aes192CbcHmacSha384 = "A192CBC-HS384"; | |||
public const string Aes256CbcHmacSha512 = "A256CBC-HS512"; | |||
|
|||
internal const string DefaultAsymmetricKeyWrapAlgorithm = RsaOaepKeyWrap; | |||
internal const string DefaultSymmetricAlgorithm = 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.
suggest:
DefaultSymmetricAlgorithm -> DefaultSymmetricEncryptionAlgorithm
there are other SymmetricAlgorithms such as Signing.
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.
it makes sense - I'll rename.
/// <param name="enc">The encryption algorithm to apply.</param> | ||
public EncryptingCredentials(SecurityKey key, string alg, string enc) | ||
/// <param name="certificate"><see cref="X509Certificate2"/>.</param> | ||
/// <param name="alg">A key wrap encryption algorithm to apply when encrypting a session key.</param> |
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.
suggest:
A key wrap encryption algorithm -> A key wrap algorithm
public EncryptingCredentials(SecurityKey key, string alg, string enc) | ||
/// <param name="certificate"><see cref="X509Certificate2"/>.</param> | ||
/// <param name="alg">A key wrap encryption algorithm to apply when encrypting a session key.</param> | ||
/// <param name="enc">Data encryption algorithm to apply when encrypting plaintext.</param> |
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.
suggest:
Data encryption algorithm to apply when encrypting plaintext ->
The Data encryption algorithm to apply
/// <summary> | ||
/// Initializes a new instance of the <see cref="EncryptingCredentials"/> class. | ||
/// </summary> | ||
/// <param name="key"><see cref="SecurityKey"/> to apply when encrypting a session key.</param> |
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.
suggest:
to apply when encrypting a session key ->
to use when encrypting a session key
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.
Done.
@brentschmaltz Thank you for the suggestions, I addressed them with: 7578fe6 |
|
If only a certificate is provided, the key wrap algorithm and data encryption algorithm will be set by default to RsaOaepKeyWrap and A128CBC-HS256, respectively. * Add new ctor to EncryptingCredentials to allow users to pass only a 'shared' symmetric key which will be used to encrypt data, but it will not be serialized to a SAML token. * Add internal const strings DefaultAsymmetricKeyWrapAlgorithm and DefaultSymmetricEncryptionAlgorithm to indicate default algorithms used for key wrap and data encryption * Add protected ctor to EncryptingCredentials to check if a certificate passed to X509EncryptedCredentials is null. Provides cleaner stack trace in case of an exception caused by a null cert. * Refactor EncryptingCredentials. Move null/empty checks to setters and provide clearer comments * Add tests for X509EncryiptingCredentials and EncryptingCredentials classes Resolves: #995 See also: #734
8f3cc6b
to
e9fe530
Compare
If only a certificate is provided, key wrap encryption algorithm
and data encryption algorithm will be set by default to RsaOaepKeyWrap
and A128CBC-HS256, respectively.
Add new ctor to EncryptingCredentials to allow users to pass
only a 'shared' symmetric key which will be used to encrypt data, but
it will not be serialized to a SAML token.
Add internal const strings DefaultAsymmetricKeyWrapAlgorithm and
DefaultSymmetricAlgorithm to indicate the default algorithms used for
key wrap and data encryption
Add protected ctor to EncryptingCredentials to check if a certificate
passed to X509EncryptedCredentials is null. Provides cleaner stack
trace in case of an exception caused by a null cert.
Refactor EncryptingCredentials. Move null/empty checks to setters
and provide clearer comments
Add tests for X509EncryiptingCredentials and EncryptingCredentials
classes
Resolves: #995
See also: #734