Skip to content

Instantly share code, notes, and snippets.

@Kr328
Created May 1, 2025 12:44
Show Gist options
  • Save Kr328/dd89c2fde02e8d00ae3fa02eb86760ba to your computer and use it in GitHub Desktop.
Save Kr328/dd89c2fde02e8d00ae3fa02eb86760ba to your computer and use it in GitHub Desktop.
Java version CertificateVerifier for rustls-platform-verifier
package org.rustls.platformverifier;
import android.content.Context;
import android.net.http.X509TrustManagerExtensions;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertPathValidator;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.PKIXRevocationChecker;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Date;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public class CertificateVerifier {
private static final String TAG = CertificateVerifier.class.getName();
private static volatile CertificateFactory certificateFactory;
private static volatile KeyStore keystore;
private static volatile X509TrustManagerExtensions trustManager;
private static boolean verifyCertUsage(final X509Certificate certificate, final String[] allowedEkus) {
try {
return certificate.getExtendedKeyUsage()
.stream()
.anyMatch(e -> Arrays.asList(allowedEkus).contains(e));
} catch (final CertificateParsingException | NullPointerException e) {
return false;
}
}
/** @noinspection unused */
private static VerificationResult verifyCertificateChain(
final Context ignoredContext,
final String serverName,
final String authMethod,
final String[] allowedEkus,
final byte[] ocspResponse,
final long time,
final byte[][] certificateChainBytes
) {
final X509Certificate[] certificateChain = new X509Certificate[certificateChainBytes.length];
for (int i = 0; i < certificateChainBytes.length; i++) {
try {
if (certificateFactory == null) {
certificateFactory = CertificateFactory.getInstance("X.509");
}
final Certificate certificate = certificateFactory.generateCertificate(new ByteArrayInputStream(
certificateChainBytes[i]));
if (!(certificate instanceof X509Certificate)) {
return new VerificationResult(
VerificationResult.InvalidEncoding,
"Not a X.509 certificate"
);
}
certificateChain[i] = (X509Certificate) certificate;
} catch (final Exception e) {
return new VerificationResult(VerificationResult.InvalidEncoding, e.toString());
}
}
if (certificateChain.length == 0) {
return new VerificationResult(VerificationResult.UnknownCert,
"No certificate provided"
);
}
final X509Certificate endEntity = certificateChain[0];
try {
endEntity.checkValidity(new Date(time));
} catch (final CertificateExpiredException | CertificateNotYetValidException e) {
return new VerificationResult(VerificationResult.Expired, e.toString());
}
if (!verifyCertUsage(endEntity, allowedEkus)) {
return new VerificationResult(VerificationResult.InvalidExtension,
"Certificate usage not allowed"
);
}
if (keystore == null) {
synchronized (CertificateVerifier.class) {
if (keystore == null) {
try {
final KeyStore ks = KeyStore.getInstance("AndroidCAStore");
ks.load(null);
keystore = ks;
} catch (final KeyStoreException | CertificateException | IOException |
NoSuchAlgorithmException e) {
return new VerificationResult(VerificationResult.Unavailable, e.toString());
}
}
}
}
if (trustManager == null) {
synchronized (CertificateVerifier.class) {
if (trustManager == null) {
try {
final TrustManagerFactory factory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
factory.init(keystore);
for (final TrustManager trustManager : factory.getTrustManagers()) {
if (trustManager instanceof X509TrustManager) {
CertificateVerifier.trustManager = new X509TrustManagerExtensions((X509TrustManager) trustManager);
break;
}
}
if (trustManager == null) {
return new VerificationResult(VerificationResult.Unavailable,
"No X509TrustManager found"
);
}
} catch (final NoSuchAlgorithmException | KeyStoreException e) {
return new VerificationResult(VerificationResult.Unavailable, e.toString());
}
}
}
}
try {
trustManager.checkServerTrusted(certificateChain, authMethod, serverName);
} catch (final CertificateException e) {
return new VerificationResult(VerificationResult.UnknownCert, e.toString());
}
try {
final CertPathValidator validator = CertPathValidator.getInstance("PKIX");
final PKIXRevocationChecker revocationChecker = (PKIXRevocationChecker) validator.getRevocationChecker();
revocationChecker.getOcspResponder();
} catch (final NoSuchAlgorithmException e) {
return new VerificationResult(VerificationResult.Unavailable, e.toString());
}
return new VerificationResult(VerificationResult.OK, "");
}
}
package org.rustls.platformverifier;
public record VerificationResult(int code, String message) {
public static final int OK = 0;
public static final int Unavailable = 1;
public static final int Expired = 2;
public static final int UnknownCert = 3;
public static final int Revoked = 4;
public static final int InvalidEncoding = 5;
public static final int InvalidExtension = 6;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment