From 15134ada9b016b14b784802891a577b3cbe4f9b4 Mon Sep 17 00:00:00 2001 From: "mohammed.ali" Date: Thu, 21 Oct 2021 22:14:47 +0530 Subject: [PATCH 01/13] Support for JDK8u252+ and E2E tests for it. Pulled from #84 --- pom.xml | 118 ++++------ .../apns/clients/SyncOkHttpApnsClient.java | 15 +- .../com/clevertap/apns/LocalHttpServer.java | 66 ++++++ .../clients/SyncOkHttpApnsClientTest.java | 202 ++++++++++++++++++ 4 files changed, 325 insertions(+), 76 deletions(-) create mode 100644 src/test/java/com/clevertap/apns/LocalHttpServer.java create mode 100644 src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java diff --git a/pom.xml b/pom.xml index 9c50fd8..f482c8c 100644 --- a/pom.xml +++ b/pom.xml @@ -12,27 +12,6 @@ A library for communicating with the Apple Push Gateway in HTTP/2. https://github.com/CleverTap/apns-http2 - - clevertap - https://sonarcloud.io - CleverTap_apns-http2 - UTF-8 - 1.8 - ${maven.compiler.source} - - - - - - org.junit - junit-bom - 5.8.1 - pom - import - - - - com.fasterxml.jackson.core @@ -55,53 +34,46 @@ com.squareup.okhttp3 okhttp - 3.2.0 + 4.8.1 + + + + com.squareup.okhttp3 + mockwebserver + 4.8.1 + test + + + + com.squareup.okhttp3 + okhttp-tls + 4.8.1 + test + + + + junit + junit + 4.11 + test org.junit.jupiter junit-jupiter + RELEASE test - - - jacoco - - - - org.jacoco - jacoco-maven-plugin - 0.8.4 - - - - prepare-agent - - - - jacoco-report - - report - - - - - - - - - org.apache.maven.plugins maven-compiler-plugin - 3.8.1 - - - maven-surefire-plugin - 2.22.2 + + 1.8 + 1.8 + org.apache.maven.plugins @@ -109,27 +81,27 @@ 2.1.2 - attach-sources - - jar-no-fork - + attach-sources + + jar-no-fork + - + - org.apache.maven.plugins - maven-javadoc-plugin - 2.7 - - - attach-javadocs - - jar - - - + org.apache.maven.plugins + maven-javadoc-plugin + 2.7 + + + attach-javadocs + + jar + + + - + org.apache.maven.plugins maven-assembly-plugin @@ -181,4 +153,4 @@ https://opensource.org/licenses/BSD-3-Clause - + \ No newline at end of file diff --git a/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java b/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java index 86e8c22..eae183a 100644 --- a/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java +++ b/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java @@ -43,6 +43,7 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; import java.util.UUID; /** @@ -178,11 +179,19 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init((KeyStore) null); - sslContext.init(keyManagers, tmf.getTrustManagers(), null); + + // check if there is an existing TrustManager configured in the builder + TrustManager[] trustManagers = (builder.getX509TrustManagerOrNull$okhttp() != null) ? + new TrustManager[] {builder.getX509TrustManagerOrNull$okhttp()} : tmf.getTrustManagers(); + sslContext.init(keyManagers, trustManagers, null); + + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); + } final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); - builder.sslSocketFactory(sslSocketFactory); + builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManagers[0]); client = builder.build(); @@ -342,4 +351,4 @@ protected NotificationResponse parseResponse(Response response) throws IOExcepti return new NotificationResponse(error, statusCode, contentBody, null); } -} +} \ No newline at end of file diff --git a/src/test/java/com/clevertap/apns/LocalHttpServer.java b/src/test/java/com/clevertap/apns/LocalHttpServer.java new file mode 100644 index 0000000..faf010b --- /dev/null +++ b/src/test/java/com/clevertap/apns/LocalHttpServer.java @@ -0,0 +1,66 @@ +package com.clevertap.apns; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class LocalHttpServer { + + private final ExecutorService executorService = Executors.newFixedThreadPool(10); + HttpServer httpServer; + int port; + String LOCAL_HOST = "http://127.0.0.1"; + String CONTEXT = "/serveRequest"; + + public static int nextFreePort() { + try { + try (ServerSocket tempSocket = new ServerSocket(0)) { + return tempSocket.getLocalPort(); + } + } catch (IOException e) { + return -1; + } + } + + public int init() throws Exception { + port = nextFreePort(); + baseConfig(port); + return port; + } + + public String getUrl() { + return LOCAL_HOST + ":" + port + CONTEXT; + } + + public void baseConfig(int port) throws Exception { + httpServer = HttpServer.create(new InetSocketAddress(port), 0); + httpServer.createContext(CONTEXT, new RequestHandler()); + httpServer.setExecutor(executorService); // creates a default executor + httpServer.start(); + System.out.println("Local Http server created on port " + port); + } + + public void shutDownServer() { + httpServer.stop(1); + executorService.shutdown(); + } + + static class RequestHandler implements HttpHandler { + @Override + public void handle(HttpExchange t) throws IOException { + String response = "Request Executed"; + t.getResponseHeaders().set("Content-Type", "text/plain"); + t.sendResponseHeaders(200, response.length()); + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java new file mode 100644 index 0000000..07806df --- /dev/null +++ b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java @@ -0,0 +1,202 @@ +package com.clevertap.apns.clients; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +import com.clevertap.apns.ApnsClient; +import com.clevertap.apns.LocalHttpServer; +import com.clevertap.apns.Notification; +import com.clevertap.apns.NotificationResponse; + +import org.junit.Before; +import org.junit.Test; + +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import okhttp3.tls.HandshakeCertificates; +import okhttp3.tls.HeldCertificate; + + +public class SyncOkHttpApnsClientTest { + + protected static final String DEFAULT_TOPIC = "com.clevertap.testTopic"; + protected static final String CERT_PASSWD = "cert-password"; + protected static final String DEVICE_TOKEN = "vaild-device-token"; + protected static final String INVALID_DEVICE_TOKEN = "invaild-device-token"; + + protected HeldCertificate rootCertificate; + protected HeldCertificate serverCertificate; + protected HeldCertificate clientCertificate; + protected HandshakeCertificates serverCertificateChain; + protected HandshakeCertificates clientCertificateChain; + + @Before + public void initCertificates() { + rootCertificate = new HeldCertificate.Builder() + .certificateAuthority(0) + .build(); + + serverCertificate = new HeldCertificate.Builder() + .addSubjectAlternativeName("localhost") + .commonName("localhost") + .signedBy(rootCertificate) + .build(); + + clientCertificate = new HeldCertificate.Builder() + .commonName("push") + .signedBy(rootCertificate) + .build(); + + serverCertificateChain = new HandshakeCertificates.Builder() + .heldCertificate(serverCertificate) + .addTrustedCertificate(rootCertificate.certificate()) + .build(); + + // Don't add client cert to client cert chain b/c it will be added via the apns api + clientCertificateChain = new HandshakeCertificates.Builder() + .addTrustedCertificate(rootCertificate.certificate()) + .build(); + } + + /** + * Convert client cert to PKCS12 Format and return as InputStream. + * @return + */ + protected InputStream getClientCertPKCS12() { + try { + KeyStore pkcs12 = KeyStore.getInstance("PKCS12"); + pkcs12.load(null, null); + Certificate chain[] = {clientCertificate.certificate()}; + pkcs12.setKeyEntry("privatekeyalias", clientCertificate.keyPair().getPrivate(), CERT_PASSWD.toCharArray(), chain); + + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + pkcs12.store(outStream, CERT_PASSWD.toCharArray()); + + return new ByteArrayInputStream(outStream.toByteArray()); + } catch(Exception e) { + fail(e.getMessage()); + } + return null; + } + + /** + * Changes Gateway-URL of the ApnsClient instance to the given URL via reflection. + * + * @param client ApnsClient instance which gatewayUrl shall be changed + * @param gatewayUrl URL to set + */ + protected void setClientGatewayUrl(ApnsClient client, HttpUrl gatewayUrl) { + try { + String url = gatewayUrl.toString(); + + // strip trailling slash + if (url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + } + + Field field = client.getClass().getDeclaredField("gateway"); + field.setAccessible(true); + + Field modifiers = Field.class.getDeclaredField("modifiers"); + modifiers.setAccessible(true); + modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL); + + field.set(client, url); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + /** + * Build ApnsClient with valid client cert in synchronous mode. + * @return apnsClient + */ + private ApnsClient buildClientWithCert() { + try { + return new ApnsClientBuilder() + .withOkHttpClientBuilder(new OkHttpClient.Builder().sslSocketFactory(clientCertificateChain.sslSocketFactory(), clientCertificateChain.trustManager())) + .withDefaultTopic(DEFAULT_TOPIC) + .withCertificate(getClientCertPKCS12()) + .withPassword(CERT_PASSWD) + .inSynchronousMode() + .withProductionGateway() + .build(); + } catch (Exception e) { + fail(e.getMessage()); + } + return null; + } + + @Test + public void pushTestWithCert() { + MockWebServer server = new MockWebServer(); + try { + server.useHttps(serverCertificateChain.sslSocketFactory(), false); + server.requestClientAuth(); + server.enqueue(new MockResponse().setResponseCode(200).setBody("Hello world!")); + + ApnsClient client = buildClientWithCert(); + setClientGatewayUrl(client, server.url("")); + + NotificationResponse response = client.push( + new Notification.Builder(DEVICE_TOKEN) + .alertBody("Notification Body") + .alertTitle("Alert Title") + .badge(10) + .sound("sound") + .build() + ); + assertEquals("HTTP-Response-Code 200", 200, response.getHttpStatusCode()); + + RecordedRequest request = server.takeRequest(); + assertEquals("/3/device/" + DEVICE_TOKEN, request.getPath()); + assertEquals(DEFAULT_TOPIC, request.getHeader("apns-topic")); + + X509Certificate clientCert = (X509Certificate) request.getHandshake().peerCertificates().get(0); + X509Certificate clientChain[] = {clientCert}; + serverCertificateChain.trustManager().checkClientTrusted(clientChain, "RSA"); + + } catch (Exception e) { + fail(e.getMessage()); + } + + try { + server.close(); + } catch (IOException e) { + fail(e.getMessage()); + } + } + + @Test + public void pushTestWithCertificateWithLocalHttpServer() throws Exception { + LocalHttpServer localHttpServer = new LocalHttpServer(); + localHttpServer.init(); + HttpUrl url = HttpUrl.parse(localHttpServer.getUrl()); + ApnsClient client = buildClientWithCert(); + setClientGatewayUrl(client, url); + + NotificationResponse response = client.push( + new Notification.Builder(DEVICE_TOKEN) + .alertBody("Notification Body") + .alertTitle("Alert Title") + .badge(10) + .sound("sound") + .build() + ); + assertEquals("Server should be hit and should return 200", 200, response.getHttpStatusCode()); + localHttpServer.shutDownServer(); + } +} \ No newline at end of file From 1334ae9b78c7b109a88bcd4cba188410867f22f5 Mon Sep 17 00:00:00 2001 From: "mohammed.ali" Date: Thu, 21 Oct 2021 22:40:45 +0530 Subject: [PATCH 02/13] Fix pom.xml diff --- pom.xml | 65 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index f482c8c..46e5e0a 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,27 @@ A library for communicating with the Apple Push Gateway in HTTP/2. https://github.com/CleverTap/apns-http2 + + clevertap + https://sonarcloud.io + CleverTap_apns-http2 + UTF-8 + 1.8 + ${maven.compiler.source} + + + + + + org.junit + junit-bom + 5.8.1 + pom + import + + + + com.fasterxml.jackson.core @@ -51,29 +72,51 @@ test - - junit - junit - 4.11 - test - org.junit.jupiter junit-jupiter - RELEASE test + + + jacoco + + + + org.jacoco + jacoco-maven-plugin + 0.8.4 + + + + prepare-agent + + + + jacoco-report + + report + + + + + + + + + org.apache.maven.plugins maven-compiler-plugin - - 1.8 - 1.8 - + 3.8.1 + + + maven-surefire-plugin + 2.22.2 org.apache.maven.plugins From b94e013298d39a2ac6e0878f5dd87524a196e148 Mon Sep 17 00:00:00 2001 From: "mohammed.ali" Date: Thu, 21 Oct 2021 22:50:07 +0530 Subject: [PATCH 03/13] Updated E2E test in SyncOkHttpApnsClientTest to use junit jupiter --- .../apns/clients/SyncOkHttpApnsClientTest.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java index 07806df..e9a9ccd 100644 --- a/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java +++ b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java @@ -18,8 +18,8 @@ import com.clevertap.apns.Notification; import com.clevertap.apns.NotificationResponse; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; @@ -37,14 +37,14 @@ public class SyncOkHttpApnsClientTest { protected static final String DEVICE_TOKEN = "vaild-device-token"; protected static final String INVALID_DEVICE_TOKEN = "invaild-device-token"; - protected HeldCertificate rootCertificate; - protected HeldCertificate serverCertificate; - protected HeldCertificate clientCertificate; - protected HandshakeCertificates serverCertificateChain; - protected HandshakeCertificates clientCertificateChain; + protected static HeldCertificate rootCertificate; + protected static HeldCertificate serverCertificate; + protected static HeldCertificate clientCertificate; + protected static HandshakeCertificates serverCertificateChain; + protected static HandshakeCertificates clientCertificateChain; - @Before - public void initCertificates() { + @BeforeAll + public static void initCertificates() { rootCertificate = new HeldCertificate.Builder() .certificateAuthority(0) .build(); From cef3f878a96610155d84aae9df4588006fa26cbe Mon Sep 17 00:00:00 2001 From: "mohammed.ali" Date: Thu, 21 Oct 2021 23:21:20 +0530 Subject: [PATCH 04/13] Added E2E for when okhttp client builder is not passed to ApnsClientBuilder --- .../clients/SyncOkHttpApnsClientTest.java | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java index e9a9ccd..958e44a 100644 --- a/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java +++ b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java @@ -120,20 +120,26 @@ protected void setClientGatewayUrl(ApnsClient client, HttpUrl gatewayUrl) { } } + + /** * Build ApnsClient with valid client cert in synchronous mode. * @return apnsClient */ - private ApnsClient buildClientWithCert() { + private ApnsClient buildClientWithCert(boolean withOkHttpClientBuilder) { try { - return new ApnsClientBuilder() - .withOkHttpClientBuilder(new OkHttpClient.Builder().sslSocketFactory(clientCertificateChain.sslSocketFactory(), clientCertificateChain.trustManager())) + ApnsClientBuilder builder = new ApnsClientBuilder() .withDefaultTopic(DEFAULT_TOPIC) .withCertificate(getClientCertPKCS12()) .withPassword(CERT_PASSWD) .inSynchronousMode() - .withProductionGateway() - .build(); + .withProductionGateway(); + + if (withOkHttpClientBuilder) { + builder.withOkHttpClientBuilder(new OkHttpClient.Builder().sslSocketFactory(clientCertificateChain.sslSocketFactory(), clientCertificateChain.trustManager())); + } + + return builder.build(); } catch (Exception e) { fail(e.getMessage()); } @@ -141,14 +147,14 @@ private ApnsClient buildClientWithCert() { } @Test - public void pushTestWithCert() { + void pushTestWithCert() { MockWebServer server = new MockWebServer(); try { server.useHttps(serverCertificateChain.sslSocketFactory(), false); server.requestClientAuth(); server.enqueue(new MockResponse().setResponseCode(200).setBody("Hello world!")); - ApnsClient client = buildClientWithCert(); + ApnsClient client = buildClientWithCert(true); setClientGatewayUrl(client, server.url("")); NotificationResponse response = client.push( @@ -181,11 +187,11 @@ public void pushTestWithCert() { } @Test - public void pushTestWithCertificateWithLocalHttpServer() throws Exception { + void pushTestWithCertificateWithLocalHttpServer() throws Exception { LocalHttpServer localHttpServer = new LocalHttpServer(); localHttpServer.init(); HttpUrl url = HttpUrl.parse(localHttpServer.getUrl()); - ApnsClient client = buildClientWithCert(); + ApnsClient client = buildClientWithCert(true); setClientGatewayUrl(client, url); NotificationResponse response = client.push( @@ -197,6 +203,22 @@ public void pushTestWithCertificateWithLocalHttpServer() throws Exception { .build() ); assertEquals("Server should be hit and should return 200", 200, response.getHttpStatusCode()); + + // Should have the same result as above if the trust manager isn't passed as well + client = buildClientWithCert(false); + setClientGatewayUrl(client, url); + response = client.push( + new Notification.Builder(DEVICE_TOKEN) + .alertBody("Notification Body") + .alertTitle("Alert Title") + .badge(10) + .sound("sound") + .build() + ); + assertEquals("Server should be hit and should return 200 without trust manager set", 200, response.getHttpStatusCode()); + + + localHttpServer.shutDownServer(); } } \ No newline at end of file From 0199eb8c16d3e866abf78d892ce82ff884b04dd9 Mon Sep 17 00:00:00 2001 From: "mohammed.ali" Date: Fri, 22 Oct 2021 17:55:49 +0530 Subject: [PATCH 05/13] Updated pom.xml to store okhttp version as a property --- pom.xml | 7 ++++--- .../clevertap/apns/clients/SyncOkHttpApnsClientTest.java | 2 -- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 46e5e0a..bc07077 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ UTF-8 1.8 ${maven.compiler.source} + 4.8.1 @@ -55,20 +56,20 @@ com.squareup.okhttp3 okhttp - 4.8.1 + ${okhttp.version} com.squareup.okhttp3 mockwebserver - 4.8.1 + ${okhttp.version} test com.squareup.okhttp3 okhttp-tls - 4.8.1 + ${okhttp.version} test diff --git a/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java index 958e44a..317dbc4 100644 --- a/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java +++ b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java @@ -217,8 +217,6 @@ void pushTestWithCertificateWithLocalHttpServer() throws Exception { ); assertEquals("Server should be hit and should return 200 without trust manager set", 200, response.getHttpStatusCode()); - - localHttpServer.shutDownServer(); } } \ No newline at end of file From 4da6a449fb7cfafd897f1f3e57b35fbf095a8d57 Mon Sep 17 00:00:00 2001 From: "mohammed.ali" Date: Mon, 25 Oct 2021 07:57:36 +0530 Subject: [PATCH 06/13] Changed trust manager exception in SyncOkHttpApnsClient to checked exception --- .../com/clevertap/apns/clients/SyncOkHttpApnsClient.java | 5 +++-- .../apns/exceptions/InvalidTrustManagerException.java | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/clevertap/apns/exceptions/InvalidTrustManagerException.java diff --git a/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java b/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java index eae183a..a7cd980 100644 --- a/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java +++ b/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java @@ -31,6 +31,7 @@ package com.clevertap.apns.clients; import com.clevertap.apns.*; +import com.clevertap.apns.exceptions.InvalidTrustManagerException; import com.clevertap.apns.internal.Constants; import com.clevertap.apns.internal.JWT; import okhttp3.*; @@ -161,7 +162,7 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, String defaultTopic, OkHttpClient.Builder builder, int connectionPort) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, - IOException, UnrecoverableKeyException, KeyManagementException { + IOException, UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { teamID = keyID = apnsAuthKey = null; @@ -186,7 +187,7 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr sslContext.init(keyManagers, trustManagers, null); if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); + throw new InvalidTrustManagerException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); } final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); diff --git a/src/main/java/com/clevertap/apns/exceptions/InvalidTrustManagerException.java b/src/main/java/com/clevertap/apns/exceptions/InvalidTrustManagerException.java new file mode 100644 index 0000000..4bdf59e --- /dev/null +++ b/src/main/java/com/clevertap/apns/exceptions/InvalidTrustManagerException.java @@ -0,0 +1,9 @@ +package com.clevertap.apns.exceptions; + +import java.security.cert.CertificateException; + +public class InvalidTrustManagerException extends CertificateException { + public InvalidTrustManagerException(String s) { + super(s); + } +} From c67382e4b1b9588f9bde940fe2a504792051fbe7 Mon Sep 17 00:00:00 2001 From: "mohammed.ali" Date: Mon, 25 Oct 2021 10:28:28 +0530 Subject: [PATCH 07/13] Removed reflection in SyncOkHttpApnsClientTest and added option to customize the gateway url in builder --- .../apns/clients/ApnsClientBuilder.java | 14 ++- .../apns/clients/AsyncOkHttpApnsClient.java | 17 +++- .../apns/clients/SyncOkHttpApnsClient.java | 95 +++++++++++++++++-- .../clients/SyncOkHttpApnsClientTest.java | 80 ++++------------ 4 files changed, 132 insertions(+), 74 deletions(-) diff --git a/src/main/java/com/clevertap/apns/clients/ApnsClientBuilder.java b/src/main/java/com/clevertap/apns/clients/ApnsClientBuilder.java index ceaaf93..4a7c349 100644 --- a/src/main/java/com/clevertap/apns/clients/ApnsClientBuilder.java +++ b/src/main/java/com/clevertap/apns/clients/ApnsClientBuilder.java @@ -52,6 +52,7 @@ public class ApnsClientBuilder { private boolean production; private String password; private int connectionPort = 443; + private String gatewayUrl; private boolean asynchronous = false; private String defaultTopic = null; @@ -169,6 +170,11 @@ public ApnsClientBuilder withDefaultTopic(String defaultTopic) { return this; } + public ApnsClientBuilder withGatewayUrl(String url) { + this.gatewayUrl = url; + return this; + } + public ApnsClient build() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException, KeyManagementException { @@ -183,15 +189,15 @@ public ApnsClient build() throws CertificateException, if (certificate != null) { if (asynchronous) { - return new AsyncOkHttpApnsClient(certificate, password, production, defaultTopic, builder, connectionPort); + return new AsyncOkHttpApnsClient(certificate, password, production, defaultTopic, builder, connectionPort, gatewayUrl); } else { - return new SyncOkHttpApnsClient(certificate, password, production, defaultTopic, builder, connectionPort); + return new SyncOkHttpApnsClient(certificate, password, production, defaultTopic, builder, connectionPort, gatewayUrl); } } else if (keyID != null && teamID != null && apnsAuthKey != null) { if (asynchronous) { - return new AsyncOkHttpApnsClient(apnsAuthKey, teamID, keyID, production, defaultTopic, builder, connectionPort); + return new AsyncOkHttpApnsClient(apnsAuthKey, teamID, keyID, production, defaultTopic, builder, connectionPort, gatewayUrl); } else { - return new SyncOkHttpApnsClient(apnsAuthKey, teamID, keyID, production, defaultTopic, builder, connectionPort); + return new SyncOkHttpApnsClient(apnsAuthKey, teamID, keyID, production, defaultTopic, builder, connectionPort, gatewayUrl); } } else { throw new IllegalArgumentException("Either the token credentials (team ID, key ID, and the private key) " + diff --git a/src/main/java/com/clevertap/apns/clients/AsyncOkHttpApnsClient.java b/src/main/java/com/clevertap/apns/clients/AsyncOkHttpApnsClient.java index 4e79ee1..9f34866 100644 --- a/src/main/java/com/clevertap/apns/clients/AsyncOkHttpApnsClient.java +++ b/src/main/java/com/clevertap/apns/clients/AsyncOkHttpApnsClient.java @@ -65,9 +65,15 @@ public AsyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, this(apnsAuthKey, teamID, keyID, production, defaultTopic, builder, 443); } + public AsyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, + boolean production, String defaultTopic, OkHttpClient.Builder builder, int connectionPort, + String gatewayUrl) { + super(apnsAuthKey, teamID, keyID, production, defaultTopic, builder, gatewayUrl); + } + public AsyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boolean production, String defaultTopic, OkHttpClient.Builder builder, int connectionPort) { - super(apnsAuthKey, teamID, keyID, production, defaultTopic, builder); + this(apnsAuthKey, teamID, keyID, production, defaultTopic, builder, 443, null); } public AsyncOkHttpApnsClient(InputStream certificate, String password, boolean production, @@ -77,11 +83,18 @@ public AsyncOkHttpApnsClient(InputStream certificate, String password, boolean p this(certificate, password, production, defaultTopic, builder, 443); } + public AsyncOkHttpApnsClient(InputStream certificate, String password, boolean production, + String defaultTopic, OkHttpClient.Builder builder, int connectionPort, String gatewayUrl) + throws CertificateException, NoSuchAlgorithmException, KeyStoreException, + IOException, UnrecoverableKeyException, KeyManagementException { + super(certificate, password, production, defaultTopic, builder, gatewayUrl); + } + public AsyncOkHttpApnsClient(InputStream certificate, String password, boolean production, String defaultTopic, OkHttpClient.Builder builder, int connectionPort) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException, KeyManagementException { - super(certificate, password, production, defaultTopic, builder); + this(certificate, password, production, defaultTopic, builder, 443, null); } @Override diff --git a/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java b/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java index a7cd980..bc293de 100644 --- a/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java +++ b/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java @@ -72,10 +72,16 @@ public class SyncOkHttpApnsClient implements ApnsClient { * @param production Whether to use the production endpoint or the sandbox endpoint * @param defaultTopic A default topic (can be changed per message) * @param clientBuilder An OkHttp client builder, possibly pre-initialized, to build the actual client + * @param gatewayUrl The gateway url the APNS client should point to */ + public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boolean production, + String defaultTopic, OkHttpClient.Builder clientBuilder, String gatewayUrl) { + this(apnsAuthKey, teamID, keyID, production, defaultTopic, clientBuilder, 443, gatewayUrl); + } + public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boolean production, String defaultTopic, OkHttpClient.Builder clientBuilder) { - this(apnsAuthKey, teamID, keyID, production, defaultTopic, clientBuilder, 443); + this(apnsAuthKey, teamID, keyID, production, defaultTopic, clientBuilder, 443, null); } /** @@ -88,9 +94,10 @@ public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boo * @param defaultTopic A default topic (can be changed per message) * @param clientBuilder An OkHttp client builder, possibly pre-initialized, to build the actual client * @param connectionPort The port to establish a connection with APNs. Either 443 or 2197 + * @param gatewayUrl The gateway url the APNS client should point to */ public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boolean production, - String defaultTopic, OkHttpClient.Builder clientBuilder, int connectionPort) { + String defaultTopic, OkHttpClient.Builder clientBuilder, int connectionPort, String gatewayUrl) { this.apnsAuthKey = apnsAuthKey; this.teamID = teamID; this.keyID = keyID; @@ -98,7 +105,27 @@ public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boo this.defaultTopic = defaultTopic; - gateway = (production ? Constants.ENDPOINT_PRODUCTION : Constants.ENDPOINT_SANDBOX) + ":" + connectionPort; + if (gatewayUrl == null) { + gateway = (production ? Constants.ENDPOINT_PRODUCTION : Constants.ENDPOINT_SANDBOX) + ":" + connectionPort; + } else { + gateway = gatewayUrl; + } + } + + /** + * Creates a new client which uses token authentication API. + * + * @param apnsAuthKey The private key - exclude -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- + * @param teamID The team ID + * @param keyID The key ID (retrieved from the file name) + * @param production Whether to use the production endpoint or the sandbox endpoint + * @param defaultTopic A default topic (can be changed per message) + * @param clientBuilder An OkHttp client builder, possibly pre-initialized, to build the actual client + * @param connectionPort The port to establish a connection with APNs. Either 443 or 2197 + */ + public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boolean production, + String defaultTopic, OkHttpClient.Builder clientBuilder, int connectionPort) { + this(apnsAuthKey, teamID, keyID, production, defaultTopic, clientBuilder, connectionPort, null); } /** @@ -138,7 +165,32 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr String defaultTopic, OkHttpClient.Builder builder) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException, KeyManagementException { - this(certificate, password, production, defaultTopic, builder, 443); + this(certificate, password, production, defaultTopic, builder, 443, null); + } + + /** + * Creates a new client and automatically loads the key store + * with the push certificate read from the input stream. + * + * @param certificate The client certificate to be used + * @param password The password (if required, else null) + * @param production Whether to use the production endpoint or the sandbox endpoint + * @param defaultTopic A default topic (can be changed per message) + * @param builder An OkHttp client builder, possibly pre-initialized, to build the actual client + * @param gatewayUrl The gateway url the APNS client should point to + * @throws UnrecoverableKeyException If the key cannot be recovered + * @throws KeyManagementException if the key failed to be loaded + * @throws CertificateException if any of the certificates in the keystore could not be loaded + * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found + * @throws IOException if there is an I/O or format problem with the keystore data, + * if a password is required but not given, or if the given password was incorrect + * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type + */ + public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, + String defaultTopic, OkHttpClient.Builder builder, String gatewayUrl) + throws CertificateException, NoSuchAlgorithmException, KeyStoreException, + IOException, UnrecoverableKeyException, KeyManagementException { + this(certificate, password, production, defaultTopic, builder, 443, gatewayUrl); } /** @@ -151,6 +203,7 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr * @param defaultTopic A default topic (can be changed per message) * @param builder An OkHttp client builder, possibly pre-initialized, to build the actual client * @param connectionPort The port to establish a connection with APNs. Either 443 or 2197 + * @param gatewayUrl The gateway url the APNS client should point to * @throws UnrecoverableKeyException If the key cannot be recovered * @throws KeyManagementException if the key failed to be loaded * @throws CertificateException if any of the certificates in the keystore could not be loaded @@ -160,7 +213,7 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type */ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, - String defaultTopic, OkHttpClient.Builder builder, int connectionPort) + String defaultTopic, OkHttpClient.Builder builder, int connectionPort, String gatewayUrl) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { @@ -197,7 +250,37 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr client = builder.build(); this.defaultTopic = defaultTopic; - gateway = (production ? Constants.ENDPOINT_PRODUCTION : Constants.ENDPOINT_SANDBOX) + ":" + connectionPort; + + if (gatewayUrl == null) { + gateway = (production ? Constants.ENDPOINT_PRODUCTION : Constants.ENDPOINT_SANDBOX) + ":" + connectionPort; + } else { + gateway = gatewayUrl; + } + } + + /** + * Creates a new client and automatically loads the key store + * with the push certificate read from the input stream. + * + * @param certificate The client certificate to be used + * @param password The password (if required, else null) + * @param production Whether to use the production endpoint or the sandbox endpoint + * @param defaultTopic A default topic (can be changed per message) + * @param builder An OkHttp client builder, possibly pre-initialized, to build the actual client + * @param connectionPort The port to establish a connection with APNs. Either 443 or 2197 + * @throws UnrecoverableKeyException If the key cannot be recovered + * @throws KeyManagementException if the key failed to be loaded + * @throws CertificateException if any of the certificates in the keystore could not be loaded + * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found + * @throws IOException if there is an I/O or format problem with the keystore data, + * if a password is required but not given, or if the given password was incorrect + * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type + */ + public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, + String defaultTopic, OkHttpClient.Builder builder, int connectionPort) + throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, + KeyManagementException, IOException { + this(certificate, password, production, defaultTopic, builder, connectionPort, null); } /** diff --git a/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java index 317dbc4..06a8343 100644 --- a/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java +++ b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java @@ -1,16 +1,14 @@ package com.clevertap.apns.clients; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.security.KeyStore; +import java.security.*; import java.security.cert.Certificate; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import com.clevertap.apns.ApnsClient; @@ -21,7 +19,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -75,8 +72,7 @@ public static void initCertificates() { * Convert client cert to PKCS12 Format and return as InputStream. * @return */ - protected InputStream getClientCertPKCS12() { - try { + protected InputStream getClientCertPKCS12() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { KeyStore pkcs12 = KeyStore.getInstance("PKCS12"); pkcs12.load(null, null); Certificate chain[] = {clientCertificate.certificate()}; @@ -86,48 +82,15 @@ protected InputStream getClientCertPKCS12() { pkcs12.store(outStream, CERT_PASSWD.toCharArray()); return new ByteArrayInputStream(outStream.toByteArray()); - } catch(Exception e) { - fail(e.getMessage()); - } - return null; } - /** - * Changes Gateway-URL of the ApnsClient instance to the given URL via reflection. - * - * @param client ApnsClient instance which gatewayUrl shall be changed - * @param gatewayUrl URL to set - */ - protected void setClientGatewayUrl(ApnsClient client, HttpUrl gatewayUrl) { - try { - String url = gatewayUrl.toString(); - - // strip trailling slash - if (url.endsWith("/")) { - url = url.substring(0, url.length() - 1); - } - - Field field = client.getClass().getDeclaredField("gateway"); - field.setAccessible(true); - - Field modifiers = Field.class.getDeclaredField("modifiers"); - modifiers.setAccessible(true); - modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL); - - field.set(client, url); - } catch (Exception e) { - fail(e.getMessage()); - } - } - - /** * Build ApnsClient with valid client cert in synchronous mode. * @return apnsClient */ - private ApnsClient buildClientWithCert(boolean withOkHttpClientBuilder) { - try { + private ApnsClient buildClientWithCert(boolean withOkHttpClientBuilder, String gatewayUrl) throws CertificateException, + UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException { ApnsClientBuilder builder = new ApnsClientBuilder() .withDefaultTopic(DEFAULT_TOPIC) .withCertificate(getClientCertPKCS12()) @@ -139,23 +102,26 @@ private ApnsClient buildClientWithCert(boolean withOkHttpClientBuilder) { builder.withOkHttpClientBuilder(new OkHttpClient.Builder().sslSocketFactory(clientCertificateChain.sslSocketFactory(), clientCertificateChain.trustManager())); } + if (gatewayUrl != null) { + builder.withGatewayUrl(gatewayUrl); + } + return builder.build(); - } catch (Exception e) { - fail(e.getMessage()); - } - return null; } @Test - void pushTestWithCert() { + void pushTestWithCert() throws IOException, CertificateException, InterruptedException, UnrecoverableKeyException, + NoSuchAlgorithmException, KeyStoreException, KeyManagementException, NoSuchFieldException, IllegalAccessException { MockWebServer server = new MockWebServer(); try { server.useHttps(serverCertificateChain.sslSocketFactory(), false); server.requestClientAuth(); server.enqueue(new MockResponse().setResponseCode(200).setBody("Hello world!")); - ApnsClient client = buildClientWithCert(true); - setClientGatewayUrl(client, server.url("")); + String url = server.url("").toString(); + url = url.substring(0, url.length() - 1); // Above method gives a trailing "/" which we want to remove + + ApnsClient client = buildClientWithCert(true, url); NotificationResponse response = client.push( new Notification.Builder(DEVICE_TOKEN) @@ -174,15 +140,8 @@ void pushTestWithCert() { X509Certificate clientCert = (X509Certificate) request.getHandshake().peerCertificates().get(0); X509Certificate clientChain[] = {clientCert}; serverCertificateChain.trustManager().checkClientTrusted(clientChain, "RSA"); - - } catch (Exception e) { - fail(e.getMessage()); - } - - try { + } finally { server.close(); - } catch (IOException e) { - fail(e.getMessage()); } } @@ -190,9 +149,7 @@ void pushTestWithCert() { void pushTestWithCertificateWithLocalHttpServer() throws Exception { LocalHttpServer localHttpServer = new LocalHttpServer(); localHttpServer.init(); - HttpUrl url = HttpUrl.parse(localHttpServer.getUrl()); - ApnsClient client = buildClientWithCert(true); - setClientGatewayUrl(client, url); + ApnsClient client = buildClientWithCert(true, localHttpServer.getUrl()); NotificationResponse response = client.push( new Notification.Builder(DEVICE_TOKEN) @@ -205,8 +162,7 @@ void pushTestWithCertificateWithLocalHttpServer() throws Exception { assertEquals("Server should be hit and should return 200", 200, response.getHttpStatusCode()); // Should have the same result as above if the trust manager isn't passed as well - client = buildClientWithCert(false); - setClientGatewayUrl(client, url); + client = buildClientWithCert(false, localHttpServer.getUrl()); response = client.push( new Notification.Builder(DEVICE_TOKEN) .alertBody("Notification Body") From fdb6981cbdceb1a3a7d77ef8960184cbb1ca62fe Mon Sep 17 00:00:00 2001 From: "mohammed.ali" Date: Mon, 25 Oct 2021 10:37:46 +0530 Subject: [PATCH 08/13] Fixed InvalidTrustManagerException's parent class --- .../apns/exceptions/InvalidTrustManagerException.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/clevertap/apns/exceptions/InvalidTrustManagerException.java b/src/main/java/com/clevertap/apns/exceptions/InvalidTrustManagerException.java index 4bdf59e..97333c5 100644 --- a/src/main/java/com/clevertap/apns/exceptions/InvalidTrustManagerException.java +++ b/src/main/java/com/clevertap/apns/exceptions/InvalidTrustManagerException.java @@ -1,8 +1,6 @@ package com.clevertap.apns.exceptions; -import java.security.cert.CertificateException; - -public class InvalidTrustManagerException extends CertificateException { +public class InvalidTrustManagerException extends Exception { public InvalidTrustManagerException(String s) { super(s); } From af8ce014545ea4b4b780181f1751787a0a6f7c45 Mon Sep 17 00:00:00 2001 From: "mohammed.ali" Date: Mon, 25 Oct 2021 10:45:48 +0530 Subject: [PATCH 09/13] Added InvalidTrustManagerException to the method signature --- .../clevertap/apns/clients/ApnsClientBuilder.java | 3 ++- .../apns/clients/SyncOkHttpApnsClient.java | 13 +++++++++---- .../apns/clients/SyncOkHttpApnsClientTest.java | 5 +++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/clevertap/apns/clients/ApnsClientBuilder.java b/src/main/java/com/clevertap/apns/clients/ApnsClientBuilder.java index 4a7c349..7b4c1a2 100644 --- a/src/main/java/com/clevertap/apns/clients/ApnsClientBuilder.java +++ b/src/main/java/com/clevertap/apns/clients/ApnsClientBuilder.java @@ -31,6 +31,7 @@ package com.clevertap.apns.clients; import com.clevertap.apns.ApnsClient; +import com.clevertap.apns.exceptions.InvalidTrustManagerException; import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; import okhttp3.OkHttpClient.Builder; @@ -177,7 +178,7 @@ public ApnsClientBuilder withGatewayUrl(String url) { public ApnsClient build() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, - UnrecoverableKeyException, KeyManagementException { + UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { if (builder == null) { builder = createDefaultOkHttpClientBuilder(); diff --git a/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java b/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java index bc293de..9eb17aa 100644 --- a/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java +++ b/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java @@ -160,11 +160,12 @@ public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boo * @throws IOException if there is an I/O or format problem with the keystore data, * if a password is required but not given, or if the given password was incorrect * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type + * @throws InvalidTrustManagerException if invalid default TrustManagers are found */ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, String defaultTopic, OkHttpClient.Builder builder) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, - IOException, UnrecoverableKeyException, KeyManagementException { + IOException, UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { this(certificate, password, production, defaultTopic, builder, 443, null); } @@ -185,11 +186,12 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr * @throws IOException if there is an I/O or format problem with the keystore data, * if a password is required but not given, or if the given password was incorrect * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type + * @throws InvalidTrustManagerException if invalid default TrustManagers are found */ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, String defaultTopic, OkHttpClient.Builder builder, String gatewayUrl) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, - IOException, UnrecoverableKeyException, KeyManagementException { + IOException, UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { this(certificate, password, production, defaultTopic, builder, 443, gatewayUrl); } @@ -211,6 +213,7 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr * @throws IOException if there is an I/O or format problem with the keystore data, * if a password is required but not given, or if the given password was incorrect * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type + * @throws InvalidTrustManagerException if invalid default TrustManagers are found */ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, String defaultTopic, OkHttpClient.Builder builder, int connectionPort, String gatewayUrl) @@ -275,11 +278,12 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr * @throws IOException if there is an I/O or format problem with the keystore data, * if a password is required but not given, or if the given password was incorrect * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type + * @throws InvalidTrustManagerException if invalid default TrustManagers are found */ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, String defaultTopic, OkHttpClient.Builder builder, int connectionPort) throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, - KeyManagementException, IOException { + KeyManagementException, IOException, InvalidTrustManagerException { this(certificate, password, production, defaultTopic, builder, connectionPort, null); } @@ -299,11 +303,12 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr * @throws IOException if there is an I/O or format problem with the keystore data, * if a password is required but not given, or if the given password was incorrect * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type + * @throws InvalidTrustManagerException if invalid default TrustManagers are found */ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, String defaultTopic, ConnectionPool connectionPool) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, - IOException, UnrecoverableKeyException, KeyManagementException { + IOException, UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { this(certificate, password, production, defaultTopic, getBuilder(connectionPool)); } diff --git a/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java index 06a8343..3140e1c 100644 --- a/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java +++ b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java @@ -16,6 +16,7 @@ import com.clevertap.apns.Notification; import com.clevertap.apns.NotificationResponse; +import com.clevertap.apns.exceptions.InvalidTrustManagerException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -90,7 +91,7 @@ protected InputStream getClientCertPKCS12() throws KeyStoreException, Certificat * @return apnsClient */ private ApnsClient buildClientWithCert(boolean withOkHttpClientBuilder, String gatewayUrl) throws CertificateException, - UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException { + UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException, InvalidTrustManagerException { ApnsClientBuilder builder = new ApnsClientBuilder() .withDefaultTopic(DEFAULT_TOPIC) .withCertificate(getClientCertPKCS12()) @@ -111,7 +112,7 @@ private ApnsClient buildClientWithCert(boolean withOkHttpClientBuilder, String g @Test void pushTestWithCert() throws IOException, CertificateException, InterruptedException, UnrecoverableKeyException, - NoSuchAlgorithmException, KeyStoreException, KeyManagementException, NoSuchFieldException, IllegalAccessException { + NoSuchAlgorithmException, KeyStoreException, KeyManagementException, NoSuchFieldException, IllegalAccessException, InvalidTrustManagerException { MockWebServer server = new MockWebServer(); try { server.useHttps(serverCertificateChain.sslSocketFactory(), false); From 3742cb3e372e8edbb90d06f818f4e0116820d23a Mon Sep 17 00:00:00 2001 From: "mohammed.ali" Date: Mon, 25 Oct 2021 10:48:49 +0530 Subject: [PATCH 10/13] Added InvalidTrustManagerException to the method signature --- .../clevertap/apns/clients/AsyncOkHttpApnsClient.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/clevertap/apns/clients/AsyncOkHttpApnsClient.java b/src/main/java/com/clevertap/apns/clients/AsyncOkHttpApnsClient.java index 9f34866..ac3d558 100644 --- a/src/main/java/com/clevertap/apns/clients/AsyncOkHttpApnsClient.java +++ b/src/main/java/com/clevertap/apns/clients/AsyncOkHttpApnsClient.java @@ -33,6 +33,7 @@ import com.clevertap.apns.Notification; import com.clevertap.apns.NotificationResponse; import com.clevertap.apns.NotificationResponseListener; +import com.clevertap.apns.exceptions.InvalidTrustManagerException; import okhttp3.*; import java.io.IOException; @@ -56,7 +57,7 @@ public AsyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, public AsyncOkHttpApnsClient(InputStream certificate, String password, boolean production, String defaultTopic, ConnectionPool connectionPool) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, - IOException, UnrecoverableKeyException, KeyManagementException { + IOException, UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { super(certificate, password, production, defaultTopic, connectionPool); } @@ -79,21 +80,21 @@ public AsyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, public AsyncOkHttpApnsClient(InputStream certificate, String password, boolean production, String defaultTopic, OkHttpClient.Builder builder) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, - IOException, UnrecoverableKeyException, KeyManagementException { + IOException, UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { this(certificate, password, production, defaultTopic, builder, 443); } public AsyncOkHttpApnsClient(InputStream certificate, String password, boolean production, String defaultTopic, OkHttpClient.Builder builder, int connectionPort, String gatewayUrl) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, - IOException, UnrecoverableKeyException, KeyManagementException { + IOException, UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { super(certificate, password, production, defaultTopic, builder, gatewayUrl); } public AsyncOkHttpApnsClient(InputStream certificate, String password, boolean production, String defaultTopic, OkHttpClient.Builder builder, int connectionPort) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, - IOException, UnrecoverableKeyException, KeyManagementException { + IOException, UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { this(certificate, password, production, defaultTopic, builder, 443, null); } From 096be4f0bbb3fe5fc5fcd0aa5f7ab7f02e307ae2 Mon Sep 17 00:00:00 2001 From: Jude Pereira Date: Mon, 25 Oct 2021 12:19:35 +0200 Subject: [PATCH 11/13] Reformat + clarify when InvalidTrustManagerException is thrown. --- .../apns/clients/SyncOkHttpApnsClient.java | 228 +++++++++++------- 1 file changed, 140 insertions(+), 88 deletions(-) diff --git a/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java b/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java index 9eb17aa..d08924d 100644 --- a/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java +++ b/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java @@ -66,38 +66,43 @@ public class SyncOkHttpApnsClient implements ApnsClient { /** * Creates a new client which uses token authentication API. * - * @param apnsAuthKey The private key - exclude -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- + * @param apnsAuthKey The private key - exclude -----BEGIN PRIVATE KEY----- and -----END + * PRIVATE KEY----- * @param teamID The team ID * @param keyID The key ID (retrieved from the file name) * @param production Whether to use the production endpoint or the sandbox endpoint * @param defaultTopic A default topic (can be changed per message) - * @param clientBuilder An OkHttp client builder, possibly pre-initialized, to build the actual client - * @param gatewayUrl The gateway url the APNS client should point to + * @param clientBuilder An OkHttp client builder, possibly pre-initialized, to build the actual + * client + * @param gatewayUrl The gateway url the APNS client should point to */ public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boolean production, - String defaultTopic, OkHttpClient.Builder clientBuilder, String gatewayUrl) { + String defaultTopic, OkHttpClient.Builder clientBuilder, String gatewayUrl) { this(apnsAuthKey, teamID, keyID, production, defaultTopic, clientBuilder, 443, gatewayUrl); } public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boolean production, - String defaultTopic, OkHttpClient.Builder clientBuilder) { + String defaultTopic, OkHttpClient.Builder clientBuilder) { this(apnsAuthKey, teamID, keyID, production, defaultTopic, clientBuilder, 443, null); } /** * Creates a new client which uses token authentication API. * - * @param apnsAuthKey The private key - exclude -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- + * @param apnsAuthKey The private key - exclude -----BEGIN PRIVATE KEY----- and -----END + * PRIVATE KEY----- * @param teamID The team ID * @param keyID The key ID (retrieved from the file name) * @param production Whether to use the production endpoint or the sandbox endpoint * @param defaultTopic A default topic (can be changed per message) - * @param clientBuilder An OkHttp client builder, possibly pre-initialized, to build the actual client + * @param clientBuilder An OkHttp client builder, possibly pre-initialized, to build the actual + * client * @param connectionPort The port to establish a connection with APNs. Either 443 or 2197 * @param gatewayUrl The gateway url the APNS client should point to */ public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boolean production, - String defaultTopic, OkHttpClient.Builder clientBuilder, int connectionPort, String gatewayUrl) { + String defaultTopic, OkHttpClient.Builder clientBuilder, int connectionPort, + String gatewayUrl) { this.apnsAuthKey = apnsAuthKey; this.teamID = teamID; this.keyID = keyID; @@ -106,7 +111,9 @@ public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boo this.defaultTopic = defaultTopic; if (gatewayUrl == null) { - gateway = (production ? Constants.ENDPOINT_PRODUCTION : Constants.ENDPOINT_SANDBOX) + ":" + connectionPort; + gateway = + (production ? Constants.ENDPOINT_PRODUCTION : Constants.ENDPOINT_SANDBOX) + ":" + + connectionPort; } else { gateway = gatewayUrl; } @@ -115,23 +122,27 @@ public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boo /** * Creates a new client which uses token authentication API. * - * @param apnsAuthKey The private key - exclude -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- + * @param apnsAuthKey The private key - exclude -----BEGIN PRIVATE KEY----- and -----END + * PRIVATE KEY----- * @param teamID The team ID * @param keyID The key ID (retrieved from the file name) * @param production Whether to use the production endpoint or the sandbox endpoint * @param defaultTopic A default topic (can be changed per message) - * @param clientBuilder An OkHttp client builder, possibly pre-initialized, to build the actual client + * @param clientBuilder An OkHttp client builder, possibly pre-initialized, to build the actual + * client * @param connectionPort The port to establish a connection with APNs. Either 443 or 2197 */ public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boolean production, - String defaultTopic, OkHttpClient.Builder clientBuilder, int connectionPort) { - this(apnsAuthKey, teamID, keyID, production, defaultTopic, clientBuilder, connectionPort, null); + String defaultTopic, OkHttpClient.Builder clientBuilder, int connectionPort) { + this(apnsAuthKey, teamID, keyID, production, defaultTopic, clientBuilder, connectionPort, + null); } /** * Creates a new client which uses token authentication API. * - * @param apnsAuthKey The private key - exclude -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- + * @param apnsAuthKey The private key - exclude -----BEGIN PRIVATE KEY----- and -----END + * PRIVATE KEY----- * @param teamID The team ID * @param keyID The key ID (retrieved from the file name) * @param production Whether to use the production endpoint or the sandbox endpoint @@ -139,84 +150,103 @@ public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boo * @param connectionPool A connection pool to use. If null, a new one will be generated */ public SyncOkHttpApnsClient(String apnsAuthKey, String teamID, String keyID, boolean production, - String defaultTopic, ConnectionPool connectionPool) { + String defaultTopic, ConnectionPool connectionPool) { this(apnsAuthKey, teamID, keyID, production, defaultTopic, getBuilder(connectionPool)); } /** - * Creates a new client and automatically loads the key store - * with the push certificate read from the input stream. + * Creates a new client and automatically loads the key store with the push certificate read + * from the input stream. * * @param certificate The client certificate to be used * @param password The password (if required, else null) * @param production Whether to use the production endpoint or the sandbox endpoint * @param defaultTopic A default topic (can be changed per message) - * @param builder An OkHttp client builder, possibly pre-initialized, to build the actual client - * @throws UnrecoverableKeyException If the key cannot be recovered - * @throws KeyManagementException if the key failed to be loaded - * @throws CertificateException if any of the certificates in the keystore could not be loaded - * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found - * @throws IOException if there is an I/O or format problem with the keystore data, - * if a password is required but not given, or if the given password was incorrect - * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type - * @throws InvalidTrustManagerException if invalid default TrustManagers are found + * @param builder An OkHttp client builder, possibly pre-initialized, to build the actual + * client + * @throws UnrecoverableKeyException If the key cannot be recovered + * @throws KeyManagementException if the key failed to be loaded + * @throws CertificateException if any of the certificates in the keystore could not be + * loaded + * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the + * keystore cannot be found + * @throws IOException if there is an I/O or format problem with the keystore + * data, if a password is required but not given, or if the + * given password was incorrect + * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for + * the specified type + * @throws InvalidTrustManagerException if two or more TrustManagers were found (unsupoprted by + * the underlying OkHttp library) */ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, - String defaultTopic, OkHttpClient.Builder builder) + String defaultTopic, OkHttpClient.Builder builder) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { this(certificate, password, production, defaultTopic, builder, 443, null); } /** - * Creates a new client and automatically loads the key store - * with the push certificate read from the input stream. + * Creates a new client and automatically loads the key store with the push certificate read + * from the input stream. * * @param certificate The client certificate to be used * @param password The password (if required, else null) * @param production Whether to use the production endpoint or the sandbox endpoint * @param defaultTopic A default topic (can be changed per message) - * @param builder An OkHttp client builder, possibly pre-initialized, to build the actual client - * @param gatewayUrl The gateway url the APNS client should point to - * @throws UnrecoverableKeyException If the key cannot be recovered - * @throws KeyManagementException if the key failed to be loaded - * @throws CertificateException if any of the certificates in the keystore could not be loaded - * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found - * @throws IOException if there is an I/O or format problem with the keystore data, - * if a password is required but not given, or if the given password was incorrect - * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type - * @throws InvalidTrustManagerException if invalid default TrustManagers are found + * @param builder An OkHttp client builder, possibly pre-initialized, to build the actual + * client + * @param gatewayUrl The gateway url the APNS client should point to + * @throws UnrecoverableKeyException If the key cannot be recovered + * @throws KeyManagementException if the key failed to be loaded + * @throws CertificateException if any of the certificates in the keystore could not be + * loaded + * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the + * keystore cannot be found + * @throws IOException if there is an I/O or format problem with the keystore + * data, if a password is required but not given, or if the + * given password was incorrect + * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for + * the specified type + * @throws InvalidTrustManagerException if two or more TrustManagers were found (unsupoprted by + * the underlying OkHttp library) */ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, - String defaultTopic, OkHttpClient.Builder builder, String gatewayUrl) + String defaultTopic, OkHttpClient.Builder builder, String gatewayUrl) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { this(certificate, password, production, defaultTopic, builder, 443, gatewayUrl); } /** - * Creates a new client and automatically loads the key store - * with the push certificate read from the input stream. + * Creates a new client and automatically loads the key store with the push certificate read + * from the input stream. * * @param certificate The client certificate to be used * @param password The password (if required, else null) * @param production Whether to use the production endpoint or the sandbox endpoint * @param defaultTopic A default topic (can be changed per message) - * @param builder An OkHttp client builder, possibly pre-initialized, to build the actual client + * @param builder An OkHttp client builder, possibly pre-initialized, to build the actual + * client * @param connectionPort The port to establish a connection with APNs. Either 443 or 2197 * @param gatewayUrl The gateway url the APNS client should point to - * @throws UnrecoverableKeyException If the key cannot be recovered - * @throws KeyManagementException if the key failed to be loaded - * @throws CertificateException if any of the certificates in the keystore could not be loaded - * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found - * @throws IOException if there is an I/O or format problem with the keystore data, - * if a password is required but not given, or if the given password was incorrect - * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type - * @throws InvalidTrustManagerException if invalid default TrustManagers are found + * @throws UnrecoverableKeyException If the key cannot be recovered + * @throws KeyManagementException if the key failed to be loaded + * @throws CertificateException if any of the certificates in the keystore could not be + * loaded + * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the + * keystore cannot be found + * @throws IOException if there is an I/O or format problem with the keystore + * data, if a password is required but not given, or if the + * given password was incorrect + * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for + * the specified type + * @throws InvalidTrustManagerException if two or more TrustManagers were found (unsupoprted by + * the underlying OkHttp library) */ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, - String defaultTopic, OkHttpClient.Builder builder, int connectionPort, String gatewayUrl) + String defaultTopic, OkHttpClient.Builder builder, int connectionPort, + String gatewayUrl) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { @@ -226,24 +256,29 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr KeyStore ks = KeyStore.getInstance("PKCS12"); ks.load(certificate, password.toCharArray()); - final X509Certificate cert = (X509Certificate) ks.getCertificate(ks.aliases().nextElement()); + final X509Certificate cert = (X509Certificate) ks.getCertificate( + ks.aliases().nextElement()); CertificateUtils.validateCertificate(production, cert); - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + KeyManagerFactory kmf = KeyManagerFactory.getInstance( + KeyManagerFactory.getDefaultAlgorithm()); kmf.init(ks, password.toCharArray()); KeyManager[] keyManagers = kmf.getKeyManagers(); SSLContext sslContext = SSLContext.getInstance("TLS"); - final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + final TrustManagerFactory tmf = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); tmf.init((KeyStore) null); // check if there is an existing TrustManager configured in the builder TrustManager[] trustManagers = (builder.getX509TrustManagerOrNull$okhttp() != null) ? - new TrustManager[] {builder.getX509TrustManagerOrNull$okhttp()} : tmf.getTrustManagers(); + new TrustManager[]{builder.getX509TrustManagerOrNull$okhttp()} + : tmf.getTrustManagers(); sslContext.init(keyManagers, trustManagers, null); if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new InvalidTrustManagerException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); + throw new InvalidTrustManagerException( + "Unexpected default trust managers:" + Arrays.toString(trustManagers)); } final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); @@ -255,58 +290,71 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr this.defaultTopic = defaultTopic; if (gatewayUrl == null) { - gateway = (production ? Constants.ENDPOINT_PRODUCTION : Constants.ENDPOINT_SANDBOX) + ":" + connectionPort; + gateway = + (production ? Constants.ENDPOINT_PRODUCTION : Constants.ENDPOINT_SANDBOX) + ":" + + connectionPort; } else { gateway = gatewayUrl; } } /** - * Creates a new client and automatically loads the key store - * with the push certificate read from the input stream. + * Creates a new client and automatically loads the key store with the push certificate read + * from the input stream. * * @param certificate The client certificate to be used * @param password The password (if required, else null) * @param production Whether to use the production endpoint or the sandbox endpoint * @param defaultTopic A default topic (can be changed per message) - * @param builder An OkHttp client builder, possibly pre-initialized, to build the actual client + * @param builder An OkHttp client builder, possibly pre-initialized, to build the actual + * client * @param connectionPort The port to establish a connection with APNs. Either 443 or 2197 - * @throws UnrecoverableKeyException If the key cannot be recovered - * @throws KeyManagementException if the key failed to be loaded - * @throws CertificateException if any of the certificates in the keystore could not be loaded - * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found - * @throws IOException if there is an I/O or format problem with the keystore data, - * if a password is required but not given, or if the given password was incorrect - * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type - * @throws InvalidTrustManagerException if invalid default TrustManagers are found + * @throws UnrecoverableKeyException If the key cannot be recovered + * @throws KeyManagementException if the key failed to be loaded + * @throws CertificateException if any of the certificates in the keystore could not be + * loaded + * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the + * keystore cannot be found + * @throws IOException if there is an I/O or format problem with the keystore + * data, if a password is required but not given, or if the + * given password was incorrect + * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for + * the specified type + * @throws InvalidTrustManagerException if two or more TrustManagers were found (unsupoprted by + * the underlying OkHttp library) */ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, - String defaultTopic, OkHttpClient.Builder builder, int connectionPort) + String defaultTopic, OkHttpClient.Builder builder, int connectionPort) throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException, InvalidTrustManagerException { this(certificate, password, production, defaultTopic, builder, connectionPort, null); } /** - * Creates a new client and automatically loads the key store - * with the push certificate read from the input stream. + * Creates a new client and automatically loads the key store with the push certificate read + * from the input stream. * * @param certificate The client certificate to be used * @param password The password (if required, else null) * @param production Whether to use the production endpoint or the sandbox endpoint * @param defaultTopic A default topic (can be changed per message) * @param connectionPool A connection pool to use. If null, a new one will be generated - * @throws UnrecoverableKeyException If the key cannot be recovered - * @throws KeyManagementException if the key failed to be loaded - * @throws CertificateException if any of the certificates in the keystore could not be loaded - * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found - * @throws IOException if there is an I/O or format problem with the keystore data, - * if a password is required but not given, or if the given password was incorrect - * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type - * @throws InvalidTrustManagerException if invalid default TrustManagers are found + * @throws UnrecoverableKeyException If the key cannot be recovered + * @throws KeyManagementException if the key failed to be loaded + * @throws CertificateException if any of the certificates in the keystore could not be + * loaded + * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the + * keystore cannot be found + * @throws IOException if there is an I/O or format problem with the keystore + * data, if a password is required but not given, or if the + * given password was incorrect + * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for + * the specified type + * @throws InvalidTrustManagerException if two or more TrustManagers were found (unsupoprted by + * the underlying OkHttp library) */ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production, - String defaultTopic, ConnectionPool connectionPool) + String defaultTopic, ConnectionPool connectionPool) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException, KeyManagementException, InvalidTrustManagerException { @@ -314,9 +362,9 @@ public SyncOkHttpApnsClient(InputStream certificate, String password, boolean pr } /** - * Creates a default builder that can be customized later and then passed to one of - * the constructors taking a builder instance. The constructors that don't take - * builders themselves use this method internally to create their client builders. + * Creates a default builder that can be customized later and then passed to one of the + * constructors taking a builder instance. The constructors that don't take builders themselves + * use this method internally to create their client builders. * * @param connectionPool A connection pool to use. If null, a new one will be generated * @return a new OkHttp client builder, intialized with default settings. @@ -337,11 +385,13 @@ public boolean isSynchronous() { @Override public void push(Notification notification, NotificationResponseListener listener) { - throw new UnsupportedOperationException("Asynchronous requests are not supported by this client"); + throw new UnsupportedOperationException( + "Asynchronous requests are not supported by this client"); } protected final Request buildRequest(Notification notification) { - final String topic = notification.getTopic() != null ? notification.getTopic() : defaultTopic; + final String topic = + notification.getTopic() != null ? notification.getTopic() : defaultTopic; final String collapseId = notification.getCollapseId(); final UUID uuid = notification.getUuid(); final long expiration = notification.getExpiration(); @@ -360,7 +410,8 @@ public void writeTo(BufferedSink sink) throws IOException { sink.write(notification.getPayload().getBytes(Constants.UTF_8)); } }) - .header("content-length", notification.getPayload().getBytes(Constants.UTF_8).length + ""); + .header("content-length", + notification.getPayload().getBytes(Constants.UTF_8).length + ""); if (topic != null) { rb.header("apns-topic", topic); @@ -389,7 +440,8 @@ public void writeTo(BufferedSink sink) throws IOException { if (keyID != null && teamID != null && apnsAuthKey != null) { // Generate a new JWT token if it's null, or older than 55 minutes - if (cachedJWTToken == null || System.currentTimeMillis() - lastJWTTokenTS > 55 * 60 * 1000) { + if (cachedJWTToken == null + || System.currentTimeMillis() - lastJWTTokenTS > 55 * 60 * 1000) { try { lastJWTTokenTS = System.currentTimeMillis(); cachedJWTToken = JWT.getToken(teamID, keyID, apnsAuthKey); From b6f9b5192835f07b42b939fd771d81cb9a624a36 Mon Sep 17 00:00:00 2001 From: Jude Pereira Date: Mon, 25 Oct 2021 12:22:31 +0200 Subject: [PATCH 12/13] Add test. --- .../InvalidTrustManagerExceptionTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/test/java/com/clevertap/apns/exceptions/InvalidTrustManagerExceptionTest.java diff --git a/src/test/java/com/clevertap/apns/exceptions/InvalidTrustManagerExceptionTest.java b/src/test/java/com/clevertap/apns/exceptions/InvalidTrustManagerExceptionTest.java new file mode 100644 index 0000000..aafd699 --- /dev/null +++ b/src/test/java/com/clevertap/apns/exceptions/InvalidTrustManagerExceptionTest.java @@ -0,0 +1,17 @@ +package com.clevertap.apns.exceptions; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +/** + * Created by Jude Pereira, at 12:21 on 25/10/2021. + */ +class InvalidTrustManagerExceptionTest { + + @Test + void constructor() { + final InvalidTrustManagerException ex = new InvalidTrustManagerException("foo"); + assertEquals("foo", ex.getMessage()); + } +} \ No newline at end of file From c222663e0b0d3ec594b0388ec42c28c5c00c282b Mon Sep 17 00:00:00 2001 From: Jude Pereira Date: Mon, 25 Oct 2021 12:30:25 +0200 Subject: [PATCH 13/13] Add tests. --- .../apns/clients/SyncOkHttpApnsClient.java | 20 +++ .../clients/SyncOkHttpApnsClientTest.java | 134 +++++++++++------- 2 files changed, 102 insertions(+), 52 deletions(-) diff --git a/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java b/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java index d08924d..970b6a2 100644 --- a/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java +++ b/src/main/java/com/clevertap/apns/clients/SyncOkHttpApnsClient.java @@ -378,6 +378,26 @@ private static OkHttpClient.Builder getBuilder(ConnectionPool connectionPool) { return builder; } + public String getDefaultTopic() { + return defaultTopic; + } + + public String getApnsAuthKey() { + return apnsAuthKey; + } + + public String getTeamID() { + return teamID; + } + + public String getKeyID() { + return keyID; + } + + public String getGateway() { + return gateway; + } + @Override public boolean isSynchronous() { return true; diff --git a/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java index 3140e1c..164a3b0 100644 --- a/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java +++ b/src/test/java/com/clevertap/apns/clients/SyncOkHttpApnsClientTest.java @@ -1,31 +1,34 @@ package com.clevertap.apns.clients; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import com.clevertap.apns.ApnsClient; +import com.clevertap.apns.LocalHttpServer; +import com.clevertap.apns.Notification; +import com.clevertap.apns.NotificationResponse; +import com.clevertap.apns.exceptions.InvalidTrustManagerException; +import com.clevertap.apns.internal.Constants; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.security.*; +import java.security.KeyManagementException; +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; import java.security.cert.X509Certificate; - -import com.clevertap.apns.ApnsClient; -import com.clevertap.apns.LocalHttpServer; -import com.clevertap.apns.Notification; -import com.clevertap.apns.NotificationResponse; - -import com.clevertap.apns.exceptions.InvalidTrustManagerException; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import okhttp3.OkHttpClient; +import okhttp3.OkHttpClient.Builder; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; import okhttp3.tls.HandshakeCertificates; import okhttp3.tls.HeldCertificate; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; public class SyncOkHttpApnsClientTest { @@ -71,56 +74,62 @@ public static void initCertificates() { /** * Convert client cert to PKCS12 Format and return as InputStream. - * @return */ - protected InputStream getClientCertPKCS12() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { - KeyStore pkcs12 = KeyStore.getInstance("PKCS12"); - pkcs12.load(null, null); - Certificate chain[] = {clientCertificate.certificate()}; - pkcs12.setKeyEntry("privatekeyalias", clientCertificate.keyPair().getPrivate(), CERT_PASSWD.toCharArray(), chain); - - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - pkcs12.store(outStream, CERT_PASSWD.toCharArray()); - - return new ByteArrayInputStream(outStream.toByteArray()); + protected InputStream getClientCertPKCS12() + throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { + KeyStore pkcs12 = KeyStore.getInstance("PKCS12"); + pkcs12.load(null, null); + Certificate[] chain = {clientCertificate.certificate()}; + pkcs12.setKeyEntry("privatekeyalias", clientCertificate.keyPair().getPrivate(), + CERT_PASSWD.toCharArray(), chain); + + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + pkcs12.store(outStream, CERT_PASSWD.toCharArray()); + + return new ByteArrayInputStream(outStream.toByteArray()); } /** * Build ApnsClient with valid client cert in synchronous mode. + * * @return apnsClient */ - private ApnsClient buildClientWithCert(boolean withOkHttpClientBuilder, String gatewayUrl) throws CertificateException, + private ApnsClient buildClientWithCert(boolean withOkHttpClientBuilder, String gatewayUrl) + throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException, InvalidTrustManagerException { - ApnsClientBuilder builder = new ApnsClientBuilder() - .withDefaultTopic(DEFAULT_TOPIC) - .withCertificate(getClientCertPKCS12()) - .withPassword(CERT_PASSWD) - .inSynchronousMode() - .withProductionGateway(); - - if (withOkHttpClientBuilder) { - builder.withOkHttpClientBuilder(new OkHttpClient.Builder().sslSocketFactory(clientCertificateChain.sslSocketFactory(), clientCertificateChain.trustManager())); - } - - if (gatewayUrl != null) { - builder.withGatewayUrl(gatewayUrl); - } - - return builder.build(); + ApnsClientBuilder builder = new ApnsClientBuilder() + .withDefaultTopic(DEFAULT_TOPIC) + .withCertificate(getClientCertPKCS12()) + .withPassword(CERT_PASSWD) + .inSynchronousMode() + .withProductionGateway(); + + if (withOkHttpClientBuilder) { + builder.withOkHttpClientBuilder(new OkHttpClient.Builder().sslSocketFactory( + clientCertificateChain.sslSocketFactory(), + clientCertificateChain.trustManager())); + } + + if (gatewayUrl != null) { + builder.withGatewayUrl(gatewayUrl); + } + + return builder.build(); } @Test - void pushTestWithCert() throws IOException, CertificateException, InterruptedException, UnrecoverableKeyException, - NoSuchAlgorithmException, KeyStoreException, KeyManagementException, NoSuchFieldException, IllegalAccessException, InvalidTrustManagerException { - MockWebServer server = new MockWebServer(); - try { + void pushTestWithCert() + throws IOException, CertificateException, InterruptedException, UnrecoverableKeyException, + NoSuchAlgorithmException, KeyStoreException, KeyManagementException, InvalidTrustManagerException { + try (MockWebServer server = new MockWebServer()) { server.useHttps(serverCertificateChain.sslSocketFactory(), false); server.requestClientAuth(); server.enqueue(new MockResponse().setResponseCode(200).setBody("Hello world!")); String url = server.url("").toString(); - url = url.substring(0, url.length() - 1); // Above method gives a trailing "/" which we want to remove + url = url.substring(0, + url.length() - 1); // Above method gives a trailing "/" which we want to remove ApnsClient client = buildClientWithCert(true, url); @@ -132,17 +141,17 @@ void pushTestWithCert() throws IOException, CertificateException, InterruptedExc .sound("sound") .build() ); - assertEquals("HTTP-Response-Code 200", 200, response.getHttpStatusCode()); + assertEquals(200, response.getHttpStatusCode()); RecordedRequest request = server.takeRequest(); assertEquals("/3/device/" + DEVICE_TOKEN, request.getPath()); assertEquals(DEFAULT_TOPIC, request.getHeader("apns-topic")); - X509Certificate clientCert = (X509Certificate) request.getHandshake().peerCertificates().get(0); - X509Certificate clientChain[] = {clientCert}; + assert request.getHandshake() != null; + X509Certificate clientCert = (X509Certificate) request.getHandshake().peerCertificates() + .get(0); + X509Certificate[] clientChain = {clientCert}; serverCertificateChain.trustManager().checkClientTrusted(clientChain, "RSA"); - } finally { - server.close(); } } @@ -160,7 +169,8 @@ void pushTestWithCertificateWithLocalHttpServer() throws Exception { .sound("sound") .build() ); - assertEquals("Server should be hit and should return 200", 200, response.getHttpStatusCode()); + assertEquals(200, response.getHttpStatusCode(), + "Server should be hit and should return 200"); // Should have the same result as above if the trust manager isn't passed as well client = buildClientWithCert(false, localHttpServer.getUrl()); @@ -172,8 +182,28 @@ void pushTestWithCertificateWithLocalHttpServer() throws Exception { .sound("sound") .build() ); - assertEquals("Server should be hit and should return 200 without trust manager set", 200, response.getHttpStatusCode()); + assertEquals(200, response.getHttpStatusCode(), + "Server should be hit and should return 200 without trust manager set"); localHttpServer.shutDownServer(); } + + @Test + void constructor() { + final SyncOkHttpApnsClient client = new SyncOkHttpApnsClient("authKey", + "teamID", "keyID", true, "defaultTopic", new Builder(), 443, "myGateway"); + + assertEquals("authKey", client.getApnsAuthKey()); + assertEquals("teamID", client.getTeamID()); + assertEquals("keyID", client.getKeyID()); + assertEquals("defaultTopic", client.getDefaultTopic()); + assertEquals("myGateway", client.getGateway()); + } + + @Test + void defaultGateway() { + final SyncOkHttpApnsClient client = new SyncOkHttpApnsClient("authKey", + "teamID", "keyID", true, "defaultTopic", new Builder(), 443, null); + assertEquals(Constants.ENDPOINT_PRODUCTION + ":443", client.getGateway()); + } } \ No newline at end of file