Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class OkhttpTest {

// set cert4android TrustManager and HostnameVerifier
val certManager = CustomCertManager(
context,
CustomCertStore.getInstance(context),
trustSystemCerts = true,
appInForeground = null
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,8 @@

package at.bitfire.cert4android

import android.net.SSLCertificateSocketFactory
import org.apache.http.conn.ssl.AllowAllHostnameVerifier
import java.net.URL
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.X509TrustManager

/**
* Provides certificates for testing.
Expand Down Expand Up @@ -71,39 +66,4 @@ object TestCertificates {

fun testCert() = certFactory.generateCertificate(RAW_TEST_CERT.byteInputStream()) as X509Certificate


/**
* Get the certificates of a site (bypassing all trusted checks).
*
* @param url the URL to get the certificates from
* @return the certificates of the site
*/
fun getSiteCertificates(url: URL): List<X509Certificate> {
val conn = url.openConnection() as HttpsURLConnection
try {
conn.hostnameVerifier = AllowAllHostnameVerifier()
conn.sslSocketFactory = object : SSLCertificateSocketFactory(1000) {
init {
setTrustManagers(arrayOf(object : X509TrustManager {
override fun checkClientTrusted(
chain: Array<out X509Certificate?>?,
authType: String?
) { /* OK */ }
override fun checkServerTrusted(
chain: Array<out X509Certificate?>?,
authType: String?
) { /* OK */ }
override fun getAcceptedIssuers(): Array<out X509Certificate?>? = emptyArray()
}))
}
}
conn.inputStream.read()
val certs = mutableListOf<X509Certificate>()
conn.serverCertificates.forEach { certs += it as X509Certificate }
return certs
} finally {
conn.disconnect()
}
}

}
44 changes: 44 additions & 0 deletions lib/src/main/java/at/bitfire/cert4android/CertStore.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* SPDX-License-Identifier: MPL-2.0
*/

package at.bitfire.cert4android

import kotlinx.coroutines.flow.StateFlow
import java.security.cert.X509Certificate

interface CertStore {

/**
* Removes user (dis-)trust decisions for all certificates.
*/
fun clearUserDecisions()

/**
* Determines whether a certificate chain is trusted.
*/
fun isTrusted(chain: Array<X509Certificate>, authType: String, trustSystemCerts: Boolean, appInForeground: StateFlow<Boolean>?): Boolean

/**
* Determines whether a certificate has been explicitly accepted by the user. In this case,
* we can ignore an invalid host name for that certificate.
*/
fun isTrustedByUser(cert: X509Certificate): Boolean

/**
* Sets this certificate as trusted.
*/
fun setTrustedByUser(cert: X509Certificate)

/**
* Sets this certificate as untrusted.
*/
fun setUntrustedByUser(cert: X509Certificate)

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
package at.bitfire.cert4android

import android.annotation.SuppressLint
import android.content.Context
import kotlinx.coroutines.flow.StateFlow
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
Expand All @@ -29,17 +28,14 @@ import javax.net.ssl.X509TrustManager
*/
@SuppressLint("CustomX509TrustManager")
class CustomCertManager @JvmOverloads constructor(
context: Context,
private val certStore: CertStore,
val trustSystemCerts: Boolean = true,
var appInForeground: StateFlow<Boolean>?
): X509TrustManager {

private val logger
get() = Logger.getLogger(javaClass.name)

val certStore = CustomCertStore.getInstance(context)


@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate>?, authType: String?) {
throw CertificateException("cert4android doesn't validate client certificates")
Expand Down
54 changes: 27 additions & 27 deletions lib/src/main/java/at/bitfire/cert4android/CustomCertStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,7 @@ import javax.net.ssl.X509TrustManager
class CustomCertStore internal constructor(
private val context: Context,
private val userTimeout: Long = 60000L
) {

companion object {

private const val KEYSTORE_DIR = "KeyStore"
private const val KEYSTORE_NAME = "KeyStore.bks"

@SuppressLint("StaticFieldLeak") // we only store the applicationContext, so this is safe
private var instance: CustomCertStore? = null

@Synchronized
fun getInstance(context: Context): CustomCertStore {
instance?.let {
return it
}

val newInstance = CustomCertStore(context.applicationContext)
instance = newInstance
return newInstance
}

}
): CertStore {

private val logger
get() = Logger.getLogger(javaClass.name)
Expand Down Expand Up @@ -82,7 +61,7 @@ class CustomCertStore internal constructor(
}

@Synchronized
fun clearUserDecisions() {
override fun clearUserDecisions() {
logger.info("Clearing user-(dis)trusted certificates")

for (alias in userKeyStore.aliases())
Expand All @@ -96,7 +75,7 @@ class CustomCertStore internal constructor(
/**
* Determines whether a certificate chain is trusted.
*/
fun isTrusted(chain: Array<X509Certificate>, authType: String, trustSystemCerts: Boolean, appInForeground: StateFlow<Boolean>?): Boolean {
override fun isTrusted(chain: Array<X509Certificate>, authType: String, trustSystemCerts: Boolean, appInForeground: StateFlow<Boolean>?): Boolean {
if (chain.isEmpty())
throw IllegalArgumentException("Certificate chain must not be empty")
val cert = chain[0]
Expand Down Expand Up @@ -146,11 +125,11 @@ class CustomCertStore internal constructor(
* we can ignore an invalid host name for that certificate.
*/
@Synchronized
fun isTrustedByUser(cert: X509Certificate): Boolean =
override fun isTrustedByUser(cert: X509Certificate): Boolean =
userKeyStore.getCertificateAlias(cert) != null

@Synchronized
fun setTrustedByUser(cert: X509Certificate) {
override fun setTrustedByUser(cert: X509Certificate) {
val alias = CertUtils.getTag(cert)
logger.info("Trusted by user: ${cert.subjectDN.name} ($alias)")

Expand All @@ -161,7 +140,7 @@ class CustomCertStore internal constructor(
}

@Synchronized
fun setUntrustedByUser(cert: X509Certificate) {
override fun setUntrustedByUser(cert: X509Certificate) {
logger.info("Distrusted by user: ${cert.subjectDN.name}")

// find certificate
Expand Down Expand Up @@ -202,4 +181,25 @@ class CustomCertStore internal constructor(
}
}

companion object {

private const val KEYSTORE_DIR = "KeyStore"
private const val KEYSTORE_NAME = "KeyStore.bks"

@SuppressLint("StaticFieldLeak") // we only store the applicationContext, so this is safe
private var instance: CustomCertStore? = null

@Synchronized
fun getInstance(context: Context): CustomCertStore {
instance?.let {
return it
}

val newInstance = CustomCertStore(context.applicationContext)
instance = newInstance
return newInstance
}

}

}
Loading