diff --git a/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/ADALClientWrapper.java b/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/ADALClientWrapper.java index 86d0616..57895aa 100644 --- a/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/ADALClientWrapper.java +++ b/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/ADALClientWrapper.java @@ -28,12 +28,12 @@ import java.util.Properties; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import javax.naming.ServiceUnavailableException; import javax.net.ssl.SSLSocketFactory; +import com.microsoft.aad.adal4j.AsymmetricKeyCredential; import com.microsoft.aad.adal4j.AuthenticationContext; import com.microsoft.aad.adal4j.AuthenticationResult; import com.microsoft.aad.adal4j.ClientCredential; @@ -41,107 +41,132 @@ /** * Azure Active Directory Authentication Client */ -public class ADALClientWrapper -{ +public class ADALClientWrapper { private String authority = "https://login.microsoftonline.com/"; private ClientCredential credential = null; + private AsymmetricKeyCredential asymmetricCredential = null; private ExecutorService service = null; private AuthenticationContext context = null; - + /** * Azure Active Directory Authentication Client - * @param aadTenant - Azure Active Directory tenant - * @param credential - Credential to use for authentication + * + * @param aadTenant + * - Azure Active Directory tenant + * @param credential + * - Credential to use for authentication * @throws IllegalArgumentException */ - public ADALClientWrapper(String aadTenant, ClientCredential credential, Properties props) throws IllegalArgumentException - { - if(aadTenant == null || aadTenant.isEmpty()) - { + public ADALClientWrapper(String aadTenant, ClientCredential credential, Properties props) throws IllegalArgumentException { + if (aadTenant == null || aadTenant.isEmpty()) { throw new IllegalArgumentException("The argument 'aadTenant' is missing"); } - - if(credential == null) - { - throw new IllegalArgumentException("The argument 'credential' is missing"); + + if (credential == null) { + throw new IllegalArgumentException("The argument 'credential' is missing"); } - - if(props != null) - { - this.authority = props.getProperty("AUTH_AUTHORITY",this.authority); + + if (props != null) { + this.authority = props.getProperty("AUTH_AUTHORITY", this.authority); } - + this.credential = credential; - this.service = Executors.newFixedThreadPool(1); - - try - { + this.service = new CurrentThreadExecutor(); + + try { context = new AuthenticationContext(this.authority + aadTenant, false, service); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("AUTH_AUTHORITY parameter was not formatted correctly which resulted in a MalformedURLException", e); + } + } + + /** + * Azure Active Directory Authentication Client + * + * @param aadTenant + * - Azure Active Directory tenant + * @param credential + * - Credential to use for authentication + * @throws IllegalArgumentException + */ + public ADALClientWrapper(String aadTenant, AsymmetricKeyCredential credential, Properties props) throws IllegalArgumentException { + if (aadTenant == null || aadTenant.isEmpty()) { + throw new IllegalArgumentException("The argument 'aadTenant' is missing"); + } + + if (credential == null) { + throw new IllegalArgumentException("The argument 'credential' is missing"); + } + + if (props != null) { + this.authority = props.getProperty("AUTH_AUTHORITY", this.authority); } - catch(MalformedURLException e) - { + + this.asymmetricCredential = credential; + this.service = new CurrentThreadExecutor(); + + try { + context = new AuthenticationContext(this.authority + aadTenant, false, service); + } catch (MalformedURLException e) { throw new IllegalArgumentException("AUTH_AUTHORITY parameter was not formatted correctly which resulted in a MalformedURLException", e); } } - + /** * Sets the SSL factory to be used on the HTTP client for authentication. + * * @param factory */ - public void SetSslSocketFactory(SSLSocketFactory factory) throws IllegalArgumentException - { - if(factory == null) - { + public void SetSslSocketFactory(SSLSocketFactory factory) throws IllegalArgumentException { + if (factory == null) { throw new IllegalArgumentException("The argument 'factory' is missing."); } - + this.context.setSslSocketFactory(factory); } - + /** * Sets the proxy to be used by the ADAL library for any HTTP or HTTPS calls + * * @param proxy */ - public void SetProxy(Proxy proxy) - { + public void SetProxy(Proxy proxy) { this.context.setProxy(proxy); } - + /** - * Gets an access token from AAD for the specified resource using the ClientCredential passed in. - * @param resource Resource to get token for. - * @param credential Credential to use to acquire token. + * Gets an access token from AAD for the specified resource using the + * ClientCredential passed in. + * + * @param resource + * Resource to get token for. * @return - * @throws ExecutionException + * @throws ExecutionException * @throws IllegalArgumentException - * @throws InterruptedException - * @throws ServiceUnavailableException + * @throws InterruptedException + * @throws ServiceUnavailableException */ - public AuthenticationResult getAccessTokenFromCredential(String resource) - throws ServiceUnavailableException, InterruptedException, ExecutionException, IllegalArgumentException - { - if(resource == null || resource.isEmpty()) - { + public AuthenticationResult getAccessTokenFromCredential(String resource) + throws ServiceUnavailableException, InterruptedException, ExecutionException, IllegalArgumentException { + if (resource == null || resource.isEmpty()) { throw new IllegalArgumentException("The argument 'resource' is missing"); } - + AuthenticationResult result = null; - - Future future = context.acquireToken(resource, credential, null); + + Future future; + if (credential != null) { + future = context.acquireToken(resource, credential, null); + } else { + future = context.acquireToken(resource, asymmetricCredential, null); + } result = future.get(); - if (result == null) - { + if (result == null) { throw new ServiceUnavailableException("Authentication result was null"); } - + return result; } - - @Override - public void finalize() - { - service.shutdown(); - } } \ No newline at end of file diff --git a/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/CurrentThreadExecutor.java b/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/CurrentThreadExecutor.java new file mode 100644 index 0000000..7544e78 --- /dev/null +++ b/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/CurrentThreadExecutor.java @@ -0,0 +1,49 @@ +package com.microsoft.intune.scepvalidation; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * This is a non-thread safe ExecutorService implementation that processes all + * submit() calls immediately rather than running on them on a separate thread. + * Since ADALClientWrapper doesn't take advantage of concurrency, this should be + * more efficient, since it doesn't create a new thread with every new + * ADALClientWrapper instance. + */ +class CurrentThreadExecutor extends AbstractExecutorService { + + boolean isShutdown = false; + + @Override + public void shutdown() { + isShutdown = true; + } + + @Override + public List shutdownNow() { + return Collections.emptyList(); + } + + @Override + public boolean isShutdown() { + return isShutdown; + } + + @Override + public boolean isTerminated() { + return isShutdown; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return false; + } + + @Override + public void execute(Runnable command) { + command.run(); + } + +} diff --git a/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/IntuneClient.java b/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/IntuneClient.java index fcf095e..39a8356 100644 --- a/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/IntuneClient.java +++ b/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/IntuneClient.java @@ -29,6 +29,8 @@ import java.net.PasswordAuthentication; import java.net.Proxy; import java.net.UnknownHostException; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -69,6 +71,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.microsoft.aad.adal4j.AsymmetricKeyCredential; import com.microsoft.aad.adal4j.AuthenticationException; import com.microsoft.aad.adal4j.AuthenticationResult; import com.microsoft.aad.adal4j.ClientCredential; @@ -86,6 +89,7 @@ class IntuneClient protected String intuneTenant; protected ClientCredential aadCredential; + protected AsymmetricKeyCredential asymmetricAadCredential; protected ADALClientWrapper authClient; protected SSLSocketFactory sslSocketFactory = null; @@ -110,6 +114,44 @@ public IntuneClient(Properties configProperties) throws IllegalArgumentException this(configProperties, null, null); } + /** + * Construct an Intune client which uses asymmetric key authentication. + * + * @param certificate client certificate + * @param privateKey client private key + * @param configProperties configuration that contains the usual fields, + * but without AAD_APP_KEY, since this instance uses the private key instead + * @param authClient previously established wrapper around client credentials. + * May be NULL. + * @param httpClientBuilder alternative http client builder. May be NULL. + */ + public IntuneClient(X509Certificate certificate, PrivateKey privateKey, Properties configProperties, ADALClientWrapper authClient, HttpClientBuilder httpClientBuilder) { + + if(configProperties == null) + { + throw new IllegalArgumentException("The argument 'configProperties' is missing"); + } + + // Read required properties + String azureAppId = configProperties.getProperty("AAD_APP_ID"); + if(azureAppId == null || azureAppId.isEmpty()) + { + throw new IllegalArgumentException("The argument 'AAD_APP_ID' is missing"); + } + + this.intuneTenant = configProperties.getProperty("TENANT"); + if(this.intuneTenant == null || this.intuneTenant.isEmpty()) + { + throw new IllegalArgumentException("The argument 'TENANT' is missing"); + } + + // Instantiate asymmetric ADAL Client + this.asymmetricAadCredential = AsymmetricKeyCredential.create(azureAppId, privateKey, certificate); + this.authClient = authClient == null ? new ADALClientWrapper(this.intuneTenant, this.asymmetricAadCredential, configProperties) : authClient; + + commonConfiguration(configProperties, httpClientBuilder); + } + /** * Constructs an IntuneClient object. This is meant to be used for unit tests for dependency injection. * @param configProperties @@ -142,17 +184,20 @@ public IntuneClient(Properties configProperties, ADALClientWrapper authClient, H { throw new IllegalArgumentException("The argument 'TENANT' is missing"); } + // Instantiate ADAL Client + this.aadCredential = new ClientCredential(azureAppId, azureAppKey); + this.authClient = authClient == null ? new ADALClientWrapper(this.intuneTenant, this.aadCredential, configProperties) : authClient; + commonConfiguration(configProperties, httpClientBuilder); + } + + private void commonConfiguration(Properties configProperties, HttpClientBuilder httpClientBuilder) { // Read optional properties this.intuneAppId = configProperties.getProperty("INTUNE_APP_ID", this.intuneAppId); this.intuneResourceUrl = configProperties.getProperty("INTUNE_RESOURCE_URL", this.intuneResourceUrl); this.graphApiVersion = configProperties.getProperty("GRAPH_API_VERSION", this.graphApiVersion); this.graphResourceUrl = configProperties.getProperty("GRAPH_RESOURCE_URL", this.graphResourceUrl); - // Instantiate ADAL Client - this.aadCredential = new ClientCredential(azureAppId, azureAppKey); - - this.authClient = authClient == null ? new ADALClientWrapper(this.intuneTenant, this.aadCredential, configProperties) : authClient; this.httpClientBuilder = httpClientBuilder == null ? this.httpClientBuilder : httpClientBuilder; proxyHost = configProperties.getProperty("PROXY_HOST"); diff --git a/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/IntuneRevocationClient.java b/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/IntuneRevocationClient.java index d6e0a62..6458484 100644 --- a/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/IntuneRevocationClient.java +++ b/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/IntuneRevocationClient.java @@ -23,6 +23,8 @@ package com.microsoft.intune.scepvalidation; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.List; import java.util.Properties; @@ -57,6 +59,16 @@ public class IntuneRevocationClient extends IntuneClient final Logger log = LoggerFactory.getLogger(IntuneRevocationClient.class); + + public IntuneRevocationClient(X509Certificate certificate, PrivateKey privateKey, Properties configProperties) { + this(certificate, privateKey, configProperties, null, null); + } + + public IntuneRevocationClient(X509Certificate certificate, PrivateKey privateKey, Properties configProperties, ADALClientWrapper adalClient, HttpClientBuilder httpClientBuilder) throws IllegalArgumentException { + super(certificate, privateKey, configProperties, adalClient, httpClientBuilder); + commonInitialization(configProperties); + } + /** * IntuneScepService Client constructor * @param configProperties Properties object containing client configuration information. @@ -77,7 +89,10 @@ public IntuneRevocationClient(Properties configProperties) throws IllegalArgumen public IntuneRevocationClient(Properties configProperties, ADALClientWrapper adalClient, HttpClientBuilder httpClientBuilder) throws IllegalArgumentException { super(configProperties, adalClient, httpClientBuilder); - + commonInitialization(configProperties); + } + + private void commonInitialization(Properties configProperties) { if(configProperties == null) { throw new IllegalArgumentException("The argument 'configProperties' is missing"); diff --git a/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/IntuneScepServiceClient.java b/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/IntuneScepServiceClient.java index 783df88..e7df581 100644 --- a/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/IntuneScepServiceClient.java +++ b/src/CsrValidation/java/lib/src/main/java/com/microsoft/intune/scepvalidation/IntuneScepServiceClient.java @@ -23,6 +23,8 @@ package com.microsoft.intune.scepvalidation; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Properties; import java.util.UUID; @@ -61,6 +63,16 @@ public IntuneScepServiceClient(Properties configProperties) throws IllegalArgume { this(configProperties, null, null); } + + /** + * IntuneScepService Client constructor for asymmetric key authentcation + * @param configProperties Properties object containing client configuration information. + * @throws IllegalArgumentException + */ + public IntuneScepServiceClient(X509Certificate certificate, PrivateKey key, Properties configProperties) throws IllegalArgumentException + { + this(certificate, key, configProperties, null, null); + } /** * IntuneScepService Client constructor meant for dependency injection @@ -72,7 +84,23 @@ public IntuneScepServiceClient(Properties configProperties) throws IllegalArgume public IntuneScepServiceClient(Properties configProperties, ADALClientWrapper adalClient, HttpClientBuilder httpClientBuilder) throws IllegalArgumentException { super(configProperties, adalClient, httpClientBuilder); - + commonInitialization(configProperties); + } + + /** + * IntuneScepService Client constructor meant for dependency injection + * @param configProperties + * @param adalClient + * @param httpClientBuilder + * @throws IllegalArgumentException + */ + public IntuneScepServiceClient(X509Certificate certificate, PrivateKey key, Properties configProperties, ADALClientWrapper adalClient, HttpClientBuilder httpClientBuilder) throws IllegalArgumentException + { + super(certificate, key, configProperties, adalClient, httpClientBuilder); + commonInitialization(configProperties); + } + + private void commonInitialization(Properties configProperties) { if(configProperties == null) { throw new IllegalArgumentException("The argument 'configProperties' is missing");