-
Notifications
You must be signed in to change notification settings - Fork 147
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
Fix regression issue in IClientCertificate caused by SHA256 changes #898
Conversation
@@ -36,4 +41,35 @@ void testGetClient() { | |||
final ClientCertificate kc = ClientCertificate.create(key, null); | |||
assertNotNull(kc); | |||
} | |||
|
|||
@Test | |||
void testIClientCertificateInterface_Sha256AndSha1() throws NoSuchAlgorithmException, CertificateException { |
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.
Please create a better E2E (not necessarily integration) test that uses MSAL public API. To assert, you can intercept the HTTP call to the /token endpoint, get the client_assertion parameter and observe that it has x5t#s256 which is correctly computed.
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 the latest commit I added a more thorough test: everything up until the call to the token endpoint is real, and then the mocked response will only be successful if 'x5t#s256' is one of the headers in the assertion.
@@ -97,4 +105,34 @@ static HttpResponse expectedResponse(int statusCode, String response) { | |||
|
|||
return httpResponse; | |||
} | |||
|
|||
static void setPrivateKeyAndCert() { |
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.
Can you use a static certificate instead? Like in MISE? That way you can assert on the the well known hash 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.
The variables are already static, or are you saying to just create one locally and add it to the repo?
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.
Insufficient testing.
…tion-library-for-java into avdunn/sha256-fix # Conflicts: # msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TestHelper.java
if (Authority.detectAuthorityType(this.authenticationAuthority.canonicalAuthorityUrl()) == AuthorityType.ADFS) { | ||
clientAuthentication = buildValidClientCertificateAuthoritySha1(); | ||
//When this was added, ADFS did not support SHA256 hashes for client certificates | ||
clientAuthentication = buildValidClientCertificateAuthority(true); | ||
} else { | ||
clientAuthentication = buildValidClientCertificateAuthority(); | ||
clientAuthentication = buildValidClientCertificateAuthority(false); |
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.
You could eliminate the if statement and call buildValidClientCertificateAuthority
only once:
const useSha1: boolean = (Authority.detectAuthorityType(this.authenticationAuthority.canonicalAuthorityUrl()) == AuthorityType.ADFS);
clientAuthentication = buildValidClientCertificateAuthority(useSha1)
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, in the latest commit I moved this logic down into the buildValidClientCertificateAuthority
method. Now that method does the ADFS authority check itself, with a comment explaining why it's done.
msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ConfidentialClientApplication.java
Outdated
Show resolved
Hide resolved
false); | ||
return createClientAuthFromClientAssertion(clientAssertion); | ||
} | ||
|
||
//The library originally used SHA-1 for thumbprints as other algorithms were not supported server-side, | ||
// and while support for SHA-256 has been added certain flows still only allow SHA-1 |
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.
super duper nit: add a ,
after added
. In my mind, it makes sense to indicate a pause to break up the sentence - please feel free to ignore this if you want
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 the latest commit I fixed the grammar and adjusted the wording a bit.
builder.x509CertThumbprint(new Base64URL(credential.publicCertificateHashSha1())); | ||
//SHA-256 is preferred, however certain flows still require SHA-1 due to what is supported server-side. If SHA-256 | ||
// is not supported or the IClientCredential.publicCertificateHash256() method is not implemented, the library will default to SHA-1. | ||
if (useSha1 || credential.publicCertificateHash256() == null) { |
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.
You're using credential.publicCertificateHash256()
twice - once in the if check and once in the else. Can you set it to a const before the if check?
(and add a type, below is typescript)
const publicCertificateHash256: hash | null = credential.publicCertificateHash256();
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 the latest commit it's now only set once.
What is the "default" hash algorithm that you're implementing in this PR? Sha1 or Sha256? Your description states (as I interpret it, based on the last paragraph) that Sha256 should be the default, but your code revolves around checks for |
To clarify, 'default' here refers a default method in an interface. If you add a new method to a public interface, it needs a default implementation or else anything that implements the interface would also need to change to implement the new method (which would be a breaking change): https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html So technically the (as a bit of background, we found this issue because a customer was using the library's implementation of |
…tion-library-for-java into avdunn/sha256-fix
Fix for the issue described in #863
As part of #840, the internal implementation of the
IClientCertificate
interface'spublicCertificateHash()
method was changed to use SHA-256, and a newpublicCertificateHashSha1()
method was added to the internal implementation to handle scenarios that still needed SHA-1.However, this meant that anyone who implemented their own
IClientCredential
instead of using ourClientCredentialFactory
would have apublicCertificateHash()
method that still used SHA-1, causing issues because our library expected that method to produce a SHA-256 hash.This PR solves that issue by adding a new
publicCertificateHash256
method to the public interface to handle SHA-256 scenarios, and once again haspublicCertificateHash
handling the SHA-1 scenarios.The new interface method is given a default implementation so that it doesn't break any customer's implementations, and if it's not implemented then the library falls back to
publicCertificateHash
and SHA-1. Anyone who was always using ourClientCredentialFactory
should not encounter any issues from either the original changes or the ones in this PR.