VRS: support new endpoint with client certificate

Fixes #506.
This commit is contained in:
Michael Dyrna 2022-12-30 23:56:36 +01:00 committed by Andreas Schildbach
parent 915538f069
commit 10136ed084
4 changed files with 32 additions and 12 deletions

View file

@ -147,10 +147,7 @@ public class VrsProvider extends AbstractNetworkProvider {
public Position position; public Position position;
} }
// valid host names: www.vrsinfo.de, android.vrsinfo.de, ios.vrsinfo.de, ekap.vrsinfo.de (only SSL protected static final HttpUrl API_BASE = HttpUrl.parse("https://ekap-app.vrs.de/index.php");
// 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 String SERVER_PRODUCT = "vrs"; protected static final String SERVER_PRODUCT = "vrs";
@SuppressWarnings("serial") @SuppressWarnings("serial")
@ -334,9 +331,9 @@ public class VrsProvider extends AbstractNetworkProvider {
STYLES.put("R", new Style(Style.parseColor("#009d81"), Style.WHITE)); STYLES.put("R", new Style(Style.parseColor("#009d81"), Style.WHITE));
} }
public VrsProvider() { public VrsProvider(final byte[] clientCertificate) {
super(NetworkId.VRS); super(NetworkId.VRS);
httpClient.setClientCertificate(clientCertificate);
setStyles(STYLES); setStyles(STYLES);
} }

View file

@ -19,10 +19,12 @@ package de.schildbach.pte.util;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import java.io.ByteArrayInputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.Proxy; import java.net.Proxy;
import java.security.KeyStore;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.HashMap; import java.util.HashMap;
@ -34,6 +36,8 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
@ -81,6 +85,8 @@ public final class HttpClient {
private Proxy proxy = null; private Proxy proxy = null;
private boolean trustAllCertificates = false; private boolean trustAllCertificates = false;
@Nullable @Nullable
private byte[] clientCertificate = null;
@Nullable
private CertificatePinner certificatePinner = null; private CertificatePinner certificatePinner = null;
private static final List<Integer> RESPONSE_CODES_BLOCKED = Ints.asList(HttpURLConnection.HTTP_BAD_REQUEST, private static final List<Integer> RESPONSE_CODES_BLOCKED = Ints.asList(HttpURLConnection.HTTP_BAD_REQUEST,
@ -190,6 +196,10 @@ public final class HttpClient {
this.trustAllCertificates = trustAllCertificates; this.trustAllCertificates = trustAllCertificates;
} }
public void setClientCertificate(final byte[] clientCertificate) {
this.clientCertificate = clientCertificate;
}
public void setCertificatePin(final String host, final String... hashes) { public void setCertificatePin(final String host, final String... hashes) {
this.certificatePinner = new CertificatePinner.Builder().add(host, hashes).build(); this.certificatePinner = new CertificatePinner.Builder().add(host, hashes).build();
} }
@ -238,12 +248,12 @@ public final class HttpClient {
request.header("Cookie", sessionCookie.toString()); request.header("Cookie", sessionCookie.toString());
final OkHttpClient okHttpClient; final OkHttpClient okHttpClient;
if (proxy != null || trustAllCertificates || certificatePinner != null) { if (proxy != null || trustAllCertificates || certificatePinner != null || clientCertificate != null) {
final OkHttpClient.Builder builder = OKHTTP_CLIENT.newBuilder(); final OkHttpClient.Builder builder = OKHTTP_CLIENT.newBuilder();
if (proxy != null) if (proxy != null)
builder.proxy(proxy); builder.proxy(proxy);
if (trustAllCertificates) if (trustAllCertificates || clientCertificate != null)
trustAllCertificates(builder); configureSSL(builder);
if (certificatePinner != null) if (certificatePinner != null)
builder.certificatePinner(certificatePinner); builder.certificatePinner(certificatePinner);
okHttpClient = builder.build(); okHttpClient = builder.build();
@ -340,10 +350,19 @@ public final class HttpClient {
return false; return false;
} }
private void trustAllCertificates(final OkHttpClient.Builder okHttpClientBuilder) { private void configureSSL(final OkHttpClient.Builder okHttpClientBuilder) {
try { try {
final SSLContext sslContext = SSLContext.getInstance("SSL"); 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(); final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
okHttpClientBuilder.sslSocketFactory(sslSocketFactory, TRUST_ALL_CERTIFICATES); okHttpClientBuilder.sslSocketFactory(sslSocketFactory, TRUST_ALL_CERTIFICATES);
} catch (final Exception x) { } catch (final Exception x) {

View file

@ -32,6 +32,7 @@ import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import com.google.common.io.BaseEncoding;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
@ -58,8 +59,10 @@ import de.schildbach.pte.dto.TripOptions;
* @author Michael Dyrna * @author Michael Dyrna
*/ */
public class VrsProviderLiveTest extends AbstractProviderLiveTest { public class VrsProviderLiveTest extends AbstractProviderLiveTest {
private static final BaseEncoding BASE64 = BaseEncoding.base64();
public VrsProviderLiveTest() { public VrsProviderLiveTest() {
super(new VrsProvider()); super(new VrsProvider(BASE64.decode(secretProperty("vrs.client_certificate"))));
} }
@Test @Test

View file

@ -32,3 +32,4 @@ se.api_authorization =
lu.api_authorization = lu.api_authorization =
bart.api_authorization = bart.api_authorization =
cmta.api_authorization = cmta.api_authorization =
vrs.client_certificate =