From 10136ed084903f049f04d479cd89d66bcc081142 Mon Sep 17 00:00:00 2001 From: Michael Dyrna Date: Fri, 30 Dec 2022 23:56:36 +0100 Subject: [PATCH] VRS: support new endpoint with client certificate Fixes #506. --- src/de/schildbach/pte/VrsProvider.java | 9 ++---- src/de/schildbach/pte/util/HttpClient.java | 29 +++++++++++++++---- .../pte/live/VrsProviderLiveTest.java | 5 +++- .../pte/live/secrets.properties.template | 1 + 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/de/schildbach/pte/VrsProvider.java b/src/de/schildbach/pte/VrsProvider.java index 385a391a..ca12ec11 100644 --- a/src/de/schildbach/pte/VrsProvider.java +++ b/src/de/schildbach/pte/VrsProvider.java @@ -147,10 +147,7 @@ public class VrsProvider extends AbstractNetworkProvider { public Position position; } - // valid host names: www.vrsinfo.de, android.vrsinfo.de, ios.vrsinfo.de, ekap.vrsinfo.de (only SSL - // encrypted with client certificate) - // performance comparison March 2015 showed www.vrsinfo.de to be fastest for trips - protected static final HttpUrl API_BASE = HttpUrl.parse("http://android.vrsinfo.de/index.php"); + protected static final HttpUrl API_BASE = HttpUrl.parse("https://ekap-app.vrs.de/index.php"); protected static final String SERVER_PRODUCT = "vrs"; @SuppressWarnings("serial") @@ -334,9 +331,9 @@ public class VrsProvider extends AbstractNetworkProvider { STYLES.put("R", new Style(Style.parseColor("#009d81"), Style.WHITE)); } - public VrsProvider() { + public VrsProvider(final byte[] clientCertificate) { super(NetworkId.VRS); - + httpClient.setClientCertificate(clientCertificate); setStyles(STYLES); } diff --git a/src/de/schildbach/pte/util/HttpClient.java b/src/de/schildbach/pte/util/HttpClient.java index 2ae24d75..bf6bf2a7 100644 --- a/src/de/schildbach/pte/util/HttpClient.java +++ b/src/de/schildbach/pte/util/HttpClient.java @@ -19,10 +19,12 @@ package de.schildbach.pte.util; import static com.google.common.base.Preconditions.checkNotNull; +import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; import java.net.HttpURLConnection; import java.net.Proxy; +import java.security.KeyStore; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.HashMap; @@ -34,6 +36,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; @@ -81,6 +85,8 @@ public final class HttpClient { private Proxy proxy = null; private boolean trustAllCertificates = false; @Nullable + private byte[] clientCertificate = null; + @Nullable private CertificatePinner certificatePinner = null; private static final List RESPONSE_CODES_BLOCKED = Ints.asList(HttpURLConnection.HTTP_BAD_REQUEST, @@ -190,6 +196,10 @@ public final class HttpClient { this.trustAllCertificates = trustAllCertificates; } + public void setClientCertificate(final byte[] clientCertificate) { + this.clientCertificate = clientCertificate; + } + public void setCertificatePin(final String host, final String... hashes) { this.certificatePinner = new CertificatePinner.Builder().add(host, hashes).build(); } @@ -238,12 +248,12 @@ public final class HttpClient { request.header("Cookie", sessionCookie.toString()); final OkHttpClient okHttpClient; - if (proxy != null || trustAllCertificates || certificatePinner != null) { + if (proxy != null || trustAllCertificates || certificatePinner != null || clientCertificate != null) { final OkHttpClient.Builder builder = OKHTTP_CLIENT.newBuilder(); if (proxy != null) builder.proxy(proxy); - if (trustAllCertificates) - trustAllCertificates(builder); + if (trustAllCertificates || clientCertificate != null) + configureSSL(builder); if (certificatePinner != null) builder.certificatePinner(certificatePinner); okHttpClient = builder.build(); @@ -340,10 +350,19 @@ public final class HttpClient { return false; } - private void trustAllCertificates(final OkHttpClient.Builder okHttpClientBuilder) { + private void configureSSL(final OkHttpClient.Builder okHttpClientBuilder) { try { final SSLContext sslContext = SSLContext.getInstance("SSL"); - sslContext.init(null, new TrustManager[] { TRUST_ALL_CERTIFICATES }, null); + KeyManager[] keyManagers = null; + if (clientCertificate != null) { + final char[] keyStorePassword = "".toCharArray(); + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + keyStore.load(new ByteArrayInputStream(clientCertificate), keyStorePassword); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, keyStorePassword); + keyManagers = keyManagerFactory.getKeyManagers(); + } + sslContext.init(keyManagers, new TrustManager[] { TRUST_ALL_CERTIFICATES }, null); final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); okHttpClientBuilder.sslSocketFactory(sslSocketFactory, TRUST_ALL_CERTIFICATES); } catch (final Exception x) { diff --git a/test/de/schildbach/pte/live/VrsProviderLiveTest.java b/test/de/schildbach/pte/live/VrsProviderLiveTest.java index 2751071d..6f1096af 100644 --- a/test/de/schildbach/pte/live/VrsProviderLiveTest.java +++ b/test/de/schildbach/pte/live/VrsProviderLiveTest.java @@ -32,6 +32,7 @@ import java.util.Random; import java.util.Set; import java.util.TreeSet; +import com.google.common.io.BaseEncoding; import org.junit.Ignore; import org.junit.Test; @@ -58,8 +59,10 @@ import de.schildbach.pte.dto.TripOptions; * @author Michael Dyrna */ public class VrsProviderLiveTest extends AbstractProviderLiveTest { + private static final BaseEncoding BASE64 = BaseEncoding.base64(); + public VrsProviderLiveTest() { - super(new VrsProvider()); + super(new VrsProvider(BASE64.decode(secretProperty("vrs.client_certificate")))); } @Test diff --git a/test/de/schildbach/pte/live/secrets.properties.template b/test/de/schildbach/pte/live/secrets.properties.template index 8d2075ea..ee89c204 100644 --- a/test/de/schildbach/pte/live/secrets.properties.template +++ b/test/de/schildbach/pte/live/secrets.properties.template @@ -32,3 +32,4 @@ se.api_authorization = lu.api_authorization = bart.api_authorization = cmta.api_authorization = +vrs.client_certificate =