Replace javapinning dependency with new PinnedPubKeyTrustManager

This commit is contained in:
Travis Burtrum 2019-04-14 23:14:02 -04:00
parent 557a33fda8
commit 7492f5d23d
4 changed files with 179 additions and 21 deletions

View File

@ -9,14 +9,15 @@
<artifactId>jDnsProxy</artifactId> <artifactId>jDnsProxy</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<name>${project.artifactId}</name>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>eu.geekplace.javapinning</groupId> <groupId>junit</groupId>
<artifactId>java-pinning-java7</artifactId> <artifactId>junit</artifactId>
<version>1.1.0</version> <version>4.12</version>
<scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<name>${project.artifactId}</name>
<build> <build>
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>
</build> </build>

View File

@ -1,14 +1,11 @@
package com.moparisthebest.dns.net; package com.moparisthebest.dns.net;
import eu.geekplace.javapinning.java7.Java7Pinning;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import java.net.*; import java.net.*;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.*; import java.util.*;
import static com.moparisthebest.dns.tls.PinnedPubKeyTrustManager.pinSha256SSLContext;
public class ParsedUrl { public class ParsedUrl {
private final String urlStr; private final String urlStr;
@ -57,18 +54,7 @@ public class ParsedUrl {
SSLSocketFactory sslSocketFactory = null; SSLSocketFactory sslSocketFactory = null;
final String pubKeyPinsSha256 = props.get("pubKeyPinsSha256"); final String pubKeyPinsSha256 = props.get("pubKeyPinsSha256");
if (pubKeyPinsSha256 != null) { if (pubKeyPinsSha256 != null) {
final String[] pins = pubKeyPinsSha256.split(","); sslSocketFactory = pinSha256SSLContext(pubKeyPinsSha256.split(",")).getSocketFactory();
// todo: ugh java-pinning only supports hex not base64 *and* hashes the cert one time per pin, fix this
for (int x = 0; x < pins.length; ++x) {
pins[x] = "SHA256:" + bytesToHex(Base64.getDecoder().decode(pins[x]));
}
final SSLContext sslContext;
try {
sslContext = Java7Pinning.forPins(pins);
} catch (KeyManagementException | NoSuchAlgorithmException e) {
throw new RuntimeException("invalid pins", e);
}
sslSocketFactory = sslContext.getSocketFactory();
} }
if(sslSocketFactory == null && url.getScheme().equals("tls")) if(sslSocketFactory == null && url.getScheme().equals("tls"))
sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();

View File

@ -0,0 +1,127 @@
package com.moparisthebest.dns.tls;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Base64;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Given a MessageDigest algorithm name and a list of public key hashes, implements secure TLS certificate verification
* via public key pinning.
*/
public class PinnedPubKeyTrustManager implements X509TrustManager {
/**
* Create TLS SSLContext for a single TrustManager
*
* @param trustManager TrustManager instance
* @return SSLContext
*/
public static SSLContext sslContext(final TrustManager trustManager) {
try {
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
return sslContext;
} catch (KeyManagementException | NoSuchAlgorithmException e) {
throw new RuntimeException("TLS not supported or ?", e);
}
}
/**
* Create pinned pubkey SSLContext
*
* @param sha256Pins HPKP RFC7469 style base64'd sha256 pins
* @return SSLContext that only accepts servers with these pubkeys
*/
public static SSLContext pinSha256SSLContext(final Stream<String> sha256Pins) {
return sslContext(pinSha256TrustManager(sha256Pins));
}
/**
* Create pinned pubkey SSLContext
*
* @param sha256Pins HPKP RFC7469 style base64'd sha256 pins
* @return SSLContext that only accepts servers with these pubkeys
*/
public static SSLContext pinSha256SSLContext(final String... sha256Pins) {
return pinSha256SSLContext(Arrays.stream(sha256Pins));
}
/**
* Create pinned pubkey TrustManager
*
* @param sha256Pins HPKP RFC7469 style base64'd sha256 pins
* @return TrustManager that only accepts servers with these pubkeys
*/
public static TrustManager pinSha256TrustManager(final Stream<String> sha256Pins) {
return new PinnedPubKeyTrustManager("SHA-256", sha256Pins
.map(sha256Pin -> Base64.getDecoder().decode(sha256Pin))
.collect(Collectors.toList()));
}
private static final X509Certificate[] acceptedIssuers = new X509Certificate[0];
private final String algorithm;
private final Iterable<byte[]> pinnedPubKeys;
/**
* PinnedPubKeyTrustManager constructor
*
* @param algorithm Algorithm to create instance of MessageDigest to hash server public key with before comparing to pinnedPubKeys
* @param pinnedPubKeys Hashes of public keys to trust, if any match the leaf certificate the connection is trusted
*/
public PinnedPubKeyTrustManager(final String algorithm, final Iterable<byte[]> pinnedPubKeys) {
this.algorithm = algorithm;
this.pinnedPubKeys = pinnedPubKeys;
}
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType) {
throw new UnsupportedOperationException();
}
@Override
public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
if(chain.length < 1)
throw new CertificateException("no certificates provided");
// first cert is the leaf one????
final byte[] hashedServerPubKey = getMessageDigest().digest(chain[0].getPublicKey().getEncoded());
for(final byte[] pinnedPubKey : pinnedPubKeys) {
if (Arrays.equals(hashedServerPubKey, pinnedPubKey)) {
return; // success!
}
}
throw new CertificateException("pinnedpubkey authentication failed, server pin (" + algorithm + "): " +
Base64.getEncoder().encodeToString(hashedServerPubKey));
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return acceptedIssuers;
}
protected MessageDigest getMessageDigest() throws CertificateException {
try {
return MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new CertificateException(algorithm + " not supported", e);
}
}
public String getAlgorithm() {
return algorithm;
}
public Iterable<byte[]> getPinnedPubKeys() {
return pinnedPubKeys;
}
}

View File

@ -0,0 +1,44 @@
package com.moparisthebest.dns.tls;
import org.junit.Test;
import javax.net.ssl.SSLSocketFactory;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.*;
public class PinnedPubKeyTrustManagerTest {
@Test
public void pinSha256SSLContext() {
// todo: make these not rely on remote server, stand up local TLS server with hard-coded certs?
assertEquals("pinnedpubkey authentication failed, server pin (SHA-256): t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=",
testPin("moparisthebest.com", 443, "bla").getCause().getMessage());
assertEquals("pinnedpubkey authentication failed, server pin (SHA-256): t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=",
testPin("moparisthebest.com", 443, "eEHQC9au2QRAP1FnvcYEsmvXT7511EXQ2gw8ppBfseM=").getCause().getMessage());
assertEquals("read: 1024",
testPin("moparisthebest.com", 443, "t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=").getMessage());
}
private static Exception testPin(final String hostname, final int port, final String... sha256Pins) {
final SSLSocketFactory sf = PinnedPubKeyTrustManager.pinSha256SSLContext(sha256Pins).getSocketFactory();
try(Socket s = sf.createSocket()) {
s.connect(new InetSocketAddress(hostname, port), 5000);
try (InputStream is = s.getInputStream();
OutputStream os = s.getOutputStream()) {
os.write("GET /\r\n".getBytes(UTF_8));
os.flush();
final byte[] resp = new byte[1024];
final int read = is.read(resp);
return new Exception("read: " + read);
}
} catch(Exception e) {
return e;
}
}
}