1+ /*
2+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
3+ *
4+ * This Source Code Form is subject to the terms of the Mozilla Public
5+ * License, v. 2.0. If a copy of the MPL was not distributed with this
6+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+ *
8+ * SPDX-License-Identifier: MPL-2.0
9+ */
10+
11+ package at.bitfire.cert4android
12+
13+ import androidx.annotation.VisibleForTesting
14+ import kotlinx.coroutines.flow.StateFlow
15+ import java.security.cert.X509Certificate
16+ import java.util.logging.Level
17+ import java.util.logging.Logger
18+
19+ class TestCertStore (): CertStore {
20+
21+ private val logger
22+ get() = Logger .getLogger(javaClass.name)
23+
24+ /* * custom TrustStore (simple map) */
25+ @VisibleForTesting
26+ internal val userKeyStore = mutableMapOf<String , X509Certificate >()
27+
28+ /* * in-memory store for untrusted certs */
29+ @VisibleForTesting
30+ internal var untrustedCerts = HashSet <X509Certificate >()
31+
32+ @Synchronized
33+ override fun clearUserDecisions () {
34+ logger.info(" Clearing user-(dis)trusted certificates" )
35+
36+ for (alias in userKeyStore.keys)
37+ userKeyStore.remove(alias)
38+
39+ // clear untrusted certs
40+ untrustedCerts.clear()
41+ }
42+
43+ /* *
44+ * Determines whether a certificate chain is trusted.
45+ */
46+ override fun isTrusted (chain : Array <X509Certificate >, authType : String , trustSystemCerts : Boolean , appInForeground : StateFlow <Boolean >? ): Boolean {
47+ if (chain.isEmpty())
48+ throw IllegalArgumentException (" Certificate chain must not be empty" )
49+ val cert = chain[0 ]
50+
51+ synchronized(this ) {
52+ // explicitly accepted by user?
53+ if (isTrustedByUser(cert))
54+ return true
55+
56+ // explicitly rejected by user?
57+ if (untrustedCerts.contains(cert))
58+ return false
59+
60+ // trusted by system? (if applicable)
61+ if (trustSystemCerts)
62+ return true // system trusts all certificates
63+ }
64+ logger.log(Level .INFO , " Certificate not known and running in non-interactive mode, rejecting" )
65+ return false
66+ }
67+
68+ /* *
69+ * Determines whether a certificate has been explicitly accepted by the user. In this case,
70+ * we can ignore an invalid host name for that certificate.
71+ */
72+ @Synchronized
73+ override fun isTrustedByUser (cert : X509Certificate ): Boolean =
74+ userKeyStore.containsValue(cert)
75+
76+ @Synchronized
77+ override fun setTrustedByUser (cert : X509Certificate ) {
78+ val alias = CertUtils .getTag(cert)
79+ logger.info(" Trusted by user: ${cert.subjectDN.name} ($alias )" )
80+ userKeyStore[alias] = cert
81+ untrustedCerts - = cert
82+ }
83+
84+ @Synchronized
85+ override fun setUntrustedByUser (cert : X509Certificate ) {
86+ logger.info(" Distrusted by user: ${cert.subjectDN.name} " )
87+
88+ // find certificate
89+ val alias = userKeyStore.entries.find { it.value == cert }?.key
90+ if (alias != null )
91+ // and delete, if applicable
92+ userKeyStore.remove(alias)
93+ untrustedCerts + = cert
94+ }
95+
96+ }
0 commit comments