diff --git a/README.markdown b/README.markdown index cf0ec96..a79ab01 100644 --- a/README.markdown +++ b/README.markdown @@ -38,12 +38,20 @@ To send a notification, you can do it in two steps: .build(); String subscriptionUri = "https://..../" service.push(subscriptionUri, notification); + +3. Create a ssl connection + + MpnsService service = MPNS.newService() + .withCert(WIN_MPNS_KEYSTORE_PATH, WIN_MPNS_CERTIFICATE_PASS, "JKS", KeyManagerFactory.getDefaultAlgorithm()) + .build(); + The common name of certificate used in creating the keystore should be used during registering for the windows push service + (opening the push channel) by the windows native app. That's it! Features In the Making --------------------------- - * Authenticated Connections + * Authenticated Connections(DONE) * Auto retries (exponential back-off feature) * More testing! diff --git a/src/main/java/com/notnoop/mpns/MpnsServiceBuilder.java b/src/main/java/com/notnoop/mpns/MpnsServiceBuilder.java index bc98684..6035d57 100644 --- a/src/main/java/com/notnoop/mpns/MpnsServiceBuilder.java +++ b/src/main/java/com/notnoop/mpns/MpnsServiceBuilder.java @@ -30,17 +30,30 @@ */ package com.notnoop.mpns; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import javax.net.ssl.SSLContext; + import org.apache.http.HttpHost; import org.apache.http.client.HttpClient; import org.apache.http.conn.params.ConnRoutePNames; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; -import com.notnoop.mpns.internal.*; +import com.notnoop.mpns.exceptions.InvalidSSLConfig; +import com.notnoop.mpns.exceptions.RuntimeIOException; +import com.notnoop.mpns.internal.AbstractMpnsService; +import com.notnoop.mpns.internal.MpnsPooledService; +import com.notnoop.mpns.internal.MpnsQueuedService; +import com.notnoop.mpns.internal.MpnsServiceImpl; +import com.notnoop.mpns.internal.Utilities; /** * The class is used to create instances of {@link MpnsService}. @@ -58,6 +71,9 @@ * */ public class MpnsServiceBuilder { + + private SSLContext sslContext; + private int pooledMax = 1; private ExecutorService executor = null; @@ -72,6 +88,36 @@ public class MpnsServiceBuilder { * Constructs a new instance of {@code MpnsServiceBuilder} */ public MpnsServiceBuilder() { } + + public MpnsServiceBuilder withCert(String fileName, String password, String ksType, String kAlgo) + throws RuntimeIOException, InvalidSSLConfig + { + FileInputStream stream = null; + try { + stream = new FileInputStream(fileName); + return withCert(stream, password, ksType, kAlgo); + } catch (FileNotFoundException e) { + throw new RuntimeIOException(e); + } finally { + Utilities.close(stream); + } + } + + public MpnsServiceBuilder withCert(InputStream stream, String password, String ksType, String kAlgo) + throws InvalidSSLConfig + { + if (password == null || password.isEmpty()) { + throw new IllegalArgumentException("Passwords must be specified." + + "Oracle Java SDK does not support passwordless p12 certificates"); + } + + return withSSLContext(Utilities.newSSLContext(stream, password,ksType, kAlgo)); + } + + public MpnsServiceBuilder withSSLContext(SSLContext sslContext) { + this.sslContext = sslContext; + return this; + } /** * Specify the address of the HTTP proxy the connection should @@ -182,9 +228,17 @@ public MpnsService build() { if (httpClient != null) { client = httpClient; } else if (pooledMax == 1) { - client = new DefaultHttpClient(); + client = new DefaultHttpClient(); + + SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext); + Scheme sch = new Scheme("https", 443, socketFactory); + client.getConnectionManager().getSchemeRegistry().register(sch); } else { client = new DefaultHttpClient(Utilities.poolManager(pooledMax)); + + SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext); + Scheme sch = new Scheme("https", 443, socketFactory); + client.getConnectionManager().getSchemeRegistry().register(sch); } if (proxy != null) { diff --git a/src/main/java/com/notnoop/mpns/internal/Utilities.java b/src/main/java/com/notnoop/mpns/internal/Utilities.java index 4a73934..38e0beb 100644 --- a/src/main/java/com/notnoop/mpns/internal/Utilities.java +++ b/src/main/java/com/notnoop/mpns/internal/Utilities.java @@ -30,20 +30,33 @@ */ package com.notnoop.mpns.internal; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.security.KeyStore; +import java.util.Enumeration; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.notnoop.mpns.MpnsDelegate; import com.notnoop.mpns.MpnsNotification; import com.notnoop.mpns.MpnsResponse; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.UnsupportedEncodingException; +import com.notnoop.mpns.exceptions.InvalidSSLConfig; public final class Utilities { + private static Logger logger = LoggerFactory.getLogger(Utilities.class); private Utilities() { throw new AssertionError("Uninstantiable class"); } /** @@ -115,7 +128,7 @@ public static MpnsResponse logicalResponseFor(HttpResponse response) { } if (r.getDeviceConnectionStatus() != null - && !r.getNotificationStatus().equals(headerValue(response, "X-DeviceConnectionStatus"))) { + && !r.getDeviceConnectionStatus().equals(headerValue(response, "X-DeviceConnectionStatus"))) { continue; } @@ -131,16 +144,84 @@ public static MpnsResponse logicalResponseFor(HttpResponse response) { assert false; return null; } + + public static void close(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (IOException e) { + logger.debug("error while closing resource", e); + } + } public static void fireDelegate(MpnsNotification message, HttpResponse response, MpnsDelegate delegate) { if (delegate != null) { MpnsResponse r = Utilities.logicalResponseFor(response); - if (r.isSuccessful()) { - delegate.messageSent(message, r); - } else { - delegate.messageFailed(message, r); + if(r!=null) { + if (r.isSuccessful()) { + delegate.messageSent(message, r); + } else { + delegate.messageFailed(message, r); + } } } } + + public static SSLSocketFactory newSSLSocketFactory(InputStream cert, String password, + String ksType, String ksAlgorithm) throws InvalidSSLConfig + { + SSLContext context = newSSLContext(cert, password, ksType, ksAlgorithm); + return context.getSocketFactory(); + } + + // Create a trust manager that does not validate certificate chains + private static TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() + { + public java.security.cert.X509Certificate[] getAcceptedIssuers() + { + return null; + } + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) + {} + + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) + {} + } + }; + + public static SSLContext newSSLContext(InputStream cert, String password, + String ksType, String ksAlgorithm) throws InvalidSSLConfig + { + try { + KeyStore ks = KeyStore.getInstance(ksType); + ks.load(cert, password.toCharArray()); + + // Get a KeyManager and initialize it + KeyManagerFactory kmf = KeyManagerFactory.getInstance(ksAlgorithm); + kmf.init(ks, password.toCharArray()); + + // Get a TrustManagerFactory and init with KeyStore + TrustManagerFactory tmf = TrustManagerFactory.getInstance(ksAlgorithm); + tmf.init(ks); + + // Get the SSLContext to help create SSLSocketFactory + SSLContext sslc = SSLContext.getInstance("TLS"); + sslc.init(kmf.getKeyManagers(), trustAllCerts, null); + + logger.debug("SSL context read with following properties:"); + logger.debug("Aliases"); + String alias; + Enumeration aliases = ks.aliases(); + while(aliases.hasMoreElements()) { + alias = aliases.nextElement(); + logger.debug("Alias:"+alias+" with certificate:"+ks.getCertificate(alias)+" certType:"+ks.getCertificate(alias).getType()); + } + return sslc; + } catch (Exception e) { + throw new InvalidSSLConfig(e); + } + } }