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

sendX5c api #285

Merged
merged 1 commit into from
Sep 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -184,13 +185,16 @@ private ClientAssertion buildShortJwt(String clientId,
.build();
SignedJWT jwt;
try {
JWSHeader.Builder builder = new JWSHeader.Builder(JWSAlgorithm.RS256);

List<Base64> 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);
Expand Down
31 changes: 14 additions & 17 deletions src/main/java/com/microsoft/aad/msal4j/ClientCertificate.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,10 @@ final class ClientCertificate implements IClientCertificate {
@Getter
private final PrivateKey privateKey;

private final X509Certificate publicKeyCertificate;

private final List<X509Certificate> publicKeyCertificateChain;

ClientCertificate
(PrivateKey privateKey, X509Certificate publicKeyCertificate, List<X509Certificate> publicKeyCertificateChain) {
(PrivateKey privateKey, List<X509Certificate> publicKeyCertificateChain) {
if (privateKey == null) {
throw new NullPointerException("PrivateKey is null or empty");
}
Expand Down Expand Up @@ -68,26 +66,21 @@ final class ClientCertificate implements IClientCertificate {
" sun.security.mscapi.RSAPrivateKey");
}

this.publicKeyCertificate = publicKeyCertificate;
this.publicKeyCertificateChain = publicKeyCertificateChain;
}

public String publicCertificateHash()
throws CertificateEncodingException, NoSuchAlgorithmException {

return Base64.getEncoder().encodeToString(ClientCertificate
.getHash(this.publicKeyCertificate.getEncoded()));
.getHash(publicKeyCertificateChain.get(0).getEncoded()));
}

public List<String> getEncodedPublicKeyCertificateOrCertificateChain() throws CertificateEncodingException {
public List<String> getEncodedPublicKeyCertificateChain() throws CertificateEncodingException {
List<String> 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;
}
Expand All @@ -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<X509Certificate> publicKeyCertificateChain = null;
ArrayList<X509Certificate> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<IAuthenticationResult> acquireToken(ClientCredentialParameters parameters) {

Expand Down Expand Up @@ -62,6 +68,7 @@ public CompletableFuture<IAuthenticationResult> acquireToken(OnBehalfOfParameter

private ConfidentialClientApplication(Builder builder) {
super(builder);
sendX5c = builder.sendX5c;

log = LoggerFactory.getLogger(ConfidentialClientApplication.class);

Expand Down Expand Up @@ -103,7 +110,8 @@ private ClientAuthentication buildValidClientCertificateAuthority() {
ClientAssertion clientAssertion = JwtHelper.buildJwt(
clientId(),
clientCertificate,
this.authenticationAuthority.selfSignedJwtAudience());
this.authenticationAuthority.selfSignedJwtAudience(),
sendX5c);
return createClientAuthFromClientAssertion(clientAssertion);
}

Expand Down Expand Up @@ -136,11 +144,26 @@ public static class Builder extends AbstractClientApplicationBase.Builder<Builde

private IClientCredential clientCredential;

private boolean sendX5c = true;

private Builder(String clientId, IClientCredential clientCredential) {
super(clientId);
this.clientCredential = clientCredential;
}

/**
* Specifies if the x5c claim (public key of the certificate) should be sent to the STS.
* Default value is true
*
* @param val true if the x5c should be sent. Otherwise false
* @return instance of the Builder on which method was called
*/
public ConfidentialClientApplication.Builder sendX5c(boolean val) {
this.sendX5c = val;

return self();
}

@Override
public ConfidentialClientApplication build() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ public interface IClientCertificate extends IClientCredential{
* @return base64 encoded string
* @throws CertificateEncodingException if an encoding error occurs
*/
List<String> getEncodedPublicKeyCertificateOrCertificateChain() throws CertificateEncodingException;
List<String> getEncodedPublicKeyCertificateChain() throws CertificateEncodingException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 9 additions & 6 deletions src/main/java/com/microsoft/aad/msal4j/JwtHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand All @@ -45,13 +45,16 @@ static ClientAssertion buildJwt(String clientId, final ClientCertificate credent

SignedJWT jwt;
try {
List<Base64> certs = new ArrayList<>();
for(String publicCertificate: credential.getEncodedPublicKeyCertificateOrCertificateChain()) {
certs.add(new Base64(publicCertificate));
JWSHeader.Builder builder = new Builder(JWSAlgorithm.RS256);

if(sendX5c){
List<Base64> 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);
Expand Down