Replace rustls-platform-verifier-android.aar with single class (#6610)
* Replace the `rustls-platform-verifier-android.aar` with the actual source code * Exclude the platform-verifier code from linters * Add manual update instructions * Exclude from Kover too
This commit is contained in:
parent
6a4fed2baf
commit
a341a1a59e
11 changed files with 530 additions and 37 deletions
|
|
@ -52,6 +52,9 @@ allprojects {
|
|||
|
||||
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
|
||||
exclude("io/element/android/tests/konsist/failures/**")
|
||||
|
||||
// This file comes from another project and we want to keep it as close to the original as possible
|
||||
exclude("org/rustls/platformverifier/**")
|
||||
}
|
||||
|
||||
// KtLint
|
||||
|
|
@ -79,6 +82,9 @@ allprojects {
|
|||
|
||||
// This file comes from another project and we want to keep it as close to the original as possible
|
||||
exclude("**/SafeChildrenTransitionScope.kt")
|
||||
|
||||
// This file comes from another project and we want to keep it as close to the original as possible
|
||||
exclude("org/rustls/platformverifier/**")
|
||||
}
|
||||
}
|
||||
// Dependency check
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ dependencies {
|
|||
} else {
|
||||
debugImplementation(libs.matrix.sdk)
|
||||
}
|
||||
implementation(files("libs/rustls-platform-verifier-android.aar"))
|
||||
implementation(projects.libraries.rustlsTls)
|
||||
|
||||
implementation(projects.appconfig)
|
||||
implementation(projects.libraries.androidutils)
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
Updated rustls-platform-verifier-android.aar using `rustls-platform-verifier-0.1.1.aar`
|
||||
9
libraries/rustls-tls/README.md
Normal file
9
libraries/rustls-tls/README.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
This module is a wrapper for the Android code distributed in the rustls-platform-verifier-android crate.
|
||||
|
||||
To avoid the distribution mess that this library has (download a Rust crate, then search for it using Gradle and use it as local maven repo),
|
||||
we previously just manually updated the AAR file instead using a script. This won't work for F-Droid because the AAR library is a black box with
|
||||
no sources attached to it, so we can't use it like that.
|
||||
|
||||
Instead, for the time being, we're adding the single `CertificateVerifier.kt` class this AAR had in it as part of our sources.
|
||||
|
||||
When this file is updated, the [UPDATED.md](./UPDATED.md) file should be updated too with the commit SHA of the new version.
|
||||
7
libraries/rustls-tls/UPDATED.md
Normal file
7
libraries/rustls-tls/UPDATED.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Below is the commit SHA in [rustls-platform-verifier](https://github.com/rustls/rustls-platform-verifier) library used to update the code in this module:
|
||||
|
||||
```
|
||||
996b1c903491641b17b3c9afb65d1352f6fc6b76
|
||||
```
|
||||
|
||||
Please update it after making manual changes.
|
||||
24
libraries/rustls-tls/build.gradle.kts
Normal file
24
libraries/rustls-tls/build.gradle.kts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import extension.buildConfigFieldBoolean
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "org.rustls.platformverifier"
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
buildConfigFieldBoolean("TEST", false)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,480 @@
|
|||
@file:SuppressLint("LogNotTimber", "ObsoleteSdkInt")
|
||||
@file:Suppress("KotlinConstantConditions")
|
||||
|
||||
// IMPORTANT: this file comes from rustls-platform-verifier and should not be modified locally.
|
||||
|
||||
/*
|
||||
* Copyright (c) 2022 1Password
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package org.rustls.platformverifier
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.net.http.X509TrustManagerExtensions
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.security.KeyStore
|
||||
import java.security.KeyStoreException
|
||||
import java.security.MessageDigest
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPathValidator
|
||||
import java.security.cert.CertPathValidatorException
|
||||
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.PKIXBuilderParameters
|
||||
import java.security.cert.PKIXRevocationChecker
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.Date
|
||||
import java.util.EnumSet
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
// If this is updated, update the Rust definition too.
|
||||
// Marked private as this is not meant to be used in Android code.
|
||||
private enum class StatusCode(val value: Int) {
|
||||
Ok(0),
|
||||
Unavailable(1),
|
||||
Expired(2),
|
||||
UnknownCert(3),
|
||||
Revoked(4),
|
||||
InvalidEncoding(5),
|
||||
InvalidExtension(6),
|
||||
}
|
||||
|
||||
// Marked private as this is not meant to be used in Android code.
|
||||
private class VerificationResult(
|
||||
status: StatusCode,
|
||||
@Suppress("unused") val message: String? = null
|
||||
) {
|
||||
@Suppress("unused")
|
||||
private val code: Int = status.value
|
||||
}
|
||||
|
||||
// NOTE: All TrustManager and certificate validation methods are not thread safe. These
|
||||
// are all guarded by Kotlin's `Synchronized` accessors to prevent undefined behavior.
|
||||
|
||||
// Only JNI and test code calls this, so unused code warnings are suppressed.
|
||||
// Internal for test code - no other Kotlin code should use this object directly.
|
||||
@Suppress("unused")
|
||||
// We want to show a difference between Kotlin-side logs and those in Rust code
|
||||
@SuppressLint("LongLogTag")
|
||||
internal object CertificateVerifier {
|
||||
private const val TAG = "rustls-platform-verifier-android"
|
||||
|
||||
private fun createTrustManager(keystore: KeyStore?): X509TrustManagerExtensions? {
|
||||
// This can never throw since the default algorithm is used.
|
||||
val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
|
||||
factory.init(keystore)
|
||||
|
||||
val availableTrustManagers = try {
|
||||
factory.trustManagers
|
||||
} catch (e: RuntimeException) {
|
||||
Log.w(TAG, "exception thrown creating a TrustManager: $e")
|
||||
return null
|
||||
}
|
||||
|
||||
for (manager in availableTrustManagers) {
|
||||
if (manager is X509TrustManager) {
|
||||
// Kotlin ensures this can't throw at runtime since it knows that
|
||||
// it must be the correct type by now.
|
||||
return X509TrustManagerExtensions(manager)
|
||||
}
|
||||
}
|
||||
|
||||
Log.e(TAG, "failed to find a usable trust manager")
|
||||
return null
|
||||
}
|
||||
|
||||
private fun makeLazyTrustManager(keystore: KeyStore?): Lazy<X509TrustManagerExtensions?> {
|
||||
// Ensure the keystore is loaded. Since all of the trust managers are initialized in a
|
||||
// `Lazy`, this will only run once.
|
||||
keystore?.load(null)
|
||||
|
||||
return lazy { createTrustManager(keystore) }
|
||||
}
|
||||
|
||||
// -- Test only --
|
||||
// Ideally, all of this will be optimized out at compile time due to not being accessed
|
||||
// in release builds.
|
||||
|
||||
@get:Synchronized
|
||||
private val mockKeystore: KeyStore = KeyStore.getInstance(KeyStore.getDefaultType())
|
||||
|
||||
@get:Synchronized
|
||||
private var mockTrustManager: Lazy<X509TrustManagerExtensions?> =
|
||||
makeLazyTrustManager(mockKeystore)
|
||||
|
||||
@JvmStatic
|
||||
private fun addMockRoot(root: ByteArray) {
|
||||
if (!BuildConfig.TEST) {
|
||||
throw Exception("attempted to add a mock root outside a test!")
|
||||
}
|
||||
|
||||
val alias = "root_${mockKeystore.size()}"
|
||||
// Throwing here is fine since test roots should always be well-formed
|
||||
val cert = certFactory.generateCertificate(ByteArrayInputStream(root))
|
||||
mockKeystore.setCertificateEntry(alias, cert)
|
||||
|
||||
reloadMockData()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun clearMockRoots() {
|
||||
// Reload to get a completely fresh internal state
|
||||
mockKeystore.load(null)
|
||||
reloadMockData()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun reloadMockData() {
|
||||
if (mockTrustManager.isInitialized()) {
|
||||
mockTrustManager = makeLazyTrustManager(mockKeystore)
|
||||
}
|
||||
}
|
||||
|
||||
// Get a list of the system's root CAs.
|
||||
// Function is public for testing only.
|
||||
@JvmStatic
|
||||
fun getSystemRootCAs(): List<X509Certificate> {
|
||||
val rootCAs = mutableListOf<X509Certificate>()
|
||||
|
||||
val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
factory.init(systemKeystore)
|
||||
|
||||
val availableTrustManagers = try {
|
||||
factory.trustManagers
|
||||
} catch (e: RuntimeException) {
|
||||
Log.w(TAG, "exception thrown creating a TrustManager: $e")
|
||||
return rootCAs
|
||||
}
|
||||
|
||||
availableTrustManagers.forEach { trustManager ->
|
||||
if (trustManager is X509TrustManager) {
|
||||
rootCAs.addAll(trustManager.acceptedIssuers)
|
||||
}
|
||||
}
|
||||
|
||||
return rootCAs
|
||||
}
|
||||
|
||||
// -- End testing requirements --
|
||||
|
||||
private val certFactory: CertificateFactory = CertificateFactory.getInstance("X.509")
|
||||
|
||||
private var systemTrustAnchorCache = hashSetOf<Pair<X500Principal, PublicKey>>()
|
||||
|
||||
@get:Synchronized
|
||||
private var systemCertificateDirectory: File? = System.getenv("ANDROID_ROOT")?.let { rootPath ->
|
||||
File("$rootPath/etc/security/cacerts")
|
||||
}
|
||||
|
||||
@get:Synchronized
|
||||
private val systemKeystore: KeyStore? = try {
|
||||
KeyStore.getInstance("AndroidCAStore")
|
||||
} catch (_: KeyStoreException) {
|
||||
null
|
||||
}
|
||||
|
||||
@get:Synchronized
|
||||
private val systemTrustManager: Lazy<X509TrustManagerExtensions?> =
|
||||
makeLazyTrustManager(systemKeystore)
|
||||
|
||||
@JvmStatic
|
||||
private fun verifyCertificateChain(
|
||||
@Suppress("UNUSED_PARAMETER") context: Context,
|
||||
serverName: String,
|
||||
authMethod: String,
|
||||
allowedEkus: Array<String>,
|
||||
ocspResponse: ByteArray?,
|
||||
time: Long,
|
||||
certChain: Array<ByteArray>
|
||||
): VerificationResult {
|
||||
// Convert the array of (supposedly) DER bytes into certificates.
|
||||
val certificateChain = mutableListOf<X509Certificate>()
|
||||
certChain.forEach { certBytes ->
|
||||
val certificate = try {
|
||||
certFactory.generateCertificate(ByteArrayInputStream(certBytes))
|
||||
} catch (e: CertificateException) {
|
||||
return VerificationResult(StatusCode.InvalidEncoding)
|
||||
}
|
||||
certificateChain.add(certificate as X509Certificate)
|
||||
}
|
||||
|
||||
// Will never throw `ArrayIndexOutOfBoundsException` because `rustls`'s `ServerCertVerifier` trait
|
||||
// has a mandatory `end_entity` parameter in `verify_server_cert`.
|
||||
val endEntity = certificateChain[0]
|
||||
|
||||
// Check that the certificate is valid at the point of time provided by `rustls`.
|
||||
try {
|
||||
endEntity.checkValidity(Date(time))
|
||||
} catch (e: CertificateExpiredException) {
|
||||
return VerificationResult(StatusCode.Expired)
|
||||
} catch (e: CertificateNotYetValidException) {
|
||||
return VerificationResult(StatusCode.Expired)
|
||||
}
|
||||
|
||||
// Check that this certificate can be used in a TLS server.
|
||||
if (!verifyCertUsage(endEntity, allowedEkus)) {
|
||||
return VerificationResult(StatusCode.InvalidExtension)
|
||||
}
|
||||
|
||||
// Select the trust manager to use.
|
||||
//
|
||||
// We select them as follows:
|
||||
// - If built for release, only use the system trust manager. This should let all test-related
|
||||
// code be optimized out.
|
||||
// - If built for tests:
|
||||
// - If the mock CA store has any values, use the mock trust manager.
|
||||
// - Otherwise, use the system trust manager.
|
||||
val (trustManager, keystore) = if (!BuildConfig.TEST) {
|
||||
val trustManager =
|
||||
systemTrustManager.value ?: return VerificationResult(StatusCode.Unavailable)
|
||||
Pair(trustManager, systemKeystore)
|
||||
} else {
|
||||
if (mockKeystore.size() != 0) {
|
||||
val trustManager = mockTrustManager.value!!
|
||||
Pair(trustManager, mockKeystore)
|
||||
} else {
|
||||
val trustManager =
|
||||
systemTrustManager.value ?: return VerificationResult(StatusCode.Unavailable)
|
||||
Pair(trustManager, systemKeystore)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the certificate chain is valid and correct, and nothing more.
|
||||
//
|
||||
// NOTE: This does not validate `serverName` is valid for the end-entity certificate.
|
||||
// That is handled in Rust as Android/Java do not currently provide a RFC 6125 compliant
|
||||
// hostname verifier. Additionally, even the RFC 2818 verifier is not available until API 24.
|
||||
//
|
||||
// `serverName` is only used for pinning/CT requirements.
|
||||
//
|
||||
// Returns the "the properly ordered chain used for verification as a list of X509Certificates.",
|
||||
// meaning a list from end-entity certificate to trust-anchor.
|
||||
val validChain = try {
|
||||
trustManager.checkServerTrusted(certificateChain.toTypedArray(), authMethod, serverName)
|
||||
} catch (e: CertificateException) {
|
||||
// In test configurations we may see `checkServerTrusted` fail once vendored test
|
||||
// certificates pass their expiry date. We try to avoid that by using a fixed
|
||||
// verification time when calling `endEntity.checkValidity` above, however we can't
|
||||
// fix the time for the `checkServerTrusted` call.
|
||||
//
|
||||
// To make diagnosing CI test failures easier we try to find the root cause of
|
||||
// checkServerTrusted failing, returning a different `StatusCode` as appropriate.
|
||||
if (BuildConfig.TEST) {
|
||||
var rootCause: Throwable? = e
|
||||
while (rootCause?.cause != null && rootCause.cause != rootCause) {
|
||||
rootCause = rootCause.cause
|
||||
}
|
||||
return when (rootCause) {
|
||||
is CertificateExpiredException, is CertificateNotYetValidException -> VerificationResult(
|
||||
StatusCode.Expired,
|
||||
rootCause.toString()
|
||||
)
|
||||
|
||||
else -> VerificationResult(StatusCode.UnknownCert, rootCause.toString())
|
||||
}
|
||||
}
|
||||
// In non-test configurations we should have caught expiry errors earlier and
|
||||
// can simply return an unknown cert error without digging through the exception
|
||||
// cause chain.
|
||||
return VerificationResult(StatusCode.UnknownCert, e.toString())
|
||||
}
|
||||
|
||||
// TEST ONLY: Mock test suite cannot attempt to check revocation status if no OSCP data has been stapled,
|
||||
// because Android requires certificates to an specify OCSP responder for network fetch in this case.
|
||||
// If in testing w/o OCSP stapled, short-circuit here - only prior checks apply.
|
||||
if (BuildConfig.TEST && (mockKeystore.size() != 0) && (ocspResponse == null)) {
|
||||
return VerificationResult(StatusCode.Ok)
|
||||
}
|
||||
|
||||
// Try to check the revocation status of the cert, if it is supported.
|
||||
//
|
||||
// This is supported at >= API 24, but we're supporting 22 (Android 5) for the best
|
||||
// compatibility.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
// Note:
|
||||
//
|
||||
// 1. Android does not provide any way only to attempt to validate revocation from cached
|
||||
// data like the other platforms do. This means it will always use the network for
|
||||
// certificates which had no stapled response.
|
||||
//
|
||||
// 2: Likely because of 1, Android requires all issued certificates to have some form of
|
||||
// revocation included in their authority information. This doesn't work universally as
|
||||
// issuing certificates in use may omit authority access information (for example the
|
||||
// Let's Encrypt R3 Intermediate Certificate).
|
||||
//
|
||||
// Given these constraints, the best option is to only check revocation information
|
||||
// at the end-entity depth. We will prefer OCSP (to use stapled information if possible).
|
||||
// If there is no stapled OCSP response, Android may use the network to attempt to fetch
|
||||
// one. If OCSP checking fails, it may fall back to fetching CRLs. We allow "soft"
|
||||
// failures, for example transient network errors.
|
||||
//
|
||||
// In the case of a non-public root, such as an internal CA or self-signed certificate,
|
||||
// we opt to skip revocation checks entirely. The only exception is if the server
|
||||
// provided stapled OCSP data, which is an explicit signal and won't introduce non-ideal
|
||||
// platform behavior when attempting validation.
|
||||
//
|
||||
// This is because these are cases where a user or administrator has explicitly opted to
|
||||
// trust a certificate they (at least believe) have control over. These certificates rarely
|
||||
// contain revocation information as well, so these cases don't lose much.
|
||||
// See https://github.com/rustls/rustls-platform-verifier/issues/69 as well.
|
||||
if (ocspResponse == null && !isKnownRoot(validChain.last())) {
|
||||
// Chain validation must have succeeded by this point.
|
||||
return VerificationResult(StatusCode.Ok)
|
||||
}
|
||||
|
||||
val parameters = PKIXBuilderParameters(keystore, null)
|
||||
|
||||
val validator = CertPathValidator.getInstance("PKIX")
|
||||
val revocationChecker = validator.revocationChecker as PKIXRevocationChecker
|
||||
|
||||
revocationChecker.options = EnumSet.of(
|
||||
PKIXRevocationChecker.Option.SOFT_FAIL,
|
||||
PKIXRevocationChecker.Option.ONLY_END_ENTITY
|
||||
)
|
||||
|
||||
// Use the OCSP data `rustls` provided, if present.
|
||||
// Its expected that the server only sends revocation data for its own leaf certificate.
|
||||
//
|
||||
// If this field is set, then Android will use it and skip any networking to
|
||||
// attempt a fetch for that certificate. Otherwise, it will attempt to fetch it from the network.
|
||||
// Ref: https://cs.android.com/android/platform/superproject/+/master:libcore/ojluni/src/main/java/sun/security/provider/certpath/RevocationChecker.java;l=694
|
||||
ocspResponse?.let { providedResponse ->
|
||||
revocationChecker.ocspResponses = mapOf(endEntity to providedResponse)
|
||||
}
|
||||
|
||||
// Use the custom revocation definition.
|
||||
// "Note that when a `PKIXRevocationChecker` is added to `PKIXParameters`, it clones the `PKIXRevocationChecker`;
|
||||
// thus any subsequent modifications to the `PKIXRevocationChecker` have no effect."
|
||||
// - https://developer.android.com/reference/java/security/cert/PKIXRevocationChecker
|
||||
parameters.certPathCheckers = listOf(revocationChecker)
|
||||
// "When supplying a revocation checker in this manner, it will be used to check revocation
|
||||
// irrespective of the setting of the `RevocationEnabled` flag."
|
||||
// - https://developer.android.com/reference/java/security/cert/PKIXRevocationChecker
|
||||
parameters.isRevocationEnabled = false
|
||||
|
||||
// Validate the revocation status of the end entity certificate.
|
||||
try {
|
||||
validator.validate(certFactory.generateCertPath(validChain), parameters)
|
||||
} catch (e: CertPathValidatorException) {
|
||||
// LetsEncrypt no longer include OCSP information (as OCSP is being deprecated) which Android is not
|
||||
// happy with since it *only* tries OCSP by default. We aren't 100% decided on how to fix this yet for real
|
||||
// (see https://github.com/rustls/rustls-platform-verifier/pull/179) so for now we implement an out for
|
||||
// tests to allow regular maintenance to proceed.
|
||||
if (BuildConfig.TEST && e.reason == CertPathValidatorException.BasicReason.UNSPECIFIED) {
|
||||
return VerificationResult(StatusCode.Ok)
|
||||
}
|
||||
|
||||
return VerificationResult(StatusCode.Revoked, e.toString())
|
||||
}
|
||||
} else {
|
||||
// This is allowed to be skipped since revocation checking is best-effort.
|
||||
Log.w(TAG, "did not attempt to validate OCSP due to Android version")
|
||||
}
|
||||
|
||||
return VerificationResult(StatusCode.Ok)
|
||||
}
|
||||
|
||||
private fun verifyCertUsage(certificate: X509Certificate, allowedEkus: Array<String>): Boolean {
|
||||
val ekus = try {
|
||||
certificate.extendedKeyUsage
|
||||
}
|
||||
// This should be unreachable, but could happen.
|
||||
catch (_: CertificateParsingException) {
|
||||
return false
|
||||
} catch (_: NullPointerException) {
|
||||
// According to Chromium's implementation, this can crash when the EKU data is malformed.
|
||||
Log.w(TAG, "exception handling certificate EKU")
|
||||
return false
|
||||
} ?: return true // If the list is empty, we have nothing to do.
|
||||
|
||||
return ekus.any { allowedEkus.contains(it) }
|
||||
}
|
||||
|
||||
// Android hashes a principal using the first four bytes of its MD5 digest, encoded in
|
||||
// lowercase hex and reversed.
|
||||
//
|
||||
// Ref: https://source.chromium.org/chromium/chromium/src/+/main:net/android/java/src/org/chromium/net/X509Util.java;l=339
|
||||
private fun hashPrincipal(principal: X500Principal): String {
|
||||
val hexDigits = "0123456789abcdef".toCharArray()
|
||||
val digest = MessageDigest.getInstance("MD5").digest(principal.encoded)
|
||||
val hexChars = CharArray(8)
|
||||
|
||||
for (i in 0..3) {
|
||||
// Kotlin doesn't support bitwise operators for bytes, only Int and Long.
|
||||
val digestByte = digest[3 - i].toInt()
|
||||
hexChars[2 * i] = hexDigits[(digestByte shr 4) and 0xf]
|
||||
hexChars[2 * i + 1] = hexDigits[digestByte and 0xf]
|
||||
}
|
||||
|
||||
return String(hexChars)
|
||||
}
|
||||
|
||||
// Check if CA root is known or not.
|
||||
// Known means installed in root CA store, either a preset public CA or a custom one installed by an enterprise/user.
|
||||
//
|
||||
// Ref: https://source.chromium.org/chromium/chromium/src/+/main:net/android/java/src/org/chromium/net/X509Util.java;l=351
|
||||
fun isKnownRoot(root: X509Certificate): Boolean {
|
||||
// System keystore and cert directory must be non-null to perform checking
|
||||
systemKeystore?.let { loadedSystemKeystore ->
|
||||
systemCertificateDirectory?.let { loadedSystemCertificateDirectory ->
|
||||
|
||||
// Check the in-memory cache first
|
||||
val key = Pair(root.subjectX500Principal, root.publicKey)
|
||||
if (systemTrustAnchorCache.contains(key)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// System trust anchors are stored under a hash of the principal.
|
||||
// In case of collisions, append number.
|
||||
val hash = hashPrincipal(root.subjectX500Principal)
|
||||
var i = 0
|
||||
while (true) {
|
||||
val alias = "$hash.$i"
|
||||
|
||||
if (!File(loadedSystemCertificateDirectory, alias).exists()) {
|
||||
break
|
||||
}
|
||||
|
||||
val anchor = loadedSystemKeystore.getCertificate("system:$alias")
|
||||
|
||||
// It's possible for `anchor` to be `null` if the user deleted a trust anchor.
|
||||
// Continue iterating as there may be further collisions after the deleted anchor.
|
||||
if (anchor == null) {
|
||||
continue
|
||||
// This should never happen
|
||||
} else if (anchor !is X509Certificate) {
|
||||
// SAFETY: This logs a unique identifier (hash value) only in cases where a file within the
|
||||
// system's root trust store is not a valid X509 certificate (extremely unlikely error).
|
||||
// The hash doesn't tell us any sensitive information about the invalid cert or reveal any of
|
||||
// its contents - it just lets us ID the bad file if a user is having TLS failure issues.
|
||||
Log.e(TAG, "anchor is not a certificate, alias: $alias")
|
||||
continue
|
||||
// If subject and public key match, it's a system root.
|
||||
} else {
|
||||
if ((root.subjectX500Principal == anchor.subjectX500Principal) && (root.publicKey == anchor.publicKey)) {
|
||||
systemTrustAnchorCache.add(key)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not found in cache or store: non-public
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ val excludedKoverSubProjects = listOf(
|
|||
":libraries:core",
|
||||
":libraries:coroutines",
|
||||
":libraries:di",
|
||||
":libraries:rustls-tls",
|
||||
":tests:detekt-rules",
|
||||
":tests:konsist",
|
||||
":tests:testutils",
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class KonsistLicenseTest {
|
|||
.files
|
||||
.filter {
|
||||
it.moduleName.startsWith("enterprise").not() &&
|
||||
it.moduleName != "libraries/rustls-tls" &&
|
||||
it.nameWithExtension != "locales.kt" &&
|
||||
it.name.startsWith("Template ").not()
|
||||
}
|
||||
|
|
@ -78,6 +79,7 @@ class KonsistLicenseTest {
|
|||
.scopeFromProject()
|
||||
.files
|
||||
.filter {
|
||||
it.moduleName.endsWith("rustls-tls").not() &&
|
||||
it.nameWithExtension != "locales.kt" &&
|
||||
it.nameWithExtension != "KonsistLicenseTest.kt" &&
|
||||
it.name.startsWith("Template ").not()
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2026 Element Creations Ltd.
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
# Please see LICENSE files in the repository root for full details.
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
VERSION=${1:-}
|
||||
if [ -n "$VERSION" ]; then
|
||||
PACKAGE=rustls-platform-verifier-android==$VERSION
|
||||
else
|
||||
PACKAGE=rustls-platform-verifier-android
|
||||
fi
|
||||
|
||||
cargo install cargo-download
|
||||
mkdir -p tmp/rustls-platform-verifier-android
|
||||
cargo download $PACKAGE > tmp/rustls-platform-verifier-android/rustls-platform-verifier-android.gz
|
||||
ROOT=$(git rev-parse --show-toplevel)
|
||||
|
||||
cd tmp/rustls-platform-verifier-android
|
||||
|
||||
echo "Extracting rustls-platform-verifier-android.aar from \`rustls-platform-verifier-android.gz\`"
|
||||
|
||||
tar -xzvf rustls-platform-verifier-android.gz &> /dev/null
|
||||
DIR=$(find . -type d -name "rustls-platform-verifier-android-*")
|
||||
AAR=$(find $DIR -type f -name "*.aar")
|
||||
cp $AAR $ROOT/libraries/matrix/impl/libs/rustls-platform-verifier-android.aar
|
||||
cd $ROOT
|
||||
rm -r tmp/rustls-platform-verifier-android
|
||||
|
||||
echo "Updated rustls-platform-verifier-android.aar using \`$(basename $AAR)\`" > libraries/matrix/impl/libs/rustls-platform-verifier-android.aar.version
|
||||
cat libraries/matrix/impl/libs/rustls-platform-verifier-android.aar.version
|
||||
Loading…
Add table
Add a link
Reference in a new issue