Skip to content

Commit

Permalink
Add SNI support in Identity SDK (Azure#15561)
Browse files Browse the repository at this point in the history
  • Loading branch information
g2vinay authored Oct 2, 2020
1 parent 3b95ddb commit 4843871
Show file tree
Hide file tree
Showing 16 changed files with 206 additions and 34 deletions.
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 @@ -42,7 +42,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 @@ -82,7 +82,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 @@ -115,10 +115,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} -->
<include>net.minidev:json-smart:[2.3]</include> <!-- {x-include-update;net.minidev:json-smart;external_dependency} -->
</includes>
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()));
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

0 comments on commit 4843871

Please sign in to comment.