Last active
November 28, 2022 09:01
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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