Skip to content

Instantly share code, notes, and snippets.

@Glamdring
Created January 13, 2019 18:36
Show Gist options
  • Save Glamdring/c45cc06cb2e8a2d20badc4473e2e3772 to your computer and use it in GitHub Desktop.
Save Glamdring/c45cc06cb2e8a2d20badc4473e2e3772 to your computer and use it in GitHub Desktop.
Java class that allows automated certificate transparency checks
package bg.bozho;
import java.io.FileInputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import org.apache.commons.codec.digest.DigestUtils;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.util.PublicKeyFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.certificatetransparency.ctlog.SignedTreeHead;
import org.certificatetransparency.ctlog.comm.HttpLogClient;
import org.conscrypt.Conscrypt;
import org.conscrypt.TrustManagerImpl;
import org.conscrypt.ct.CTLogInfo;
import org.conscrypt.ct.CTLogStore;
import org.conscrypt.ct.CTPolicy;
import org.conscrypt.ct.CTVerificationResult;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
public class CertificateTransparencyVerification {
public static void main(String[] args) throws Exception {
// Configure Conscrypt to enable and enforce certificate transparencey checks
Security.setProperty("conscrypt.ct.enable", "true");
Security.setProperty("conscrypt.ct.enforce.*", "true");
Security.addProvider(new BouncyCastleProvider());
LogStore logStore = new LogStore();
logStore.init("https://www.gstatic.com/ct/log_list/log_list.json");
Security.insertProviderAt(Conscrypt.newProvider(), 1);
SSLContext ctx = SSLContext.getInstance("TLS");
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(new FileInputStream(System.getenv("JAVA_HOME") + "/lib/security/cacerts"), "changeit".toCharArray());
ctx.init(null, new TrustManager[] {new TrustManagerImpl(
trustStore, null, null, null, logStore, null,
new StrictCTPolicy())}, new SecureRandom());
URL url = new URL("https://google.com");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(ctx.getSocketFactory());
conn.connect();
conn.getInputStream();
conn.disconnect();
}
public static class LogStore implements CTLogStore {
private Map<String, CTLogInfo> logs = new HashMap<>();
public void init(String url) throws Exception {
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(new URL(url).openStream());
ArrayNode logs = (ArrayNode) root.get("logs");
Field field = CTLogInfo.class.getDeclaredField("logId");
field.setAccessible(true);
for (JsonNode log : logs) {
String logUrl = log.get("url").asText();
String key = log.get("key").asText();
String description = log.get("description").asText();
try {
CTLogInfo logInfo = new CTLogInfo(getKey(key), description, logUrl);
// reflection needed, because the CTLogInfo caculates the logID incorrectly
field.set(logInfo, DigestUtils.sha256(Base64.getDecoder().decode(key)));
this.logs.put(Base64.getEncoder().encodeToString(logInfo.getID()), logInfo);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
@Override
public CTLogInfo getKnownLog(byte[] logId) {
// using base64 form for key, as byte arrays are not fit for that
return logs.get(Base64.getEncoder().encodeToString(logId));
}
private PublicKey getKey(String key) throws Exception {
AsymmetricKeyParameter keyParams = PublicKeyFactory.createKey(Base64.getDecoder().decode(key));
if (keyParams instanceof ECPublicKeyParameters) {
ECPublicKeyParameters ecParams = (ECPublicKeyParameters) keyParams;
KeyFactory eckf = KeyFactory.getInstance("EC", "BC");
ECParameterSpec spec = new ECParameterSpec(ecParams.getParameters().getCurve(),
ecParams.getParameters().getG(),
ecParams.getParameters().getN(),
ecParams.getParameters().getH());
return eckf.generatePublic(new ECPublicKeySpec(ecParams.getQ(), spec));
} else if (keyParams instanceof RSAKeyParameters) {
RSAKeyParameters rsaParams = (RSAKeyParameters) keyParams;
KeyFactory rsakf = KeyFactory.getInstance("RSA", "BC");
return rsakf.generatePublic(new RSAPublicKeySpec(rsaParams.getModulus(), rsaParams.getExponent()));
} else {
return null;
}
}
}
public static class StrictCTPolicy implements CTPolicy {
@Override
public boolean doesResultConformToPolicy(CTVerificationResult result, String hostname,
X509Certificate[] chain) {
return !result.getValidSCTs().isEmpty() && result.getInvalidSCTs().isEmpty();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment