From f14bcfe951947dd144727b0ec10526c3757e50ff Mon Sep 17 00:00:00 2001 From: SomkaPe Date: Mon, 21 Sep 2020 23:00:07 -0700 Subject: [PATCH] sendX5c api --- .../ClientCredentialsIT.java | 3 +- .../ConfidentialClientApplicationUnitT.java | 10 ++++-- .../aad/msal4j/ClientCertificate.java | 31 +++++++++---------- .../aad/msal4j/ClientCredentialFactory.java | 6 +++- .../msal4j/ConfidentialClientApplication.java | 25 ++++++++++++++- .../aad/msal4j/IClientCertificate.java | 2 +- .../IConfidentialClientApplication.java | 5 +++ .../com/microsoft/aad/msal4j/JwtHelper.java | 15 +++++---- 8 files changed, 67 insertions(+), 30 deletions(-) diff --git a/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java b/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java index 70cee028..c616ab4c 100644 --- a/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java +++ b/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java @@ -51,7 +51,8 @@ public void acquireTokenClientCredentials_ClientAssertion() throws Exception{ ClientAssertion clientAssertion = JwtHelper.buildJwt( clientId, (ClientCertificate) certificate, - "https://login.microsoftonline.com/common/oauth2/v2.0/token"); + "https://login.microsoftonline.com/common/oauth2/v2.0/token", + true); IClientCredential credential = ClientCredentialFactory.createFromClientAssertion( clientAssertion.assertion()); diff --git a/src/integrationtest/java/com.microsoft.aad.msal4j/ConfidentialClientApplicationUnitT.java b/src/integrationtest/java/com.microsoft.aad.msal4j/ConfidentialClientApplicationUnitT.java index d3ab1a9b..bfef31e4 100644 --- a/src/integrationtest/java/com.microsoft.aad.msal4j/ConfidentialClientApplicationUnitT.java +++ b/src/integrationtest/java/com.microsoft.aad.msal4j/ConfidentialClientApplicationUnitT.java @@ -156,7 +156,8 @@ public void testClientCertificateRebuildsWhenExpired() throws Exception { "buildJwt", EasyMock.isA(String.class), EasyMock.isA(ClientCertificate.class), - EasyMock.isA(String.class)) + EasyMock.isA(String.class), + EasyMock.anyBoolean()) .andReturn(shortExperationJwt) .times(2); // By this being called twice we ensure the client assertion is rebuilt once it has expired @@ -184,13 +185,16 @@ private ClientAssertion buildShortJwt(String clientId, .build(); SignedJWT jwt; try { + JWSHeader.Builder builder = new JWSHeader.Builder(JWSAlgorithm.RS256); + List certs = new ArrayList<>(); - for (String cert: credential.getEncodedPublicKeyCertificateOrCertificateChain()) { + for (String cert : credential.getEncodedPublicKeyCertificateChain()) { certs.add(new Base64(cert)); } - JWSHeader.Builder builder = new JWSHeader.Builder(JWSAlgorithm.RS256); builder.x509CertChain(certs); + builder.x509CertThumbprint(new Base64URL(credential.publicCertificateHash())); + jwt = new SignedJWT(builder.build(), claimsSet); final RSASSASigner signer = new RSASSASigner(credential.privateKey()); jwt.sign(signer); diff --git a/src/main/java/com/microsoft/aad/msal4j/ClientCertificate.java b/src/main/java/com/microsoft/aad/msal4j/ClientCertificate.java index 4b2b4ab4..bea76b83 100644 --- a/src/main/java/com/microsoft/aad/msal4j/ClientCertificate.java +++ b/src/main/java/com/microsoft/aad/msal4j/ClientCertificate.java @@ -32,12 +32,10 @@ final class ClientCertificate implements IClientCertificate { @Getter private final PrivateKey privateKey; - private final X509Certificate publicKeyCertificate; - private final List publicKeyCertificateChain; ClientCertificate - (PrivateKey privateKey, X509Certificate publicKeyCertificate, List publicKeyCertificateChain) { + (PrivateKey privateKey, List publicKeyCertificateChain) { if (privateKey == null) { throw new NullPointerException("PrivateKey is null or empty"); } @@ -68,7 +66,6 @@ final class ClientCertificate implements IClientCertificate { " sun.security.mscapi.RSAPrivateKey"); } - this.publicKeyCertificate = publicKeyCertificate; this.publicKeyCertificateChain = publicKeyCertificateChain; } @@ -76,18 +73,14 @@ public String publicCertificateHash() throws CertificateEncodingException, NoSuchAlgorithmException { return Base64.getEncoder().encodeToString(ClientCertificate - .getHash(this.publicKeyCertificate.getEncoded())); + .getHash(publicKeyCertificateChain.get(0).getEncoded())); } - public List getEncodedPublicKeyCertificateOrCertificateChain() throws CertificateEncodingException { + public List getEncodedPublicKeyCertificateChain() throws CertificateEncodingException { List result = new ArrayList<>(); - if (publicKeyCertificateChain != null && publicKeyCertificateChain.size() > 0) { - for (X509Certificate cert : publicKeyCertificateChain) { - result.add(Base64.getEncoder().encodeToString(cert.getEncoded())); - } - } else { - result.add(Base64.getEncoder().encodeToString(publicKeyCertificate.getEncoded())); + for (X509Certificate cert : publicKeyCertificateChain) { + result.add(Base64.getEncoder().encodeToString(cert.getEncoded())); } return result; } @@ -108,22 +101,26 @@ static ClientCertificate create(final InputStream pkcs12Certificate, final Strin throw new IllegalArgumentException("more than one certificate alias found in input stream"); } - ArrayList publicKeyCertificateChain = null; + ArrayList publicKeyCertificateChain = new ArrayList<>();; PrivateKey privateKey = (PrivateKey) keystore.getKey(alias, password.toCharArray()); + X509Certificate publicKeyCertificate = (X509Certificate) keystore.getCertificate(alias); Certificate[] chain = keystore.getCertificateChain(alias); - if (chain != null) { - publicKeyCertificateChain = new ArrayList<>(); + if (chain != null && chain.length > 0) { for (Certificate c : chain) { publicKeyCertificateChain.add((X509Certificate) c); } } - return new ClientCertificate(privateKey, publicKeyCertificate, publicKeyCertificateChain); + else{ + publicKeyCertificateChain.add(publicKeyCertificate); + } + + return new ClientCertificate(privateKey, publicKeyCertificateChain); } static ClientCertificate create(final PrivateKey key, final X509Certificate publicKeyCertificate) { - return new ClientCertificate(key, publicKeyCertificate, null); + return new ClientCertificate(key, Arrays.asList(publicKeyCertificate)); } private static byte[] getHash(final byte[] inputBytes) throws NoSuchAlgorithmException { diff --git a/src/main/java/com/microsoft/aad/msal4j/ClientCredentialFactory.java b/src/main/java/com/microsoft/aad/msal4j/ClientCredentialFactory.java index f87c24fe..cfafa00e 100644 --- a/src/main/java/com/microsoft/aad/msal4j/ClientCredentialFactory.java +++ b/src/main/java/com/microsoft/aad/msal4j/ClientCredentialFactory.java @@ -10,6 +10,8 @@ import java.security.cert.X509Certificate; import java.util.List; +import static com.microsoft.aad.msal4j.ParameterValidationUtils.validateNotNull; + /** * Factory for creating client credentials used in confidential client flows. For more details, see * https://aka.ms/msal4j-client-credentials @@ -50,6 +52,8 @@ public static IClientCertificate createFromCertificate(final InputStream pkcs12C * @return {@link ClientCertificate} */ public static IClientCertificate createFromCertificate(final PrivateKey key, final X509Certificate publicKeyCertificate) { + validateNotNull("publicKeyCertificate", publicKeyCertificate); + return ClientCertificate.create(key, publicKeyCertificate); } @@ -63,7 +67,7 @@ public static IClientCertificate createFromCertificateChain(PrivateKey key, List if(key == null || publicKeyCertificateChain == null || publicKeyCertificateChain.size() == 0){ throw new IllegalArgumentException("null or empty input parameter"); } - return new ClientCertificate(key, publicKeyCertificateChain.get(0), publicKeyCertificateChain); + return new ClientCertificate(key, publicKeyCertificateChain); } /** diff --git a/src/main/java/com/microsoft/aad/msal4j/ConfidentialClientApplication.java b/src/main/java/com/microsoft/aad/msal4j/ConfidentialClientApplication.java index bad504d2..184d2ff1 100644 --- a/src/main/java/com/microsoft/aad/msal4j/ConfidentialClientApplication.java +++ b/src/main/java/com/microsoft/aad/msal4j/ConfidentialClientApplication.java @@ -9,6 +9,8 @@ import com.nimbusds.oauth2.sdk.auth.PrivateKeyJWT; import com.nimbusds.oauth2.sdk.auth.Secret; import com.nimbusds.oauth2.sdk.id.ClientID; +import lombok.Getter; +import lombok.experimental.Accessors; import org.slf4j.LoggerFactory; import java.util.Collections; @@ -33,6 +35,10 @@ public class ConfidentialClientApplication extends AbstractClientApplicationBase private boolean clientCertAuthentication = false; private ClientCertificate clientCertificate; + @Accessors(fluent = true) + @Getter + private boolean sendX5c; + @Override public CompletableFuture acquireToken(ClientCredentialParameters parameters) { @@ -62,6 +68,7 @@ public CompletableFuture acquireToken(OnBehalfOfParameter private ConfidentialClientApplication(Builder builder) { super(builder); + sendX5c = builder.sendX5c; log = LoggerFactory.getLogger(ConfidentialClientApplication.class); @@ -103,7 +110,8 @@ private ClientAuthentication buildValidClientCertificateAuthority() { ClientAssertion clientAssertion = JwtHelper.buildJwt( clientId(), clientCertificate, - this.authenticationAuthority.selfSignedJwtAudience()); + this.authenticationAuthority.selfSignedJwtAudience(), + sendX5c); return createClientAuthFromClientAssertion(clientAssertion); } @@ -136,11 +144,26 @@ public static class Builder extends AbstractClientApplicationBase.Builder getEncodedPublicKeyCertificateOrCertificateChain() throws CertificateEncodingException; + List getEncodedPublicKeyCertificateChain() throws CertificateEncodingException; } diff --git a/src/main/java/com/microsoft/aad/msal4j/IConfidentialClientApplication.java b/src/main/java/com/microsoft/aad/msal4j/IConfidentialClientApplication.java index e9199e7d..160e5474 100644 --- a/src/main/java/com/microsoft/aad/msal4j/IConfidentialClientApplication.java +++ b/src/main/java/com/microsoft/aad/msal4j/IConfidentialClientApplication.java @@ -12,6 +12,11 @@ * For details see https://aka.ms/msal4jclientapplications */ public interface IConfidentialClientApplication extends IClientApplicationBase { + /** + * @return a boolean value which determines whether x5c claim (public key of the certificate) + * will be sent to the STS. + */ + boolean sendX5c(); /** * Acquires tokens from the authority configured in the application, for the confidential client diff --git a/src/main/java/com/microsoft/aad/msal4j/JwtHelper.java b/src/main/java/com/microsoft/aad/msal4j/JwtHelper.java index 02a8f318..7d2f2abb 100644 --- a/src/main/java/com/microsoft/aad/msal4j/JwtHelper.java +++ b/src/main/java/com/microsoft/aad/msal4j/JwtHelper.java @@ -21,7 +21,7 @@ final class JwtHelper { static ClientAssertion buildJwt(String clientId, final ClientCertificate credential, - final String jwtAudience) throws MsalClientException { + final String jwtAudience, boolean sendX5c) throws MsalClientException { if (StringHelper.isBlank(clientId)) { throw new IllegalArgumentException("clientId is null or empty"); } @@ -45,13 +45,16 @@ static ClientAssertion buildJwt(String clientId, final ClientCertificate credent SignedJWT jwt; try { - List certs = new ArrayList<>(); - for(String publicCertificate: credential.getEncodedPublicKeyCertificateOrCertificateChain()) { - certs.add(new Base64(publicCertificate)); + JWSHeader.Builder builder = new Builder(JWSAlgorithm.RS256); + + if(sendX5c){ + List certs = new ArrayList<>(); + for (String cert: credential.getEncodedPublicKeyCertificateChain()) { + certs.add(new Base64(cert)); + } + builder.x509CertChain(certs); } - JWSHeader.Builder builder = new Builder(JWSAlgorithm.RS256); - builder.x509CertChain(certs); builder.x509CertThumbprint(new Base64URL(credential.publicCertificateHash())); jwt = new SignedJWT(builder.build(), claimsSet);