Skip to content

Instantly share code, notes, and snippets.

@simon04
Last active November 28, 2022 09:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simon04/fc224c90c1033293b60051c2d2badbc2 to your computer and use it in GitHub Desktop.
Save simon04/fc224c90c1033293b60051c2d2badbc2 to your computer and use it in GitHub Desktop.
Java 17: SSLContext/X509TrustManager which only accepts a server certificate with the given SHA-256 fingerprint
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.HexFormat;
import java.util.Objects;
public interface HTTPS {
/**
* Returns an SSLContext which only accepts a server certificate with the given SHA-256 fingerprint
*
* @param sha256Fingerprint the server certificate SHA-256 fingerprint to accept
* @return an SSLContext which only accepts a server certificate with the given SHA-256 fingerprint
*/
static SSLContext acceptFingerprint(String sha256Fingerprint) {
Objects.requireNonNull(sha256Fingerprint);
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[]{new FingerprintTrustManager(sha256Fingerprint)}, new SecureRandom());
return context;
} catch (NoSuchAlgorithmException e) {
// Every implementation of the Java platform is required to support TLS
throw new IllegalStateException(e);
} catch (KeyManagementException e) {
throw new RuntimeException(e);
}
}
/**
* An X509TrustManager which only accepts a server certificate with the given SHA-256 fingerprint
*
* @param sha256Fingerprint the server certificate SHA-256 fingerprint to accept
*/
record FingerprintTrustManager(String sha256Fingerprint) implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
throw new CertificateException();
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
String fingerprint = fingerprint(chain[0]);
if (!sha256Fingerprint.equalsIgnoreCase(fingerprint)) {
throw new CertificateException();
}
}
private static String fingerprint(X509Certificate certificate) throws CertificateException {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(certificate.getEncoded());
return HexFormat.of().formatHex(md.digest());
} catch (NoSuchAlgorithmException e) {
// Every implementation of the Java platform is required to support SHA-256
throw new IllegalStateException(e);
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
}
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import javax.net.ssl.SSLException;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
class HTTPSTest {
HttpClient client = HttpClient.newBuilder()
// TLS certificate fingerprint of https://httpbin.org/
// TLS certificate expires on Monday, 20 November 2023 at 00:59:59
.sslContext(HTTPS.acceptFingerprint("55FDB02499017E9D1E0997A1E8CFAD735A1D83F6BBA4A2F38E8A157291C703D6"))
.build();
private void sendRequest(String str) throws IOException, InterruptedException {
client.send(HttpRequest.newBuilder().uri(URI.create(str)).build(), HttpResponse.BodyHandlers.discarding());
}
@Test
void testOk() throws Exception {
sendRequest("https://httpbin.org/get");
}
@Test
void testFail() {
Assertions.assertThrows(SSLException.class, () -> sendRequest("https://www.google.com/"));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment