From 3e2633bb45298e7a7eec7933c4c4969fd38e1ca9 Mon Sep 17 00:00:00 2001 From: wdixon Date: Thu, 27 Jun 2013 17:39:52 -0500 Subject: [PATCH 1/2] Example Code for calling HTTPS Serivice with Client Cert Authentication using Jersey Libraries APICall is all-inclusive, should probably be made an abstract hierarchy. get() and post() could return other types by call, perhaps parsed from results. Example is just a simple example to show how to use ConnectionCredential and a subscription id to call the Manangement REST API KeyStoreType should be obvious SSLContextFactory just encapsulates SSLContextCreation. It can be changed to support other than just TLS if necessary. --- microsoft-azure-api/pom.xml | 11 ++- .../services/management/APICall.java | 86 +++++++++++++++++++ .../management/ConnectionCredential.java | 54 ++++++++++++ .../services/management/Example.java | 30 +++++++ .../services/management/KeyStoreType.java | 12 +++ .../management/SSLContextFactory.java | 55 ++++++++++++ 6 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/APICall.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/ConnectionCredential.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/Example.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/KeyStoreType.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/SSLContextFactory.java diff --git a/microsoft-azure-api/pom.xml b/microsoft-azure-api/pom.xml index 5fc0ac258437..d6cd08f733a4 100644 --- a/microsoft-azure-api/pom.xml +++ b/microsoft-azure-api/pom.xml @@ -53,7 +53,7 @@ com.sun.jersey jersey-client - 1.13 + 1.17.1 javax.xml.bind @@ -87,7 +87,7 @@ com.sun.jersey jersey-json - 1.13 + 1.17.1 commons-logging @@ -110,6 +110,13 @@ 1.46 test + + + com.google.guava + guava-io + r03 + + diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/APICall.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/APICall.java new file mode 100644 index 000000000000..d9084e934d52 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/APICall.java @@ -0,0 +1,86 @@ +/* + * + * The author contributes this code to the public domain, + * retaining no rights and incurring no responsibilities for its use in whole or in part. + */ + +package com.microsoft.windowsazure.services.management; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.text.MessageFormat; + +import javax.net.ssl.SSLContext; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.WebResource.Builder; +import com.sun.jersey.api.client.config.ClientConfig; +import com.sun.jersey.api.client.config.DefaultClientConfig; +import com.sun.jersey.client.urlconnection.HTTPSProperties; + +public class APICall { + + private final String url; + + private final SSLContext sslContext; + + public static String SERIVICE_VERSION_HEADER_KEY = "x-ms-version"; + + public static String SERIVICE_VERSION_HEADER_VALUE = "2013-03-01"; + + public static String SERVICE_CONTENT_TYPE_HEADER_KEY = "Content-Type"; + + public static String SERVICE_CONTENT_TYPE_HEADER_VALUE = "application/xml"; + + public APICall(String subscription, SSLContext context) { + // make this changeable in whatever way suits you..... + url = MessageFormat.format("https://management.core.windows.net/{0}/services/hostedservices", subscription); + sslContext = context; + } + + public APICall(String subscription, ConnectionCredential cred) throws GeneralSecurityException, IOException { + this(subscription, SSLContextFactory.createSSLContext(cred)); + } + + public String get() throws IOException, GeneralSecurityException { + Builder b = prepWebResourceBuilder(); + ClientResponse response = b.get(ClientResponse.class); + checkBadResponse(response); + return response.getEntity(String.class); + } + + public String post(String body) throws IOException, GeneralSecurityException { + Builder b = prepWebResourceBuilder(); + ClientResponse response = b.post(ClientResponse.class, body); + checkBadResponse(response); + return response.getEntity(String.class); + } + + private Builder prepWebResourceBuilder() throws IOException, GeneralSecurityException { + Client client = createClient(); + WebResource wr = client.resource(url); + return wr.header(SERVICE_CONTENT_TYPE_HEADER_KEY, SERVICE_CONTENT_TYPE_HEADER_VALUE).header( + SERIVICE_VERSION_HEADER_KEY, SERIVICE_VERSION_HEADER_VALUE); + } + + private Client createClient() throws IOException, GeneralSecurityException { + ClientConfig config = new DefaultClientConfig(); + config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, new HTTPSProperties(null, sslContext)); + Client client = Client.create(config); + return client; + } + + void checkBadResponse(ClientResponse response) { + if (response.getStatus() == 404) { + System.out.println("Entity doesn't Exist"); + } + else if (response.getStatus() != 200) { + System.out.println("Something went wrong...."); + System.out.println(response.getEntity(String.class)); + throw new RuntimeException("Failed : HTTP error code : " + response.getStatus()); + } + } + +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/ConnectionCredential.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/ConnectionCredential.java new file mode 100644 index 000000000000..97aa547fe809 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/ConnectionCredential.java @@ -0,0 +1,54 @@ +/* + * + * + * The author contributes this code to the public domain, + * retaining no rights and incurring no responsibilities for its use in whole or in part. + */ + +package com.microsoft.windowsazure.services.management; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import com.google.common.io.ByteStreams; + +public class ConnectionCredential { + private final byte[] keyStore; + + private final String keyPasswd; + + private final KeyStoreType keyStoreType; + + /** + * Creates a Credential from a keyStore. + * + * @param keyPass + * - keyStore password, key for store and the internal private key must be + * symmetric + * @param keys + * - an InputStream probably a FileInputStream from a keyStore, the jks containing + * your management cert + * @throws IOException + */ + ConnectionCredential(InputStream keys, String keyPass, KeyStoreType type) throws IOException { + keyPasswd = keyPass; + // Apache IOUtils could be used instead of google.common.io, + // or do a brute force read into List and then into an array. + keyStore = ByteStreams.toByteArray(keys); + keyStoreType = type; + } + + public KeyStoreType getKeyStoreType() { + return keyStoreType; + } + + public InputStream getKeyStore() { + return new ByteArrayInputStream(keyStore); + } + + public String getKeyPasswd() { + return keyPasswd; + } + +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/Example.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/Example.java new file mode 100644 index 000000000000..9e6a004bde8f --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/Example.java @@ -0,0 +1,30 @@ +/* + * + * The author contributes this code to the public domain, + * retaining no rights and incurring no responsibilities for its use in whole or in part. + */ +package com.microsoft.windowsazure.services.management; + +import java.io.FileInputStream; + +public class Example { + public static void main(String[] args) throws Exception { + ConnectionCredential cred = new ConnectionCredential( + // the .jks file (or other jks stream) containing your management cert bytes + new FileInputStream("../test.jks"), + // the password to the cert file and the private key inside it + "I won't tell you", KeyStoreType.jks); + APICall call = new APICall("my-subscription", cred); + System.out.println(call.get()); + //or... call.post(someBodyText); + + cred = new ConnectionCredential( + // the .pfx file (or other pfx stream) containing your management cert bytes + new FileInputStream("../test.pfx"), + // the password to the cert file and the private key inside it + "I won't tell you", KeyStoreType.pkcs12); + call = new APICall("my-subscription", cred); + System.out.println(call.get()); + + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/KeyStoreType.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/KeyStoreType.java new file mode 100644 index 000000000000..7d1d37b0901f --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/KeyStoreType.java @@ -0,0 +1,12 @@ +/* + * + * + * The author contributes this code to the public domain, + * retaining no rights and incurring no responsibilities for its use in whole or in part. + */ + +package com.microsoft.windowsazure.services.management; + +public enum KeyStoreType { + jks, pkcs12 +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/SSLContextFactory.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/SSLContextFactory.java new file mode 100644 index 000000000000..6767463a9384 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/SSLContextFactory.java @@ -0,0 +1,55 @@ +/* + * + * The author contributes this code to the public domain, + * retaining no rights and incurring no responsibilities for its use in whole or in part. + */ +package com.microsoft.windowsazure.services.management; + +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; + +/** + * Note: as it stands this provides a TLS SSLContext from jks and pfx stores. It could be modified to + * support other protocols (SSLv3, SSLv2, etc) and different key stores... + * + * Note that older .pfx files may need to be updated to pkcs12 format.... + * + */ +public class SSLContextFactory { + + public static SSLContext createSSLContext(ConnectionCredential cred) throws GeneralSecurityException, IOException { + return createSSLContext(cred.getKeyStore(), cred.getKeyPasswd(), cred.getKeyStoreType()); + } + + public static SSLContext createSSLContext(InputStream keyStoreStream, String keyStreamPasswd, KeyStoreType type) + throws GeneralSecurityException, IOException { + // Could Proxy KeyManagers to include only those with a specific alias for multi-cert file.... + KeyManager[] keyManagers = getKeyManagers(keyStoreStream, keyStreamPasswd, type.name()); + // note: may want to broaden this to SSLv3, SSLv2, SSL, etc... + SSLContext context = SSLContext.getInstance("TLS"); + // use default TrustManager and SecureRandom + context.init(keyManagers, null, null); + return context; + } + + private static KeyManager[] getKeyManagers(InputStream keyStoreStream, String keyStreamPasswd, String type) + throws IOException, GeneralSecurityException { + + KeyStore ks = KeyStore.getInstance(type); + ks.load(keyStoreStream, keyStreamPasswd.toCharArray()); + keyStoreStream.close(); + + String alg = KeyManagerFactory.getDefaultAlgorithm(); + KeyManagerFactory kmFact = KeyManagerFactory.getInstance(alg); + kmFact.init(ks, keyStreamPasswd.toCharArray()); + + return kmFact.getKeyManagers(); + } + +} \ No newline at end of file From 3afaf8f39fe6747f78afdf5518ca04877852c45b Mon Sep 17 00:00:00 2001 From: wdixon Date: Thu, 27 Jun 2013 18:08:53 -0500 Subject: [PATCH 2/2] Missed some finals on constants --- .../windowsazure/services/management/APICall.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/APICall.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/APICall.java index d9084e934d52..6762e730828f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/APICall.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/management/APICall.java @@ -26,13 +26,13 @@ public class APICall { private final SSLContext sslContext; - public static String SERIVICE_VERSION_HEADER_KEY = "x-ms-version"; + public static final String SERIVICE_VERSION_HEADER_KEY = "x-ms-version"; - public static String SERIVICE_VERSION_HEADER_VALUE = "2013-03-01"; + public static final String SERIVICE_VERSION_HEADER_VALUE = "2013-03-01"; - public static String SERVICE_CONTENT_TYPE_HEADER_KEY = "Content-Type"; + public static final String SERVICE_CONTENT_TYPE_HEADER_KEY = "Content-Type"; - public static String SERVICE_CONTENT_TYPE_HEADER_VALUE = "application/xml"; + public static final String SERVICE_CONTENT_TYPE_HEADER_VALUE = "application/xml"; public APICall(String subscription, SSLContext context) { // make this changeable in whatever way suits you.....