Skip to content
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
4 changes: 2 additions & 2 deletions eng/versioning/external_dependencies.txt
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ com.microsoft.azure:azure-mgmt-resources;1.3.0
com.microsoft.azure:azure-mgmt-search;1.24.1
com.microsoft.azure:azure-mgmt-storage;1.3.0
com.microsoft.azure:azure-storage;8.0.0
com.microsoft.azure:msal4j;1.6.2
com.microsoft.azure:msal4j;1.7.1
com.microsoft.azure:msal4j-persistence-extension;1.0.0
com.sun.activation:jakarta.activation;1.2.2
io.opentelemetry:opentelemetry-api;0.6.0
Expand Down Expand Up @@ -212,7 +212,7 @@ io.dropwizard.metrics:metrics-core;4.1.12.1
io.dropwizard.metrics:metrics-graphite;4.1.12.1
io.dropwizard.metrics:metrics-jvm;4.1.12.1
io.reactivex.rxjava2:rxjava;2.2.19
net.java.dev.jna:jna-platform;5.4.0
net.java.dev.jna:jna-platform;5.6.0
net.jonathangiles.tools:dependencyChecker-maven-plugin;1.0.4
net.jonathangiles.tools:whitelistgenerator-maven-plugin;1.0.2
org.apache.commons:commons-collections4;4.2
Expand Down
2 changes: 1 addition & 1 deletion sdk/eventhubs/microsoft-azure-eventhubs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.6.2</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
<version>1.7.1</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
<scope>test</scope>
</dependency>
<dependency>
Expand Down
8 changes: 4 additions & 4 deletions sdk/identity/azure-identity/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.6.2</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
<version>1.7.1</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
Expand Down Expand Up @@ -78,7 +78,7 @@
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.4.0</version> <!-- {x-version-update;net.java.dev.jna:jna-platform;external_dependency} -->
<version>5.6.0</version> <!-- {x-version-update;net.java.dev.jna:jna-platform;external_dependency} -->
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
Expand Down Expand Up @@ -111,10 +111,10 @@
<rules>
<bannedDependencies>
<includes>
<include>com.microsoft.azure:msal4j:[1.6.2]</include> <!-- {x-include-update;com.microsoft.azure:msal4j;external_dependency} -->
<include>com.microsoft.azure:msal4j:[1.7.1]</include> <!-- {x-include-update;com.microsoft.azure:msal4j;external_dependency} -->
<include>com.microsoft.azure:msal4j-persistence-extension:[1.0.0]</include> <!-- {x-include-update;com.microsoft.azure:msal4j-persistence-extension;external_dependency} -->
<include>com.nimbusds:oauth2-oidc-sdk:[7.1.1]</include> <!-- {x-include-update;com.nimbusds:oauth2-oidc-sdk;external_dependency} -->
<include>net.java.dev.jna:jna-platform:[5.4.0]</include> <!-- {x-include-update;net.java.dev.jna:jna-platform;external_dependency} -->
<include>net.java.dev.jna:jna-platform:[5.6.0]</include> <!-- {x-include-update;net.java.dev.jna:jna-platform;external_dependency} -->
<include>org.linguafranca.pwdb:KeePassJava2:[2.1.4]</include> <!-- {x-include-update;org.linguafranca.pwdb:KeePassJava2;external_dependency} -->
</includes>
</bannedDependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ public ClientCertificateCredentialBuilder enablePersistentCache() {
return this;
}

/**
* Specifies if the x5c claim (public key of the certificate) should be sent as part of the authentication request
* and enable subject name / issuer based authentication. The default value is false.
*
* @param includeX5c the flag to indicate if x5c should be sent as part of authentication request.
* @return An updated instance of this builder.
*/
public ClientCertificateCredentialBuilder includeX5c(boolean includeX5c) {
this.identityClientOptions.setIncludeX5c(includeX5c);
return this;
}

/**
* Creates a new {@link ClientCertificateCredential} with the current configurations.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,9 @@ public Mono<AuthenticationRecord> authenticate() {
private AccessToken updateCache(MsalToken msalToken) {
cachedToken.set(
new MsalAuthenticationAccount(
new AuthenticationRecord(msalToken.getAuthenticationResult(),
identityClient.getTenantId(), identityClient.getClientId())));
new AuthenticationRecord(msalToken.getAuthenticationResult(),
identityClient.getTenantId(), identityClient.getClientId()),
msalToken.getAccount().getTenantProfiles()));
return msalToken;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,9 @@ public Mono<AuthenticationRecord> authenticate() {
private AccessToken updateCache(MsalToken msalToken) {
cachedToken.set(
new MsalAuthenticationAccount(
new AuthenticationRecord(msalToken.getAuthenticationResult(),
identityClient.getTenantId(), identityClient.getClientId())));
new AuthenticationRecord(msalToken.getAuthenticationResult(),
identityClient.getTenantId(), identityClient.getClientId()),
msalToken.getAccount().getTenantProfiles()));
Comment on lines +124 to +126
Copy link
Member

Choose a reason for hiding this comment

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

nit: The formatting looks a bit odd. Probably moving the instantiation of AuthenticationRecord outside of set() would make it more readable too.

Copy link
Member Author

Choose a reason for hiding this comment

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

agreed,
will update it.

return msalToken;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@ public Mono<AuthenticationRecord> authenticate() {
private AccessToken updateCache(MsalToken msalToken) {
cachedToken.set(
new MsalAuthenticationAccount(
new AuthenticationRecord(msalToken.getAuthenticationResult(),
identityClient.getTenantId(), identityClient.getClientId())));
new AuthenticationRecord(msalToken.getAuthenticationResult(),
identityClient.getTenantId(), identityClient.getClientId()),
msalToken.getAccount().getTenantProfiles()));
return msalToken;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
Expand Down Expand Up @@ -175,9 +177,15 @@ private ConfidentialClientApplication getConfidentialClientApplication() {
if (certificatePassword == null) {
byte[] pemCertificateBytes = Files.readAllBytes(Paths.get(certificatePath));

credential = ClientCredentialFactory.createFromCertificate(
CertificateUtil.privateKeyFromPem(pemCertificateBytes),
CertificateUtil.publicKeyFromPem(pemCertificateBytes));
List<X509Certificate> x509CertificateList = CertificateUtil.publicKeyFromPem(pemCertificateBytes);
PrivateKey privateKey = CertificateUtil.privateKeyFromPem(pemCertificateBytes);
if (x509CertificateList.size() == 1) {
credential = ClientCredentialFactory.createFromCertificate(
privateKey, x509CertificateList.get(0));
} else {
credential = ClientCredentialFactory.createFromCertificateChain(
privateKey, x509CertificateList);
}
} else {
credential = ClientCredentialFactory.createFromCertificate(
new FileInputStream(certificatePath), certificatePassword);
Expand All @@ -190,6 +198,7 @@ private ConfidentialClientApplication getConfidentialClientApplication() {
throw logger.logExceptionAsError(
new IllegalArgumentException("Must provide client secret or client certificate path"));
}

ConfidentialClientApplication.Builder applicationBuilder =
ConfidentialClientApplication.builder(clientId, credential);
try {
Expand All @@ -198,6 +207,8 @@ private ConfidentialClientApplication getConfidentialClientApplication() {
throw logger.logExceptionAsWarning(new IllegalStateException(e));
}

applicationBuilder.sendX5c(options.isIncludeX5c());

initializeHttpPipelineAdapter();
if (httpPipelineAdapter != null) {
applicationBuilder.httpClient(httpPipelineAdapter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public final class IdentityClientOptions {
private boolean allowUnencryptedCache;
private boolean sharedTokenCacheEnabled;
private String keePassDatabasePath;
private boolean includeX5c;
private AuthenticationRecord authenticationRecord;

/**
Expand Down Expand Up @@ -242,6 +243,28 @@ public IdentityClientOptions setAuthenticationRecord(AuthenticationRecord authen
return this;
}


/**
* Get the status whether x5c claim (public key of the certificate) should be included as part of the authentication
* request or not.
* @return the status of x5c claim inclusion.
*/
public boolean isIncludeX5c() {
return includeX5c;
}

/**
* Specifies if the x5c claim (public key of the certificate) should be sent as part of the authentication request.
* The default value is false.
*
* @param includeX5c true if the x5c should be sent. Otherwise false
* @return The updated identity client options.
*/
public IdentityClientOptions setIncludeX5c(boolean includeX5c) {
this.includeX5c = includeX5c;
return this;
}

/**
* Get the configured {@link AuthenticationRecord}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,24 @@

import com.azure.identity.AuthenticationRecord;
import com.microsoft.aad.msal4j.IAccount;
import com.microsoft.aad.msal4j.ITenantProfile;

import java.util.Map;

public class MsalAuthenticationAccount implements IAccount {
private AuthenticationRecord authenticationRecord;
private Map<String, ITenantProfile> tenantProfiles;

public MsalAuthenticationAccount(AuthenticationRecord authenticationRecord) {
this.authenticationRecord = authenticationRecord;
}

public MsalAuthenticationAccount(AuthenticationRecord authenticationRecord,
Map<String, ITenantProfile> tenantProfiles) {
this.authenticationRecord = authenticationRecord;
this.tenantProfiles = tenantProfiles;
}

@Override
public String homeAccountId() {
return authenticationRecord.getHomeAccountId();
Expand All @@ -28,6 +38,11 @@ public String username() {
return authenticationRecord.getUsername();
}

@Override
public Map<String, ITenantProfile> getTenantProfiles() {
return tenantProfiles;
}

public AuthenticationRecord getAuthenticationRecord() {
return authenticationRecord;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -54,24 +56,31 @@ public static PrivateKey privateKeyFromPem(byte[] pem) {
}

/**
* Extracts the X509Certificate certificate from a PEM certificate.
* Extracts the X509Certificate certificate/certificate-chain from a PEM certificate.
* @param pem the contents of a PEM certificate.
* @return the X509Certificate certificate
* @return the {@link List} of X509Certificate certificate
*/
public static X509Certificate publicKeyFromPem(byte[] pem) {
Pattern pattern = Pattern.compile("(?s)-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----");
public static List<X509Certificate> publicKeyFromPem(byte[] pem) {
Pattern pattern = Pattern.compile("(?s)-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----");
Matcher matcher = pattern.matcher(new String(pem, StandardCharsets.UTF_8));
if (!matcher.find()) {

List<X509Certificate> x509CertificateList = new ArrayList<>();
while (matcher.find()) {
try {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
InputStream stream = new ByteArrayInputStream(matcher.group().getBytes(StandardCharsets.UTF_8));
x509CertificateList.add((X509Certificate) factory.generateCertificate(stream));
} catch (CertificateException e) {
throw LOGGER.logExceptionAsError(new IllegalStateException(e));
}
}

if (x509CertificateList.size() == 0) {
throw LOGGER.logExceptionAsError(new IllegalArgumentException(
"PEM certificate provided does not contain -----BEGIN CERTIFICATE-----END CERTIFICATE----- block"));
}
try {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
InputStream stream = new ByteArrayInputStream(matcher.group().getBytes(StandardCharsets.UTF_8));
return (X509Certificate) factory.generateCertificate(stream);
} catch (CertificateException e) {
throw LOGGER.logExceptionAsError(new IllegalStateException(e));
}

return x509CertificateList;
}

private CertificateUtil() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.security.cert.X509Certificate;
import java.sql.Date;
import java.time.LocalDate;
import java.util.List;

@RunWith(PowerMockRunner.class)
public class CertificateUtilTests {
Expand All @@ -24,10 +25,20 @@ public class CertificateUtilTests {
public void testPublicKey() throws Exception {
String pemPath = getPath("certificate.pem");
byte[] pemCertificateBytes = Files.readAllBytes(Paths.get(pemPath));
X509Certificate x509Certificate = CertificateUtil.publicKeyFromPem(pemCertificateBytes);
x509Certificate.checkValidity(Date.valueOf(LocalDate.of(2025, 12, 25)));
List<X509Certificate> x509CertificateList = CertificateUtil.publicKeyFromPem(pemCertificateBytes);
x509CertificateList.get(0).checkValidity(Date.valueOf(LocalDate.of(2025, 12, 25)));
}

@Test(expected = CertificateExpiredException.class)
public void testPublicKeyChain() throws Exception {
String pemPath = getPath("cert-chain.pem");
byte[] pemCertificateBytes = Files.readAllBytes(Paths.get(pemPath));
List<X509Certificate> x509CertificateList = CertificateUtil.publicKeyFromPem(pemCertificateBytes);
Assert.assertEquals(2, x509CertificateList.size());
x509CertificateList.get(0).checkValidity(Date.valueOf(LocalDate.of(4025, 12, 25)));
}


@Test
public void testPrivateKey() throws Exception {
String pemPath = getPath("key.pem");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
import com.azure.identity.implementation.MsalToken;
import com.microsoft.aad.msal4j.IAccount;
import com.microsoft.aad.msal4j.IAuthenticationResult;
import com.microsoft.aad.msal4j.ITenantProfile;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

Expand Down Expand Up @@ -54,6 +56,11 @@ public String environment() {
public String username() {
return "testuser";
}

@Override
public Map<String, ITenantProfile> getTenantProfiles() {
return null;
}
};
}

Expand Down
Loading