From 6892254209fd5ceec47b51e2635becc94e801dfa Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Tue, 4 Aug 2015 18:14:18 -0700 Subject: [PATCH 01/15] Fixes #114 - BatchApnsService should allow ScheduledExecutorService to be passed into constructor --- .../com/notnoop/apns/ApnsServiceBuilder.java | 37 ++++++++++++++++--- .../apns/internal/BatchApnsService.java | 15 ++++++-- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/notnoop/apns/ApnsServiceBuilder.java b/src/main/java/com/notnoop/apns/ApnsServiceBuilder.java index f0188925..9280f6a2 100644 --- a/src/main/java/com/notnoop/apns/ApnsServiceBuilder.java +++ b/src/main/java/com/notnoop/apns/ApnsServiceBuilder.java @@ -30,6 +30,7 @@ */ package com.notnoop.apns; +import static java.util.concurrent.Executors.defaultThreadFactory; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; @@ -40,6 +41,8 @@ import java.security.KeyStore; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import javax.net.ssl.SSLContext; @@ -94,7 +97,7 @@ public class ApnsServiceBuilder { private boolean isBatched = false; private int batchWaitTimeInSec; private int batchMaxWaitTimeInSec; - private ThreadFactory batchThreadFactory = null; + private ScheduledExecutorService batchThreadPoolExecutor = null; private ApnsDelegate delegate = ApnsDelegate.EMPTY; private Proxy proxy = null; @@ -529,7 +532,7 @@ public ApnsServiceBuilder asBatched() { * maximum wait time for batch before executing */ public ApnsServiceBuilder asBatched(int waitTimeInSec, int maxWaitTimeInSec) { - return asBatched(waitTimeInSec, maxWaitTimeInSec, null); + return asBatched(waitTimeInSec, maxWaitTimeInSec, (ThreadFactory)null); } /** @@ -552,14 +555,36 @@ public ApnsServiceBuilder asBatched(int waitTimeInSec, int maxWaitTimeInSec) { * thread factory to use for batch processing */ public ApnsServiceBuilder asBatched(int waitTimeInSec, int maxWaitTimeInSec, ThreadFactory threadFactory) { + return asBatched(waitTimeInSec, maxWaitTimeInSec, new ScheduledThreadPoolExecutor(1, threadFactory != null ? threadFactory : defaultThreadFactory())); + } + + /** + * Construct service which will process notification requests in batch. + * After each request batch will wait waitTimeInSec for more request to come + * before executing but not more than maxWaitTimeInSec + * + * Each batch creates new connection and close it after finished. + * In case reconnect policy is specified it will be applied by batch processing. + * E.g.: {@link ReconnectPolicy.Provided#EVERY_HALF_HOUR} will reconnect the connection in case batch is running for more than half an hour + * + * Note: It is not recommended to use pooled connection + * + * @param waitTimeInSec + * time to wait for more notification request before executing + * batch + * @param maxWaitTimeInSec + * maximum wait time for batch before executing + * @param batchThreadPoolExecutor + * executor for batched processing (may be null) + */ + public ApnsServiceBuilder asBatched(int waitTimeInSec, int maxWaitTimeInSec, ScheduledExecutorService batchThreadPoolExecutor) { this.isBatched = true; this.batchWaitTimeInSec = waitTimeInSec; this.batchMaxWaitTimeInSec = maxWaitTimeInSec; - this.batchThreadFactory = threadFactory; + this.batchThreadPoolExecutor = batchThreadPoolExecutor; return this; } - - + /** * Sets the delegate of the service, that gets notified of the * status of message delivery. @@ -629,7 +654,7 @@ public ApnsService build() { } if (isBatched) { - service = new BatchApnsService(conn, feedback, batchWaitTimeInSec, batchMaxWaitTimeInSec, batchThreadFactory); + service = new BatchApnsService(conn, feedback, batchWaitTimeInSec, batchMaxWaitTimeInSec, batchThreadPoolExecutor); } service.start(); diff --git a/src/main/java/com/notnoop/apns/internal/BatchApnsService.java b/src/main/java/com/notnoop/apns/internal/BatchApnsService.java index cd38855f..e8c31c00 100644 --- a/src/main/java/com/notnoop/apns/internal/BatchApnsService.java +++ b/src/main/java/com/notnoop/apns/internal/BatchApnsService.java @@ -1,8 +1,9 @@ package com.notnoop.apns.internal; +import static java.util.concurrent.Executors.defaultThreadFactory; + import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -40,15 +41,21 @@ public class BatchApnsService extends AbstractApnsService { private ScheduledExecutorService scheduleService; private ScheduledFuture taskFuture; - + private Runnable batchRunner = new SendMessagesBatch(); - public BatchApnsService(ApnsConnection prototype, ApnsFeedbackConnection feedback, int batchWaitTimeInSec, int maxBachWaitTimeInSec, ThreadFactory tf) { + public BatchApnsService(ApnsConnection prototype, ApnsFeedbackConnection feedback, int batchWaitTimeInSec, int maxBachWaitTimeInSec, ThreadFactory tf) { + this(prototype, feedback, batchWaitTimeInSec, maxBachWaitTimeInSec, + new ScheduledThreadPoolExecutor(1, + tf != null ? tf : defaultThreadFactory())); + } + + public BatchApnsService(ApnsConnection prototype, ApnsFeedbackConnection feedback, int batchWaitTimeInSec, int maxBachWaitTimeInSec, ScheduledExecutorService executor) { super(feedback); this.prototype = prototype; this.batchWaitTimeInSec = batchWaitTimeInSec; this.maxBatchWaitTimeInSec = maxBachWaitTimeInSec; - this.scheduleService = new ScheduledThreadPoolExecutor(1, tf == null ? Executors.defaultThreadFactory() : tf); + this.scheduleService = executor != null ? executor : new ScheduledThreadPoolExecutor(1, defaultThreadFactory()); } public void start() { From fc66d1e37763fc666703bd0c9a8e94dadc574eaa Mon Sep 17 00:00:00 2001 From: Hugo Trippaers Date: Fri, 20 Nov 2015 16:28:30 +0100 Subject: [PATCH 02/15] Use a builder pattern for the SSLContext --- .../com/notnoop/apns/ApnsServiceBuilder.java | 15 ++-- .../apns/internal/SSLContextBuilder.java | 79 +++++++++++++++++++ .../com/notnoop/apns/internal/Utilities.java | 39 --------- .../notnoop/apns/ApnsServerSocketBuilder.java | 15 +++- .../notnoop/apns/utils/FixedCertificates.java | 22 +++--- 5 files changed, 113 insertions(+), 57 deletions(-) create mode 100644 src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java diff --git a/src/main/java/com/notnoop/apns/ApnsServiceBuilder.java b/src/main/java/com/notnoop/apns/ApnsServiceBuilder.java index 9280f6a2..00ba66c9 100644 --- a/src/main/java/com/notnoop/apns/ApnsServiceBuilder.java +++ b/src/main/java/com/notnoop/apns/ApnsServiceBuilder.java @@ -174,9 +174,11 @@ public ApnsServiceBuilder withCert(String fileName, String password) public ApnsServiceBuilder withCert(InputStream stream, String password) throws InvalidSSLConfig { assertPasswordNotEmpty(password); - return withSSLContext( - newSSLContext(stream, password, - KEYSTORE_TYPE, KEY_ALGORITHM)); + return withSSLContext(new SSLContextBuilder() + .withAlgorithm(KEY_ALGORITHM) + .withCertificateKeyStore(stream, password, KEYSTORE_TYPE) + .withDefaultTrustKeyStore() + .build()); } /** @@ -200,8 +202,11 @@ public ApnsServiceBuilder withCert(InputStream stream, String password) public ApnsServiceBuilder withCert(KeyStore keyStore, String password) throws InvalidSSLConfig { assertPasswordNotEmpty(password); - return withSSLContext( - newSSLContext(keyStore, password, KEY_ALGORITHM)); + return withSSLContext(new SSLContextBuilder() + .withAlgorithm(KEY_ALGORITHM) + .withCertificateKeyStore(keyStore, password) + .withDefaultTrustKeyStore() + .build()); } private void assertPasswordNotEmpty(String password) { diff --git a/src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java b/src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java new file mode 100644 index 00000000..2bc6d410 --- /dev/null +++ b/src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java @@ -0,0 +1,79 @@ +package com.notnoop.apns.internal; + +import com.notnoop.exceptions.InvalidSSLConfig; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; + +public class SSLContextBuilder { + private String algorithm = "sunx509"; + private KeyManagerFactory keyManagerFactory; + private TrustManager[] trustManagers; + + public SSLContextBuilder withAlgorithm(String algorithm) { + this.algorithm = algorithm; + return this; + } + + public SSLContextBuilder withDefaultTrustKeyStore() throws InvalidSSLConfig { + try { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm); + trustManagerFactory.init((KeyStore)null); + trustManagers = trustManagerFactory.getTrustManagers(); + return this; + } catch (GeneralSecurityException e) { + throw new InvalidSSLConfig(e); + } + } + + public SSLContextBuilder withTrustManager(TrustManager trustManager) { + trustManagers = new TrustManager[] { trustManager }; + return this; + } + + public SSLContextBuilder withCertificateKeyStore(InputStream keyStoreStream, String keyStorePassword, String keyStoreType) throws InvalidSSLConfig { + try { + final KeyStore ks = KeyStore.getInstance(keyStoreType); + ks.load(keyStoreStream, keyStorePassword.toCharArray()); + return withCertificateKeyStore(ks, keyStorePassword); + } catch (GeneralSecurityException e) { + throw new InvalidSSLConfig(e); + } catch (IOException e) { + throw new InvalidSSLConfig(e); + } + } + + public SSLContextBuilder withCertificateKeyStore(KeyStore keyStore, String keyStorePassword) throws InvalidSSLConfig { + try { + keyManagerFactory = KeyManagerFactory.getInstance(algorithm); + keyManagerFactory.init(keyStore, keyStorePassword.toCharArray()); + return this; + } catch (GeneralSecurityException e) { + throw new InvalidSSLConfig(e); + } + } + + public SSLContext build() throws InvalidSSLConfig { + if (keyManagerFactory == null) { + throw new InvalidSSLConfig("Missing KeyManagerFactory"); + } + + if (trustManagers == null) { + throw new InvalidSSLConfig("Missing TrustManagers"); + } + + try { + final SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagerFactory.getKeyManagers(), trustManagers, null); + return sslContext; + } catch (GeneralSecurityException e) { + throw new InvalidSSLConfig(e); + } + } +} diff --git a/src/main/java/com/notnoop/apns/internal/Utilities.java b/src/main/java/com/notnoop/apns/internal/Utilities.java index 55afc4cd..7a62ab19 100644 --- a/src/main/java/com/notnoop/apns/internal/Utilities.java +++ b/src/main/java/com/notnoop/apns/internal/Utilities.java @@ -47,7 +47,6 @@ import java.util.regex.Pattern; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; import com.notnoop.exceptions.InvalidSSLConfig; import com.notnoop.exceptions.NetworkIOException; @@ -73,44 +72,6 @@ public final class Utilities { private Utilities() { throw new AssertionError("Uninstantiable class"); } - public static SSLSocketFactory newSSLSocketFactory(final InputStream cert, final String password, - final String ksType, final String ksAlgorithm) throws InvalidSSLConfig { - final SSLContext context = newSSLContext(cert, password, ksType, ksAlgorithm); - return context.getSocketFactory(); - } - - public static SSLContext newSSLContext(final InputStream cert, final String password, - final String ksType, final String ksAlgorithm) throws InvalidSSLConfig { - try { - final KeyStore ks = KeyStore.getInstance(ksType); - ks.load(cert, password.toCharArray()); - return newSSLContext(ks, password, ksAlgorithm); - } catch (final Exception e) { - throw new InvalidSSLConfig(e); - } - } - - public static SSLContext newSSLContext(final KeyStore ks, final String password, - final String ksAlgorithm) throws InvalidSSLConfig { - try { - // Get a KeyManager and initialize it - final KeyManagerFactory kmf = KeyManagerFactory.getInstance(ksAlgorithm); - kmf.init(ks, password.toCharArray()); - - // Get a TrustManagerFactory with the DEFAULT KEYSTORE, so we have all - // the certificates in cacerts trusted - final TrustManagerFactory tmf = TrustManagerFactory.getInstance(ksAlgorithm); - tmf.init((KeyStore)null); - - // Get the SSLContext to help create SSLSocketFactory - final SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - return sslContext; - } catch (final GeneralSecurityException e) { - throw new InvalidSSLConfig(e); - } - } - private static final Pattern pattern = Pattern.compile("[ -]"); public static byte[] decodeHex(final String deviceToken) { final String hex = pattern.matcher(deviceToken).replaceAll(""); diff --git a/src/test/java/com/notnoop/apns/ApnsServerSocketBuilder.java b/src/test/java/com/notnoop/apns/ApnsServerSocketBuilder.java index ced5cca1..66c88760 100644 --- a/src/test/java/com/notnoop/apns/ApnsServerSocketBuilder.java +++ b/src/test/java/com/notnoop/apns/ApnsServerSocketBuilder.java @@ -40,6 +40,8 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; + +import com.notnoop.apns.internal.SSLContextBuilder; import com.notnoop.apns.internal.Utilities; import com.notnoop.exceptions.InvalidSSLConfig; import com.notnoop.exceptions.RuntimeIOException; @@ -145,8 +147,11 @@ public ApnsServerSocketBuilder withCert(String fileName, String password) public ApnsServerSocketBuilder withCert(InputStream stream, String password) throws InvalidSSLConfig { assertPasswordNotEmpty(password); - return withSSLContext(newSSLContext(stream, password, KEYSTORE_TYPE, - KEY_ALGORITHM)); + return withSSLContext(new SSLContextBuilder() + .withAlgorithm(KEY_ALGORITHM) + .withCertificateKeyStore(stream, password, KEYSTORE_TYPE) + .withDefaultTrustKeyStore() + .build()); } /** @@ -170,7 +175,11 @@ public ApnsServerSocketBuilder withCert(InputStream stream, String password) public ApnsServerSocketBuilder withCert(KeyStore keyStore, String password) throws InvalidSSLConfig { assertPasswordNotEmpty(password); - return withSSLContext(newSSLContext(keyStore, password, KEY_ALGORITHM)); + return withSSLContext(new SSLContextBuilder() + .withAlgorithm(KEY_ALGORITHM) + .withCertificateKeyStore(keyStore, password) + .withDefaultTrustKeyStore() + .build()); } private void assertPasswordNotEmpty(String password) { diff --git a/src/test/java/com/notnoop/apns/utils/FixedCertificates.java b/src/test/java/com/notnoop/apns/utils/FixedCertificates.java index 9a25bae6..8b215094 100644 --- a/src/test/java/com/notnoop/apns/utils/FixedCertificates.java +++ b/src/test/java/com/notnoop/apns/utils/FixedCertificates.java @@ -1,13 +1,10 @@ package com.notnoop.apns.utils; -import java.io.InputStream; -import java.security.SecureRandom; +import com.notnoop.apns.internal.SSLContextBuilder; import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; - -import com.notnoop.apns.internal.Utilities; +import java.io.InputStream; public class FixedCertificates { @@ -21,10 +18,13 @@ public class FixedCertificates { public static SSLContext serverContext() { try { - //System.setProperty("javax.net.ssl.trustStore", ClassLoader.getSystemResource(CLIENT_STORE).getPath()); InputStream stream = FixedCertificates.class.getResourceAsStream("/" + SERVER_STORE); assert stream != null; - return Utilities.newSSLContext(stream, SERVER_PASSWORD, "PKCS12", "sunx509"); + return new SSLContextBuilder() + .withAlgorithm("sunx509") + .withCertificateKeyStore(stream, SERVER_PASSWORD, "PKCS12") + .withTrustManager(new X509TrustManagerTrustAll()) + .build(); } catch (Exception e) { throw new RuntimeException(e); } @@ -34,9 +34,11 @@ public static SSLContext clientContext() { try { InputStream stream = FixedCertificates.class.getResourceAsStream("/" + CLIENT_STORE); assert stream != null; - SSLContext context = Utilities.newSSLContext(stream, CLIENT_PASSWORD, "PKCS12", "sunx509"); - context.init(null, new TrustManager[] { new X509TrustManagerTrustAll() }, new SecureRandom()); - return context; + return new SSLContextBuilder() + .withAlgorithm("sunx509") + .withCertificateKeyStore(stream, CLIENT_PASSWORD, "PKCS12") + .withTrustManager(new X509TrustManagerTrustAll()) + .build(); } catch (Exception e) { throw new RuntimeException(e); } From 9401bfa2dbefa4c98d37d6bf1afc41568a4bffe4 Mon Sep 17 00:00:00 2001 From: Hugo Trippaers Date: Fri, 20 Nov 2015 16:55:44 +0100 Subject: [PATCH 03/15] The test server should require and validate client certificates Add updated versions of the certifcates as the current ones are expired. --- .../apns/internal/SSLContextBuilder.java | 23 ++++++++++++++++++ .../notnoop/apns/utils/ApnsServerStub.java | 18 ++++++++------ .../notnoop/apns/utils/FixedCertificates.java | 6 ++++- src/test/resources/clientStore.p12 | Bin 2483 -> 2516 bytes src/test/resources/serverStore.p12 | Bin 2483 -> 1764 bytes src/test/resources/serverTrustStore.p12 | Bin 0 -> 1740 bytes 6 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 src/test/resources/serverTrustStore.p12 diff --git a/src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java b/src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java index 2bc6d410..3fbfa973 100644 --- a/src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java +++ b/src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java @@ -32,6 +32,29 @@ public SSLContextBuilder withDefaultTrustKeyStore() throws InvalidSSLConfig { } } + public SSLContextBuilder withTrustKeyStore(InputStream keyStoreStream, String keyStorePassword, String keyStoreType) throws InvalidSSLConfig { + try { + final KeyStore ks = KeyStore.getInstance(keyStoreType); + ks.load(keyStoreStream, keyStorePassword.toCharArray()); + return withTrustKeyStore(ks, keyStorePassword); + } catch (GeneralSecurityException e) { + throw new InvalidSSLConfig(e); + } catch (IOException e) { + throw new InvalidSSLConfig(e); + } + + } + public SSLContextBuilder withTrustKeyStore(KeyStore keyStore, String keyStorePassword) throws InvalidSSLConfig { + try { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm); + trustManagerFactory.init(keyStore); + trustManagers = trustManagerFactory.getTrustManagers(); + return this; + } catch (GeneralSecurityException e) { + throw new InvalidSSLConfig(e); + } + } + public SSLContextBuilder withTrustManager(TrustManager trustManager) { trustManagers = new TrustManager[] { trustManager }; return this; diff --git a/src/test/java/com/notnoop/apns/utils/ApnsServerStub.java b/src/test/java/com/notnoop/apns/utils/ApnsServerStub.java index bf1dff18..a608e691 100644 --- a/src/test/java/com/notnoop/apns/utils/ApnsServerStub.java +++ b/src/test/java/com/notnoop/apns/utils/ApnsServerStub.java @@ -1,16 +1,16 @@ package com.notnoop.apns.utils; +import javax.net.ServerSocketFactory; +import javax.net.ssl.SSLServerSocket; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.nio.ByteBuffer; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicInteger; -import javax.net.ServerSocketFactory; public class ApnsServerStub { @@ -70,8 +70,8 @@ public ApnsServerStub(ServerSocketFactory sslFactory, int gatewayPort, int feedb Thread gatewayThread; Thread feedbackThread; - ServerSocket gatewaySocket; - ServerSocket feedbackSocket; + SSLServerSocket gatewaySocket; + SSLServerSocket feedbackSocket; public void start() { gatewayThread = new GatewayRunner(); @@ -152,10 +152,10 @@ private class GatewayRunner extends Thread { @SuppressWarnings("InfiniteLoopStatement") public void run() { - - + Thread.currentThread().setName("GatewayThread"); try { - gatewaySocket = sslFactory.createServerSocket(gatewayPort); + gatewaySocket = (SSLServerSocket)sslFactory.createServerSocket(gatewayPort); + gatewaySocket.setNeedClientAuth(true); } catch (IOException e) { messages.release(); throw new RuntimeException(e); @@ -215,8 +215,10 @@ public void run() { private class FeedbackRunner extends Thread { public void run() { + Thread.currentThread().setName("FeedbackThread"); try { - feedbackSocket = sslFactory.createServerSocket(feedbackPort); + feedbackSocket = (SSLServerSocket)sslFactory.createServerSocket(feedbackPort); + feedbackSocket.setNeedClientAuth(true); } catch (IOException e) { e.printStackTrace(); messages.release(); diff --git a/src/test/java/com/notnoop/apns/utils/FixedCertificates.java b/src/test/java/com/notnoop/apns/utils/FixedCertificates.java index 8b215094..18b229a5 100644 --- a/src/test/java/com/notnoop/apns/utils/FixedCertificates.java +++ b/src/test/java/com/notnoop/apns/utils/FixedCertificates.java @@ -14,16 +14,20 @@ public class FixedCertificates { public static final String SERVER_STORE = "serverStore.p12"; public static final String SERVER_PASSWORD = "123456"; + public static final String SERVER_TRUST_STORE = "serverTrustStore.p12"; + public static final String SERVER_TRUST_PASSWORD = "123456"; + public static final String LOCALHOST = "localhost"; public static SSLContext serverContext() { try { InputStream stream = FixedCertificates.class.getResourceAsStream("/" + SERVER_STORE); + InputStream trustStream = FixedCertificates.class.getResourceAsStream("/" + SERVER_TRUST_STORE); assert stream != null; return new SSLContextBuilder() .withAlgorithm("sunx509") .withCertificateKeyStore(stream, SERVER_PASSWORD, "PKCS12") - .withTrustManager(new X509TrustManagerTrustAll()) + .withTrustKeyStore(trustStream, SERVER_TRUST_PASSWORD, "PKCS12") .build(); } catch (Exception e) { throw new RuntimeException(e); diff --git a/src/test/resources/clientStore.p12 b/src/test/resources/clientStore.p12 index 28ab70a12d6c8cbfe79a0765b5237f89d48db9d0..ef8243e15a1ccd24b12faaeddd1a030c7bdd0800 100644 GIT binary patch literal 2516 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 OVu40p>Yn-iD*gi}36Jss literal 2483 zcmY+EcRUmhAIERmn=>OL=_s68Mt*0Ubw;?%?0GmuWX4@&q|9IFY#CW+RI+83Ju`AL z4rN4SlgID%dY-4}`Q!WfzFwct>-GKX`+39hG(->(49C+1P|}D!(SCBk0Hgxu;%QtV zcpAqG+ZK)oAN+|ZbMfGB7Zw==1Y9iAp9Bc!rKJ7$1$rPQoDo7@Wi0FHBbV$B0#O4Z z@Zc)nam_3KgEk}Ngr=wjrq0zc-cTv& zD@&tS!~MaXV8R!0@8E%ynG4Hcu{^2lB%N@OazA`L=lN@}6Wy@6z9O+oER;oc_*3U=Ks zkq^asI`xSZ${*YlrFOXfW_ z?q+EhSsM+!5+9y-pAYKJVt0TUF(hQLZrm@~b0~kHYN-x0{t=#LXAC(DTl1L<$_Hl3 zx)9C>Yc|b~{Idh(77KkAiq#xqhJBq@PY;! z`T7iR-c`D|r-io>Cn6(Hbz0E(!_JA-`9_l&sF})fRl^o`P%mAg)O-WY2Wz`kp!%ajz0m`+( zdgxddKC^-_i*ZnL>fzOsN}I;lFe-2H!=Q7b8ggGnAh5&dxag8@eNL7kA-lv~7CP_NRNjPLXq@;N@G4Q65x_fHz$`o3{FyD#W6sr;xd?D&88pj;+^xl?dyQMVG}we)4-``Ptx@Kogyf zN0bGD;JL}i)(xmTmX8GDL^q&}*)lB!eG#hF1nkd8&_u~h(hWB(Vn#NHhc{{x-ck}K zhK~D2bm+~VOZ$690oH@F&FbU^ab}X4ngm0RrllCD3Nxq48Gj*@04q;VZ|Oa=9&n8@ zv$it*{+;P#SS;%@6wU>!4@Rh#armAl5N-)5W15Tw*{T`G)%Sx`>E75wD%{fL8cO5V zX3Ro3>BV-WnFP5)*xRa|N{4Dc_c{Db6`kGtm0l)uaxy@lr2(+v4c5=nIDU0I){^a4 zKfx}T(t5e$2^>#h_df;C#ZwqT@D%zNw$8;MDe3-GcUmAQ7Z3RX$3xcsj{@L-DDbJd z%$Z<=eE$yxAb7~fpLxsSZ+_d0glXSwJq1HsI83$Ly)CF=Jh6+qZB*MVCV^o?d0C@V zp*pMuRShkaSP4~Y51N4)uamBFf4(K`C=@qSWl?~tiC$<;#6D{-6aG*NW50sNT$|g^ zY^tO&zW74SGqY<43b1cS$`L*XKlw8o9x|N|aR&5PoVoe0Pd;7cDiQy55Zb+JWv|4# z01^;pG-#iFv0=Y|qno^1B<$JRNN`BYJAJ%Xb+!m!9u*^~iq;T?xFJRNt|Bt(lp9I( z^__olSAEH(4kq!?>Y6Cgn8$GU>aaG@yGO6tWlq+tNFeb5fvUP2U(Rp3c?kI z-lL}VJKWc^ZOh{`y{CA~_~X#cm_u6OV`v((P)7T4F=Hgv<-U}x=rwW9@_^mVMR3%? zhZ}A*?{@1BQI_)^yyfi2zJyKGvD?J(Y>z#nuUT=40I-Sp5d@}XyEiEENJ_~|*JVZI z^RH1BE_GTdmK|dIB(_G>axkS)uDF%$5~I&Yc6+OLm_70Svu?E|4sPc{N)x&20O?X< zN0+M!soM7ZxN#};jU;EF+&-rL&NVLj`ljgyZXXh2yH;1A7d ziKk>#jfWR&VyZmmB$z`{9ps^CR!FaM>)d^NXmt9NI=Y0Be_CK-i##@Xcwbuu{&10z z3=JlS4pe4x-8rh8a`^6S{!s_Y(CaQqJi&2gB0}vsUm&_$e*8K>5J*Ch6-lyb2sYVK z$IpugNOo4MtR1EI6T3s-Q$^!yC$(nQ&#=rlOlz1Xe#Y)JEguGh(CaCvlDF~h)L9&_FW+dig@kj>hQjm$W9U6Y)%+2r)S6s6t{a_*zN z>pihDy&4HFRh~7HUjVfK;Cyu#N_>pX+nC2ZSpPEOd{ve9*M)imeBf99#OzWW5GWhJm7gXbC~BkSD~{|=%nO{B?uWzSf^Zs2itCJE5Dx_a n0!8ONyee^^uk&($HawBPJI1G2jRQi>*4F!Z%h$gj1O)yIIS7g@ diff --git a/src/test/resources/serverStore.p12 b/src/test/resources/serverStore.p12 index b375de78b57744e2103ec1f822fcc1075f6f7645..808062693b31c8218b87a40c42f39a9131171ebd 100644 GIT binary patch literal 1764 zcmY+Dc{~&d8^`A|L*yLS5Gq*lz?MHKX{hl3wnPpAL8VksBa#ZLMQTA0{og(~ zj)1@e25W_D`z+kiI}!9aKYa623&{oqf&d^?5T(z%Ay;u=@yhnKGoN4f741EEB2R!( zyozuWJt`L~!j|xA1UZ*Cgwf?S0_B(T$zzeQ&p#%4>2%c0S1g=}8oA6(RtfG(R`5YuhtxQ1_X@!cs`yd! z1X!6;o#(Zjhd$TutR+=j89WIqZYaAY)}zU|u}Hl#22@~frVgN`v5agJB5$bTJ&!N# z>v7E?%Fp9O&>PNgjV-qlJm0WhYzzwe!!&p0Pmc?9RZ6C!7-KjtrYqj!iOD-MO54gR;d^U%%S>7`qK$i^L zZOI;zyb6?R844YE{R}!4yRE&>!1m7Lc~QI!-+9^0w##>XHk9g{TC>GRuSU}?1Jsju z!bP>o%vT`~&tqYzQ&C=xb?T|d)#kIF{b+`ks__Nxj{I>1@@;TSH|9)%g|p&MX~X`` zqNU0$v$`PJLOtG**YLQjWVQ41x1S}8RWHzrm%ID16I78K8+|fzW>n*kQ%iCNODxxC zq#!n)HapZ)m3r^5v$sRtr9P}sk zofrU(%@=)4C{pf%K)u}Rvg)ban+mZx<^^!p3>PzN1HPnzK>;mEm71L>7o_grA1(w( zDJdxc!U048`M*j8Tmk3sg3S)B6CfbqprQUrfd2vo`W=+(Vz>?mK~CK(#57!WTV0!cc_sQgC=@Da zGh^`d@%i@(l?z?)61{O>`AmS%6rXl7rlA-k%WkcG-bwHrxzW(!FsHO$n=?q$ruQSd z=j=Fw>%;ZtL}!VW;rrc%wN;Th1iC*ZKE>PRCCvHZX$fdwTKa{u)VlP7 zpKd`KVuxmeR!6il8M48X0*xA+O&#tX$d>xeRY%mvLmFJ#*jh)d(jd9ffO1yc0p=< za3hc|y#E1GL){g>?3K}%95VH(JwwomMc24Fh%`s4B88v=x}snZLI41PS2|WD?cL4q pdbJP$hlWr>=^2t`eH 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 diff --git a/src/test/resources/serverTrustStore.p12 b/src/test/resources/serverTrustStore.p12 new file mode 100644 index 0000000000000000000000000000000000000000..458b8b6347976368995a3151541bca9b090ec363 GIT binary patch literal 1740 zcmY+DdpOhy7su!N!we(Su#xLhI~g<>mNcdr6=P(}dfPFhrCeIMjcX}|pIag1@>VXB zFp3?QOva_cj@+ZQ?opdXOu{o^hRx2tdY|{*efE!Y&hvcF_j~?2A0i0!z@Ts<2!Mc+ zaf(xlOa-a{EdhZD1PFxg$bm!N~R9PN@Ls|D}{sP*@2Ft0#i68X_J* z{a@ZWu7$t>=-S19#|F2!5PU1W?Y{f-!cRAQf?nM~B3#QRQc}P2DC5r6ZhWm*@+$Y;o%zQVqv0nry$y@K-+jL+ zZ?^Y+$!TK^4#%wh$$IEE-X;`haq5vl$2G&4iWx>c>tWYz*}?6!`YU4L+d=hsK^!}w zxiCNS0{K&5!#e-DHZyR5dif72S43Oh`t6icosDZ=Jm2<*_V7~a4Z*SPwQ4+p?|`)a z%@d}ab?9TM#GRY+;_Jm@{+n`qm&yXU_4#Gx8iNEluDEt zW!2cs@4GdS2XqUkeVfm#-iWXSN2POZILcw%4ngQ}Wn zvFbc5q~s{8E3IbeeQ^SFS%UU@uEQ>Ku3eCor+{(IzwXsht)1Qy!|%4)9P(Dt({Rq$ z$z^8x7f?+G0!ve(hP-x*Ejr&KGKP}KQY^4h1*v+n8={V1%W2WqWKEq0po*5sgw@?P%2*+ zhetQsznS@E-tBk{ch9X|)UY={v|-omw_m^0or{@PxG6y7j*1XNt z7pdCvyP=_rDl~(D2WK*FzSbL=a(B4wA6IU~$lFT%Mv}8Tn{cO&!Ea6MUpIFm8vga+ zN;q>3T^u9?5($XTMn-zIf)90Kbe5bURKP9oV5EiJ9BtSl@@J3!d| zWl{i2K)A_gd|*(>&iFg70R0Cbz-K@N-f#D|m`C5MgHoK_)t4-<9M;Et1|%MYEe;;E zPa>9#=H}5e9~${DrJV!PNAP(bmp_*O>`cku& zQy7alarlC)7!xwTE@%sxd z8U^h!WTCbOO8hiejE8AaERUDwbGr9VA}ODw($;`P(?`bhZwkc9Y0}%d7CeR3Cm-ap zSmM%(BSdo5OcFe#GxBT8Ol^~{vZljq_i3_r4n2crH9%jWCKmvyNwU;FZQv1;bcA5y z*qJc<_FQ{T|MTczR)g}Tg!yAg=D}Z>!O)el1Cf_EE?pSuL%hs4k+bD0geuHA(&_D9 z)uPVAo+z2ai^ChIG716c?-=|@uAR)QXU(U7({OXhwx!v&{g&yJ_thJ&2zKRUo8gqt zEl1LrbJMA0wQ??<%Q-54E8`11C$2? zy#fm!JI3pbklqwk(*kU=el)W7?bsVDA#v}<;#BzyP_ZDQsmAkRS4&0q_Y)f{0?sPI z$mQAiZsE|1V%17-`Llq@lVnEDiw8B)&UDqHRoU}StpwjHk&^_#lvZ<;9DMJDY}+yp zUqv=XaqENzb0IV14jC7eBFQHDb^yOW-}7{Yr*$`L>)HiF+QRh#oj*dDK2?|}y~Xbz zT}*|ko}iwI5la{@PpE2XDrWLzuQoETvQPzV*)>>q_UBYla%Xifb>A%(dy5Gly#@+z zHlbX86txs-bim*2R|qRlem{pvBdEmYO<)_zq%-fkV z6ZaKMMIKN4#%*6@3wGzK_@&#K?9p%h7<|?ddM;D{>p8>m;(WYUb<_vzhpjw6cA+u1 zFDzFfbP+vHBohsYN&u38fy1(tA0GoGZOnRdJ T6y?>@A{=`^tE@BSGb;Z9Vq6m} literal 0 HcmV?d00001 From d9f3b688b61166499cfbcdea08b6ecb1ec894ee5 Mon Sep 17 00:00:00 2001 From: Hugo Trippaers Date: Fri, 20 Nov 2015 17:19:40 +0100 Subject: [PATCH 04/15] Immediately fail if the SSL handshake fails --- .../java/com/notnoop/apns/internal/ApnsConnectionImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/notnoop/apns/internal/ApnsConnectionImpl.java b/src/main/java/com/notnoop/apns/internal/ApnsConnectionImpl.java index b7a36397..8c819d2d 100644 --- a/src/main/java/com/notnoop/apns/internal/ApnsConnectionImpl.java +++ b/src/main/java/com/notnoop/apns/internal/ApnsConnectionImpl.java @@ -44,6 +44,7 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.net.SocketFactory; +import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSocketFactory; import com.notnoop.apns.ApnsDelegate; import com.notnoop.apns.StartSendingApnsDelegate; @@ -334,6 +335,9 @@ private synchronized void sendMessage(ApnsNotification m, boolean fromBuffer) th //logger.debug("Message \"{}\" sent", m); attempts = 0; break; + } catch (SSLHandshakeException e) { + // No use retrying this, it's dead Jim + throw new NetworkIOException(e); } catch (IOException e) { Utilities.close(socket); if (attempts >= RETRIES) { From 000b707794cb29db89bea97b26410c4cf3d3a2d3 Mon Sep 17 00:00:00 2001 From: Hugo Trippaers Date: Fri, 20 Nov 2015 17:35:40 +0100 Subject: [PATCH 05/15] Allow JKS files with multiple certificates and selection by alias --- .../com/notnoop/apns/ApnsServiceBuilder.java | 101 ++++++++++++++++-- .../apns/internal/SSLContextBuilder.java | 48 +++++++++ .../apns/integration/ApnsConnectionTest.java | 37 ++++++- .../notnoop/apns/utils/FixedCertificates.java | 17 +++ src/test/resources/clientStore.jks | Bin 0 -> 4209 bytes 5 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 src/test/resources/clientStore.jks diff --git a/src/main/java/com/notnoop/apns/ApnsServiceBuilder.java b/src/main/java/com/notnoop/apns/ApnsServiceBuilder.java index 00ba66c9..a1d39de5 100644 --- a/src/main/java/com/notnoop/apns/ApnsServiceBuilder.java +++ b/src/main/java/com/notnoop/apns/ApnsServiceBuilder.java @@ -30,14 +30,26 @@ */ package com.notnoop.apns; -import static java.util.concurrent.Executors.defaultThreadFactory; +import com.notnoop.apns.internal.ApnsConnection; +import com.notnoop.apns.internal.ApnsConnectionImpl; +import com.notnoop.apns.internal.ApnsFeedbackConnection; +import com.notnoop.apns.internal.ApnsPooledConnection; +import com.notnoop.apns.internal.ApnsServiceImpl; +import com.notnoop.apns.internal.BatchApnsService; +import com.notnoop.apns.internal.QueuedApnsService; +import com.notnoop.apns.internal.SSLContextBuilder; +import com.notnoop.apns.internal.Utilities; +import com.notnoop.exceptions.InvalidSSLConfig; +import com.notnoop.exceptions.RuntimeIOException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.Socket; - import java.security.KeyStore; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -45,14 +57,15 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; - -import com.notnoop.apns.internal.*; -import com.notnoop.exceptions.InvalidSSLConfig; -import com.notnoop.exceptions.RuntimeIOException; - -import static com.notnoop.apns.internal.Utilities.*; +import static com.notnoop.apns.internal.Utilities.PRODUCTION_FEEDBACK_HOST; +import static com.notnoop.apns.internal.Utilities.PRODUCTION_FEEDBACK_PORT; +import static com.notnoop.apns.internal.Utilities.PRODUCTION_GATEWAY_HOST; +import static com.notnoop.apns.internal.Utilities.PRODUCTION_GATEWAY_PORT; +import static com.notnoop.apns.internal.Utilities.SANDBOX_FEEDBACK_HOST; +import static com.notnoop.apns.internal.Utilities.SANDBOX_FEEDBACK_PORT; +import static com.notnoop.apns.internal.Utilities.SANDBOX_GATEWAY_HOST; +import static com.notnoop.apns.internal.Utilities.SANDBOX_GATEWAY_PORT; +import static java.util.concurrent.Executors.defaultThreadFactory; /** * The class is used to create instances of {@link ApnsService}. @@ -208,7 +221,73 @@ public ApnsServiceBuilder withCert(KeyStore keyStore, String password) .withDefaultTrustKeyStore() .build()); } - + + /** + * Specify the certificate store used to connect to Apple APNS + * servers. This relies on the stream of keystore (*.p12 | *.jks) + * containing the keys and certificates, along with the given + * password and alias. + * + * The keystore can be either PKCS12 or JKS and the keystore + * needs to be encrypted using the SunX509 algorithm. + * + * This library does not support password-less p12 certificates, due to a + * Oracle Java library + * Bug 6415637. There are three workarounds: use a password-protected + * certificate, use a different boot Java SDK implementation, or constract + * the `SSLContext` yourself! Needless to say, the password-protected + * certificate is most recommended option. + * + * @param stream the keystore represented as input stream + * @param password the password of the keystore + * @param alias the alias identifing the key to be used + * @return this + * @throws InvalidSSLConfig if stream is an invalid Keystore, + * the password is invalid or the alias is not found + */ + public ApnsServiceBuilder withCert(InputStream stream, String password, String alias) + throws InvalidSSLConfig { + assertPasswordNotEmpty(password); + return withSSLContext(new SSLContextBuilder() + .withAlgorithm(KEY_ALGORITHM) + .withCertificateKeyStore(stream, password, KEYSTORE_TYPE, alias) + .withDefaultTrustKeyStore() + .build()); + } + + /** + * Specify the certificate store used to connect to Apple APNS + * servers. This relies on the stream of keystore (*.p12 | *.jks) + * containing the keys and certificates, along with the given + * password and alias. + * + * The keystore can be either PKCS12 or JKS and the keystore + * needs to be encrypted using the SunX509 algorithm. + * + * This library does not support password-less p12 certificates, due to a + * Oracle Java library + * Bug 6415637. There are three workarounds: use a password-protected + * certificate, use a different boot Java SDK implementation, or constract + * the `SSLContext` yourself! Needless to say, the password-protected + * certificate is most recommended option. + * + * @param keyStore the keystore + * @param password the password of the keystore + * @param alias the alias identifing the key to be used + * @return this + * @throws InvalidSSLConfig if stream is an invalid Keystore, + * the password is invalid or the alias is not found + */ + public ApnsServiceBuilder withCert(KeyStore keyStore, String password, String alias) + throws InvalidSSLConfig { + assertPasswordNotEmpty(password); + return withSSLContext(new SSLContextBuilder() + .withAlgorithm(KEY_ALGORITHM) + .withCertificateKeyStore(keyStore, password, alias) + .withDefaultTrustKeyStore() + .build()); + } + private void assertPasswordNotEmpty(String password) { if (password == null || password.length() == 0) { throw new IllegalArgumentException("Passwords must be specified." + diff --git a/src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java b/src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java index 3fbfa973..88c9dd26 100644 --- a/src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java +++ b/src/main/java/com/notnoop/apns/internal/SSLContextBuilder.java @@ -9,7 +9,13 @@ import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; +import java.security.Key; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; public class SSLContextBuilder { private String algorithm = "sunx509"; @@ -72,6 +78,18 @@ public SSLContextBuilder withCertificateKeyStore(InputStream keyStoreStream, Str } } + public SSLContextBuilder withCertificateKeyStore(InputStream keyStoreStream, String keyStorePassword, String keyStoreType, String keyAlias) throws InvalidSSLConfig { + try { + final KeyStore ks = KeyStore.getInstance(keyStoreType); + ks.load(keyStoreStream, keyStorePassword.toCharArray()); + return withCertificateKeyStore(ks, keyStorePassword, keyAlias); + } catch (GeneralSecurityException e) { + throw new InvalidSSLConfig(e); + } catch (IOException e) { + throw new InvalidSSLConfig(e); + } + } + public SSLContextBuilder withCertificateKeyStore(KeyStore keyStore, String keyStorePassword) throws InvalidSSLConfig { try { keyManagerFactory = KeyManagerFactory.getInstance(algorithm); @@ -82,6 +100,36 @@ public SSLContextBuilder withCertificateKeyStore(KeyStore keyStore, String keySt } } + public SSLContextBuilder withCertificateKeyStore(KeyStore keyStore, String keyStorePassword, String keyAlias) throws InvalidSSLConfig { + try { + if (!keyStore.containsAlias(keyAlias)) { + throw new InvalidSSLConfig("No key with alias " + keyAlias); + } + KeyStore singleKeyKeyStore = getKeyStoreWithSingleKey(keyStore, keyStorePassword, keyAlias); + return withCertificateKeyStore(singleKeyKeyStore, keyStorePassword); + } catch (GeneralSecurityException e) { + throw new InvalidSSLConfig(e); + } catch (IOException e) { + throw new InvalidSSLConfig(e); + } + } + + /* + * Workaround for keystores containing multiple keys. Java will take the first key that matches + * and this way we can still offer configuration for a keystore with multiple keys and a selection + * based on alias. Also much easier than making a subclass of a KeyManagerFactory + */ + private KeyStore getKeyStoreWithSingleKey(KeyStore keyStore, String keyStorePassword, String keyAlias) + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException { + KeyStore singleKeyKeyStore = KeyStore.getInstance(keyStore.getType(), keyStore.getProvider()); + final char[] password = keyStorePassword.toCharArray(); + singleKeyKeyStore.load(null, password); + Key key = keyStore.getKey(keyAlias, password); + Certificate[] chain = keyStore.getCertificateChain(keyAlias); + singleKeyKeyStore.setKeyEntry(keyAlias, key, password, chain); + return singleKeyKeyStore; + } + public SSLContext build() throws InvalidSSLConfig { if (keyManagerFactory == null) { throw new InvalidSSLConfig("Missing KeyManagerFactory"); diff --git a/src/test/java/com/notnoop/apns/integration/ApnsConnectionTest.java b/src/test/java/com/notnoop/apns/integration/ApnsConnectionTest.java index 9ac4cd6e..c348b77a 100644 --- a/src/test/java/com/notnoop/apns/integration/ApnsConnectionTest.java +++ b/src/test/java/com/notnoop/apns/integration/ApnsConnectionTest.java @@ -8,14 +8,19 @@ import com.notnoop.apns.utils.junit.DumpThreadsOnErrorRule; import com.notnoop.apns.utils.junit.Repeat; import com.notnoop.apns.utils.junit.RepeatRule; +import com.notnoop.exceptions.NetworkIOException; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; -import static com.notnoop.apns.utils.FixedCertificates.*; -import static org.junit.Assert.*; +import static com.notnoop.apns.utils.FixedCertificates.LOCALHOST; +import static com.notnoop.apns.utils.FixedCertificates.clientContext; +import static com.notnoop.apns.utils.FixedCertificates.clientMultiKeyContext; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; @SuppressWarnings("ALL") @@ -122,4 +127,32 @@ public void sendOneSimpleWithTimeout() throws InterruptedException { assertArrayEquals(msg1.marshall(), server.getReceived().toByteArray()); } + @Test(timeout = 2000) + public void sendOneSimpleMultiKey() throws InterruptedException { + ApnsService service = + APNS.newService().withSSLContext(clientMultiKeyContext("notnoop-client")) + .withGatewayDestination(LOCALHOST, gatewayPort) + .build(); + server.stopAt(msg1.length()); + service.push(msg1); + server.getMessages().acquire(); + + assertArrayEquals(msg1.marshall(), server.getReceived().toByteArray()); + } + + @Test(timeout = 2000) + public void sendOneSimpleClientCertFail() throws InterruptedException { + ApnsService service = + APNS.newService().withSSLContext(clientMultiKeyContext("notused")) + .withGatewayDestination(LOCALHOST, gatewayPort) + .build(); + server.stopAt(msg1.length()); + try { + service.push(msg1); + fail(); + } catch (NetworkIOException e) { + assertTrue("Expected bad_certifcate exception", e.getMessage().contains("bad_certificate")); + } + } + } diff --git a/src/test/java/com/notnoop/apns/utils/FixedCertificates.java b/src/test/java/com/notnoop/apns/utils/FixedCertificates.java index 18b229a5..8af23561 100644 --- a/src/test/java/com/notnoop/apns/utils/FixedCertificates.java +++ b/src/test/java/com/notnoop/apns/utils/FixedCertificates.java @@ -11,6 +11,9 @@ public class FixedCertificates { public static final String CLIENT_STORE = "clientStore.p12"; public static final String CLIENT_PASSWORD = "123456"; + public static final String CLIENT_MULTI_KEY_STORE = "clientStore.jks"; + public static final String CLIENT_MULTI_KEY_PASSWORD = "123456"; + public static final String SERVER_STORE = "serverStore.p12"; public static final String SERVER_PASSWORD = "123456"; @@ -48,6 +51,20 @@ public static SSLContext clientContext() { } } + public static SSLContext clientMultiKeyContext(String keyAlias) { + try { + InputStream stream = FixedCertificates.class.getResourceAsStream("/" + CLIENT_MULTI_KEY_STORE); + assert stream != null; + return new SSLContextBuilder() + .withAlgorithm("sunx509") + .withCertificateKeyStore(stream, CLIENT_MULTI_KEY_PASSWORD, "JKS", keyAlias) + .withTrustManager(new X509TrustManagerTrustAll()) + .build(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public static String clientCertPath() { return ClassLoader.getSystemResource(CLIENT_STORE).getPath(); } diff --git a/src/test/resources/clientStore.jks b/src/test/resources/clientStore.jks new file mode 100644 index 0000000000000000000000000000000000000000..8564ff4a209a78b1d18f45f748d5e921493235f4 GIT binary patch literal 4209 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 literal 0 HcmV?d00001 From c179d5e1b4e0a9ffefc6dcb52d9f62c21af377cd Mon Sep 17 00:00:00 2001 From: Hugo Trippaers Date: Fri, 20 Nov 2015 17:48:47 +0100 Subject: [PATCH 06/15] Add a shell script to recreate the certificate stores --- tools/generate_test_stores.sh | 57 +++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tools/generate_test_stores.sh diff --git a/tools/generate_test_stores.sh b/tools/generate_test_stores.sh new file mode 100644 index 00000000..2d3899b5 --- /dev/null +++ b/tools/generate_test_stores.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +KEYSIZE=1024 +VALIDITY=1460 +rm -f serverStore.p12 serverTrustStore.p12 caKey.pem caCert.pem request.crs clientStore.p12 clientCert.crt clientStore.jks + +echo ---=== Generating Certificates ===--- +echo This tools will request several parameters. +echo The names are free, but it is recommended +echo to enter a name that identifies the certificate +echo for example TestServer, TestCA, TestClient, TestInvalidClient +echo Enter 123456 when asked for a password +echo + +# Server Cert +echo --== Generating Server Certificate ==-- +keytool -genkey -keyalg RSA -alias notnoop-server \ + -keystore serverStore.p12 -storepass 123456 -storetype PKCS12 \ + -validity ${VALIDITY} -keysize ${KEYSIZE} +keytool -exportcert -alias notnoop-server \ + -keystore serverStore.p12 -storepass 123456 -storetype PKCS12 | keytool -printcert + +# Client Cert CA +echo --== Generating Client CA Certificate ==-- +keytool -genkey -keyalg RSA -alias notnoop-ca \ + -keystore serverTrustStore.p12 -storetype pkcs12 -storepass 123456 \ + -validity ${VALIDITY} -keysize ${KEYSIZE} +keytool -exportcert -alias notnoop-ca \ + -keystore serverTrustStore.p12 -storepass 123456 -storetype PKCS12 | keytool -printcert + +openssl pkcs12 -in serverTrustStore.p12 -nocerts -out caKey.pem +openssl pkcs12 -in serverTrustStore.p12 -clcerts -nokeys -out caCert.pem + +echo --== Generating Client Certificate ==-- +keytool -genkey -keyalg RSA -keystore clientStore.p12 -storepass 123456 -storetype PKCS12 -alias notnoop-client \ + -validity ${VALIDITY} -keysize ${KEYSIZE} +keytool -certreq -keystore clientStore.p12 -storepass 123456 -storetype PKCS12 -alias notnoop-client -file request.crs + +echo --== Signing Client Certificate with CA ==-- +openssl x509 -req -CA CACert.pem -CAkey CAKey.pem -in request.crs -out clientCert.crt \ + -days ${VALIDITY} -CAcreateserial -outform PEM -set_serial 1 +cat caCert.pem >> clientCert.crt +keytool -import -keystore clientStore.p12 -storepass 123456 -storetype PKCS12 -file clientCert.crt -alias notnoop-client +keytool -exportcert -alias notnoop-client \ + -keystore clientStore.p12 -storepass 123456 -storetype PKCS12 | keytool -printcert + +echo --== Generating JKS Client Keystore with Client Certificate ==-- +keytool -importkeystore \ + -srckeystore clientStore.p12 -srcstorepass 123456 -srcstoretype PKCS12 -srcalias notnoop-client \ + -destkeystore clientStore.jks -deststorepass 123456 -deststoretype JKS -destalias notnoop-client + +echo --== Generating Invalid Client Certificate ==-- +keytool -genkeypair -keyalg RSA -keystore clientStore.jks -storepass 123456 -storetype JKS -alias notused + +keytool -list -keystore clientStore.jks -storepass 123456 -storetype JKS + +rm caKey.pem caCert.pem request.crs clientCert.crt \ No newline at end of file From cfa26bc7953125507a1a4b082039b13c57fe0cde Mon Sep 17 00:00:00 2001 From: Hugo Trippaers Date: Fri, 20 Nov 2015 18:10:33 +0100 Subject: [PATCH 07/15] Adding a space to trigger a retest with travis --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 55b3519f..c78c07f4 100644 --- a/README.markdown +++ b/README.markdown @@ -160,4 +160,4 @@ Licensed under the [New 3-Clause BSD License](http://www.opensource.org/licenses Contact --------------- -Support mailing list: http://groups.google.com/group/java-apns-discuss +Support mailing list: http://groups.google.com/group/java-apns-discuss From 8342a68f772fc97e201e8927fde39c3babb878cd Mon Sep 17 00:00:00 2001 From: Hugo Trippaers Date: Mon, 23 Nov 2015 09:58:28 +0100 Subject: [PATCH 08/15] Add a check to the build to verify license details --- LICENSE.contributor | 27 +++++++++++++++++++++++++++ pom.xml | 31 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 LICENSE.contributor diff --git a/LICENSE.contributor b/LICENSE.contributor new file mode 100644 index 00000000..9713c165 --- /dev/null +++ b/LICENSE.contributor @@ -0,0 +1,27 @@ + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Mahmood Ali. nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pom.xml b/pom.xml index 123fbe9a..84646588 100644 --- a/pom.xml +++ b/pom.xml @@ -138,6 +138,37 @@ + + com.mycila + license-maven-plugin + 2.11 + + + default-cli + process-classes + + check + + + + + true + true +
LICENSE
+ + LICENSE.contributor + + + XML_STYLE + SLASHSTAR_STYLE + SEMICOLON_STYLE + + true + + src/**/*.java + +
+