Replace javapinning dependency with new PinnedPubKeyTrustManager
This commit is contained in:
parent
557a33fda8
commit
7492f5d23d
@ -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>
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user