Skip to content

Instantly share code, notes, and snippets.

@Kr328
Created May 1, 2025 12:44
Show Gist options
  • Select an option

  • Save Kr328/dd89c2fde02e8d00ae3fa02eb86760ba to your computer and use it in GitHub Desktop.

Select an option

Save Kr328/dd89c2fde02e8d00ae3fa02eb86760ba to your computer and use it in GitHub Desktop.

Revisions

  1. Kr328 created this gist May 1, 2025.
    154 changes: 154 additions & 0 deletions CertificateVerifier.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,154 @@
    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, "");
    }
    }
    11 changes: 11 additions & 0 deletions VerificationResult.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,11 @@
    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;
    }