Skip to content
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

Merged
merged 1 commit into from
Aug 21, 2018
Merged

Conversation

GeoK
Copy link
Member

@GeoK GeoK commented Aug 16, 2018

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

@GeoK GeoK requested review from brentschmaltz and mafurman August 16, 2018 22:00
/// </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>
Copy link
Member

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.
Copy link
Member

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;
}
Copy link
Member

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()?

Copy link
Member Author

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>
Copy link
Member

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.
Copy link
Member

@mafurman mafurman Aug 17, 2018

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.
Copy link
Member

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
Copy link
Member

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)
Copy link
Member

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

Copy link
Member Author

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
Copy link
Member

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.

Copy link
Member Author

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))
Copy link
Contributor

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.

Copy link
Member Author

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.

@GeoK GeoK force-pushed the geok/X509EncryptingCredentials branch 2 times, most recently from dbc3903 to 4728e83 Compare August 18, 2018 02:09
Copy link
Member

@brentschmaltz brentschmaltz left a 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
{
Copy link
Member

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.

Copy link
Member Author

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"
Copy link
Member

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.

Copy link
Member Author

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:"),
Copy link
Member

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.

Copy link
Member Author

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);
Copy link
Member

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();
Copy link
Member

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)
Copy link
Member

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.

Copy link
Member Author

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;
Copy link
Member

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.

Copy link
Member Author

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>
Copy link
Member

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>
Copy link
Member

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>
Copy link
Member

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@GeoK
Copy link
Member Author

GeoK commented Aug 20, 2018

@brentschmaltz Thank you for the suggestions, I addressed them with: 7578fe6

@brentschmaltz
Copy link
Member

:shipit:

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
@GeoK GeoK force-pushed the geok/X509EncryptingCredentials branch from 8f3cc6b to e9fe530 Compare August 21, 2018 18:45
@GeoK GeoK merged commit e9fe530 into dev Aug 21, 2018
@GeoK GeoK deleted the geok/X509EncryptingCredentials branch August 21, 2018 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants