Created
May 1, 2025 12:44
-
-
Save Kr328/dd89c2fde02e8d00ae3fa02eb86760ba to your computer and use it in GitHub Desktop.
Java version CertificateVerifier for rustls-platform-verifier
This file contains hidden or 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
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, ""); | |
} | |
} |
This file contains hidden or 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
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