diff options
author | Ricki Hirner <hirner@bitfire.at> | 2020-10-25 22:10:24 +0300 |
---|---|---|
committer | Ricki Hirner <hirner@bitfire.at> | 2020-10-25 22:10:24 +0300 |
commit | 37984d0d0ba234c7111297496d771f8915b02b2e (patch) | |
tree | 255c274972c51e1d81b60ec5f1e7d4669793d9e3 | |
parent | 047e48f9b5dddbc726d36b3677c01d223b6ad2dd (diff) |
Use MD5 fingerprint instead of CN as key to store trusted certificates (so that multiple certs with the same CN can be stored)
4 files changed, 47 insertions, 23 deletions
diff --git a/src/main/java/at/bitfire/cert4android/CertUtils.kt b/src/main/java/at/bitfire/cert4android/CertUtils.kt index 5947fa3..128d6c8 100644 --- a/src/main/java/at/bitfire/cert4android/CertUtils.kt +++ b/src/main/java/at/bitfire/cert4android/CertUtils.kt @@ -10,6 +10,7 @@ package at.bitfire.cert4android import java.security.GeneralSecurityException import java.security.KeyStore +import java.security.MessageDigest import java.security.cert.X509Certificate import java.util.logging.Level import javax.net.ssl.TrustManagerFactory @@ -17,6 +18,11 @@ import javax.net.ssl.X509TrustManager object CertUtils { + fun fingerprint(cert: X509Certificate, algorithm: String): String { + val md = MessageDigest.getInstance(algorithm) + return hexString(md.digest(cert.encoded)) + } + fun getTrustManager(keyStore: KeyStore?): X509TrustManager? { try { val tmf = TrustManagerFactory.getInstance("X509") @@ -37,4 +43,14 @@ object CertUtils { return str.toString() } + fun hexString(data: ByteArray): String { + val str = StringBuilder() + for ((idx, b) in data.withIndex()) { + if (idx != 0) + str.append(':') + str.append(String.format("%02x", b).toUpperCase()) + } + return str.toString() + } + } diff --git a/src/main/java/at/bitfire/cert4android/CustomCertService.kt b/src/main/java/at/bitfire/cert4android/CustomCertService.kt index 1591f53..b62b53d 100644 --- a/src/main/java/at/bitfire/cert4android/CustomCertService.kt +++ b/src/main/java/at/bitfire/cert4android/CustomCertService.kt @@ -14,6 +14,7 @@ import android.content.Context import android.content.Intent import android.util.Log import android.widget.Toast +import androidx.annotation.MainThread import androidx.core.app.NotificationCompat import org.conscrypt.Conscrypt import java.io.ByteArrayInputStream @@ -118,6 +119,7 @@ class CustomCertService: Service() { // started service + @MainThread override fun onStartCommand(intent: Intent?, flags: Int, id: Int): Int { Constants.log.fine("Received command: $intent") @@ -156,7 +158,11 @@ class CustomCertService: Service() { untrustedCerts.remove(cert) try { - trustedKeyStore.setCertificateEntry(cert.subjectDN.name, cert) + // This is the key which is used to store the certificate. If the CN is used, + // there can be only one certificate per CN (which is not always desired), + // so we use the MD5 fingerprint. + val certKey = CertUtils.fingerprint(cert, "MD5") + trustedKeyStore.setCertificateEntry(certKey, cert) saveKeyStore() } catch(e: KeyStoreException) { Constants.log.log(Level.SEVERE, "Couldn't add certificate into key store", e) diff --git a/src/main/java/at/bitfire/cert4android/TrustCertificateActivity.kt b/src/main/java/at/bitfire/cert4android/TrustCertificateActivity.kt index 8285c1d..e6653a7 100644 --- a/src/main/java/at/bitfire/cert4android/TrustCertificateActivity.kt +++ b/src/main/java/at/bitfire/cert4android/TrustCertificateActivity.kt @@ -18,14 +18,12 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import at.bitfire.cert4android.databinding.ActivityTrustCertificateBinding import java.io.ByteArrayInputStream -import java.security.MessageDigest import java.security.cert.CertificateFactory import java.security.cert.CertificateParsingException import java.security.cert.X509Certificate import java.security.spec.MGF1ParameterSpec.SHA1 import java.security.spec.MGF1ParameterSpec.SHA256 import java.text.DateFormat -import java.util.* import java.util.logging.Level import kotlin.concurrent.thread @@ -114,8 +112,8 @@ class TrustCertificateActivity: AppCompatActivity() { validFrom.postValue(formatter.format(cert.notBefore)) validTo.postValue(formatter.format(cert.notAfter)) - sha1.postValue(fingerprint(cert, SHA1.digestAlgorithm)) - sha256.postValue(fingerprint(cert, SHA256.digestAlgorithm)) + sha1.postValue("SHA1: " + CertUtils.fingerprint(cert, SHA1.digestAlgorithm)) + sha256.postValue("SHA256: " + CertUtils.fingerprint(cert, SHA256.digestAlgorithm)) } catch(e: CertificateParsingException) { Constants.log.log(Level.WARNING, "Couldn't parse certificate", e) @@ -124,19 +122,6 @@ class TrustCertificateActivity: AppCompatActivity() { } } - private fun fingerprint(cert: X509Certificate, algorithm: String) = - try { - val md = MessageDigest.getInstance(algorithm) - "$algorithm: ${hexString(md.digest(cert.encoded))}" - } catch(e: Exception) { - e.message ?: "Couldn't create message digest" - } - - private fun hexString(data: ByteArray): String { - val str = data.mapTo(LinkedList()) { String.format("%02x", it) } - return str.joinToString(":") - } - } }
\ No newline at end of file diff --git a/src/test/java/at/bitfire/cert4android/CertUtilsTest.kt b/src/test/java/at/bitfire/cert4android/CertUtilsTest.kt index d508d1f..cd33b18 100644 --- a/src/test/java/at/bitfire/cert4android/CertUtilsTest.kt +++ b/src/test/java/at/bitfire/cert4android/CertUtilsTest.kt @@ -13,20 +13,31 @@ import org.junit.Assert.assertNotNull import org.junit.Test import java.security.cert.CertificateFactory import java.security.cert.X509Certificate +import java.security.spec.MGF1ParameterSpec class CertUtilsTest { + val certFactory = CertificateFactory.getInstance("X.509") + + @Test - fun getTrustManagerSystem() { - assertNotNull(CertUtils.getTrustManager(null)) + fun testFingerprint() { + javaClass.classLoader!!.getResourceAsStream("davdroid-web.crt").use { stream -> + val cert = certFactory.generateCertificate(stream) as X509Certificate + assertEquals("8D:E5:74:B2:AA:3E:5C:EE:62:84:4A:3B:78:71:B6:C3", CertUtils.fingerprint(cert, "MD5")) + assertEquals("6C:83:A0:12:1A:F5:55:BF:C2:BC:23:DA:78:E4:5F:88:6E:01:0A:BC", CertUtils.fingerprint(cert, MGF1ParameterSpec.SHA1.digestAlgorithm)) + } } @Test - fun getTag() { - val factory = CertificateFactory.getInstance("X.509") + fun testGetTrustManagerSystem() { + assertNotNull(CertUtils.getTrustManager(null)) + } + @Test + fun testGetTag() { javaClass.classLoader!!.getResourceAsStream("davdroid-web.crt").use { stream -> - val cert = factory.generateCertificate(stream) as X509Certificate + val cert = certFactory.generateCertificate(stream) as X509Certificate assertNotNull(cert) assertEquals("276126a80783ee16b84811e1e96e977a" + @@ -48,4 +59,10 @@ class CertUtilsTest { } } + @Test + fun testHexString() { + assertEquals("", CertUtils.hexString(ByteArray(0))) + assertEquals("00:01:02:03:04:05:06:07:08:09:0A:0B:0C:0D:0E:0F:10", CertUtils.hexString(ByteArray(17) { i -> i.toByte() })) + } + } |