diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 56236e8f1b87d169815d348f24eade1579ff5a1b..26cab5b72114748a0b0b1dc63b9ff73ec4ecfbdc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,4 +15,4 @@ cache: build: stage: build script: - - ./gradlew build \ No newline at end of file + - ./gradlew build diff --git a/README.md b/README.md index 316f7aaa36601dc07b670a36468751a200a225a1..7833b210977b633b27d4a296cd5d16cca8a520ec 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,16 @@ # cert4android -cert4android is an Android library for managing custom certificates which has -been developed for [DAVdroid](https://davdroid.bitfire.at). Feel free to use +cert4android is a library for Android to manage custom certificates which has +been developed for [DAVx⁵](https://www.davx5.com). Feel free to use it in your own open-source app. +_This software is not affiliated to, nor has it been authorized, sponsored or otherwise approved +by Google LLC. Android is a trademark of Google LLC._ + Generated KDoc: https://bitfireat.gitlab.io/cert4android/dokka/cert4android/ -Discussion: https://forums.bitfire.at/category/7/transport-level-security +Discussion: https://forums.bitfire.at/category/18/libraries # Features @@ -31,11 +34,40 @@ Discussion: https://forums.bitfire.at/category/7/transport-level-security 1. Close the instance when it's not required anymore (will disconnect from the `CustomCertService`, thus allowing it to be destroyed). +Example of initialzing an okhttp client: + + val keyManager = ... + CustomCertManager(...).use { trustManager -> + val sslContext = SSLContext.getInstance("TLS") + sslContext.init( + if (keyManager != null) arrayOf(keyManager) else null, + arrayOf(trustManager), + null + ) + val builder = OkHttpClient.Builder() + builder.sslSocketFactory(sslContext.socketFactory, trustManager) + .hostnameVerifier(hostnameVerifier) + val httpClient = builder.build() + // use httpClient + } + + You can overwrite resources when you want, just have a look at the `res/strings` directory. Especially `certificate_notification_connection_security` and `trust_certificate_unknown_certificate_found` should contain your app name. +## Contact + +``` +bitfire web engineering – Stockmann, Hirner GesnbR +Florastraße 27 +2540 Bad Vöslau, AUSTRIA +``` + +Email: [play@bitfire.at](mailto:play@bitfire.at) (do not use this) + + # License Copyright (C) bitfire web engineering (Ricki Hirner, Bernhard Stockmann). diff --git a/build.gradle b/build.gradle index 59642d91b9ce71ce4f050c733a97e242c101a7f3..6dc87f67c59d73571c96e845682cfb3e2c218a63 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,10 @@ buildscript { - ext.kotlin_version = '1.3.10' - ext.dokka_version = '0.9.17' + ext.versions = [ + kotlin: '1.3.61', + dokka: '0.10.0', + conscrypt: '2.2.1' + ] repositories { google() @@ -9,9 +12,9 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:${dokka_version}" + classpath 'com.android.tools.build:gradle:3.5.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" + classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" } } @@ -22,35 +25,58 @@ repositories { apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'org.jetbrains.dokka-android' +apply plugin: 'org.jetbrains.dokka' android { - compileSdkVersion 27 - buildToolsVersion '27.1.1' + compileSdkVersion 29 + buildToolsVersion '29.0.2' defaultConfig { - minSdkVersion 24 - targetSdkVersion 27 + minSdkVersion 14 + targetSdkVersion 29 } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + dataBinding.enabled = true + lintOptions { disable 'MissingTranslation', 'ExtraTranslation' // translations from Transifex are not always up to date disable "OnClick" // doesn't recognize Kotlin onClick methods } defaultConfig { - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + dokka.configuration { + sourceLink { + url = "https://gitlab.com/bitfireAT/cert4android/tree/master/" + lineSuffix = "#L" + } + jdkVersion = 7 } } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}" - implementation 'com.android.support:appcompat-v7:27.1.1' - implementation 'com.android.support:cardview-v7:27.1.1' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' + implementation 'androidx.lifecycle:lifecycle-livedata:2.1.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0' + implementation 'com.google.android.material:material:1.2.0-alpha03' + implementation "org.conscrypt:conscrypt-android:${versions.conscrypt}" - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test:rules:1.0.2' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.12.5' + androidTestImplementation 'commons-io:commons-io:2.6' + androidTestImplementation 'org.apache.commons:commons-lang3:3.9' testImplementation 'junit:junit:4.12' } diff --git a/gradle.properties b/gradle.properties index 8bd86f6805108dec87d0be823bdb1384bec8aa19..d9cf55df7c1945850a53322a299a58f4b3229097 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,2 @@ org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5801d8e8035b2cc3012598ce30a163b41d437654..dcc77281b370672855b0e5f012b67acf3db3e2a6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Aug 23 16:42:17 CEST 2016 +#Wed Apr 17 22:31:47 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip diff --git a/project.properties b/project.properties deleted file mode 100644 index 2e788c3f757545052172df570928924dbd0827d5..0000000000000000000000000000000000000000 --- a/project.properties +++ /dev/null @@ -1,3 +0,0 @@ - -android.library=true - diff --git a/src/androidTest/java/foundation/e/cert4android/CustomCertManagerTest.kt b/src/androidTest/java/foundation/e/cert4android/CustomCertManagerTest.kt index e05b6ff8a905c9f7c298d8e16048725f01e67da8..0a5137aa2aa1b32d620ac53f51deb953f0382940 100644 --- a/src/androidTest/java/foundation/e/cert4android/CustomCertManagerTest.kt +++ b/src/androidTest/java/foundation/e/cert4android/CustomCertManagerTest.kt @@ -11,9 +11,8 @@ package foundation.e.cert4android import android.app.Service import android.content.Intent import android.os.IBinder -import android.support.test.InstrumentationRegistry.getContext -import android.support.test.InstrumentationRegistry.getTargetContext -import android.support.test.rule.ServiceTestRule +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import androidx.test.rule.ServiceTestRule import org.junit.After import org.junit.Assert.assertNotNull import org.junit.Assume.assumeNotNull @@ -70,12 +69,13 @@ class CustomCertManagerTest { val binder = bindService(CustomCertService::class.java) assertNotNull(binder) - CustomCertManager.resetCertificates(getContext()) + val context = getInstrumentation().context + CustomCertManager.resetCertificates(context) - certManager = CustomCertManager(getContext(), false) + certManager = CustomCertManager(context, false) assertNotNull(certManager) - paranoidCertManager = CustomCertManager(getContext(), false, false) + paranoidCertManager = CustomCertManager(context, false, false) assertNotNull(paranoidCertManager) } @@ -114,7 +114,7 @@ class CustomCertManagerTest { // remove certificate and check again // should now be rejected for the whole session, i.e. no timeout anymore - val intent = Intent(getContext(), CustomCertService::class.java) + val intent = Intent(getInstrumentation().context, CustomCertService::class.java) intent.action = CustomCertService.CMD_CERTIFICATION_DECISION intent.putExtra(CustomCertService.EXTRA_CERTIFICATE, siteCerts!!.first().encoded) intent.putExtra(CustomCertService.EXTRA_TRUSTED, false) @@ -124,7 +124,7 @@ class CustomCertManagerTest { private fun addCustomCertificate() { // add certificate and check again - val intent = Intent(getContext(), CustomCertService::class.java) + val intent = Intent(getInstrumentation().context, CustomCertService::class.java) intent.action = CustomCertService.CMD_CERTIFICATION_DECISION intent.putExtra(CustomCertService.EXTRA_CERTIFICATE, siteCerts!!.first().encoded) intent.putExtra(CustomCertService.EXTRA_TRUSTED, true) @@ -133,10 +133,10 @@ class CustomCertManagerTest { private fun bindService(clazz: Class): IBinder { - var binder = serviceTestRule.bindService(Intent(getTargetContext(), clazz)) + var binder = serviceTestRule.bindService(Intent(getInstrumentation().targetContext, clazz)) var it = 0 while (binder == null && it++ <100) { - binder = serviceTestRule.bindService(Intent(getTargetContext(), clazz)) + binder = serviceTestRule.bindService(Intent(getInstrumentation().targetContext, clazz)) System.err.println("Waiting for ServiceTestRule.bindService") Thread.sleep(50) } diff --git a/src/androidTest/resources/sample.crt b/src/androidTest/resources/sample.crt new file mode 100644 index 0000000000000000000000000000000000000000..c8139807ced56b2a9859a38e7180dc87d07ab1d6 --- /dev/null +++ b/src/androidTest/resources/sample.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEEzCCAfsCAQEwDQYJKoZIhvcNAQEFBQAwRjELMAkGA1UEBhMCQ0ExEzARBgNV +BAgMClNvbWUtU3RhdGUxEDAOBgNVBAoMB0NBIENlcnQxEDAOBgNVBAMMB0NBIENl +cnQwHhcNMTgwMTEzMjAyOTI5WhcNMTkwMTEzMjAyOTI5WjBZMQswCQYDVQQGEwJD +QTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMRIwEAYDVQQDDAlVc2VyIENlcnQwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQDqOyHAeG4psE/f6i/eTfwbhn6j7WaFXxZiSOWwpQZmzRrx +MrfkABJCk0X7KNgCaJcmBkG9G1Ri4HfKrxvJFswMXknlq+0ulGBk7oDnZM+pihuX +3D9VCWMMkCqYhLCGADj2zB2mkX4LpcMRi6XoOetKURE/vcIy7rSLAtJM6ZRdftfh +2ZxnautS1Tyujh9Au3NI/+Of80tT/nA+oBJQeT1fB/ga1OQlZP5kjSaA7IPiIbTz +QBO+r898MvqK/lwsvOYnWAp7TY03z+vPfCs0zjijZEl9Wrl0hW6o5db5kU1v5bcr +p87hxFJsGD2HIr2y6kvYfL2hn+h9iANyYdRnUgapAgMBAAEwDQYJKoZIhvcNAQEF +BQADggIBAHANsiJITedXPyp89lVMEmGY3zKtOqgQ3tqjvjlNt2sdPnj7wmZbmrNd +sa90S/UwOn8PzEFOVxYy1BPlljlEjtjmc4OHMcm4P4Zv36uawHilmK8V+zT59gCK +ftB5FP2TLFUFi2X9o8J06d0xJRE77uewN155NV4RmPuP4b/tMmeixoQppHqLqEr5 +lgEUnt3Mh1ctmeFQFJR6lJ01hlB0gdpVHIhzrVLTO3uo8ePLJTmxP6tyKl/HXj9F +mpVsKb1kriKwbkGczfw99OUZeUVbTwQOR07r0SrG71B7IuDvxIORnhQc1OUjt7ob +wjdaZauAHxpGBRu+hw9Yqaxchk9Gldy1nEjGyyVCD0FU5taXbl8PhBWEDc4U9tI+ +xVNmPpsSuCsbz3Mjd1YIVRGL99vLrKsQcj+TNM+jJKKRKes3ihl+l/0FwG6UuO7L +EvjlUg5hOtYi1D7xuYyMjroGBGh7swYMt6w4eCDbcjzcCkaCi0H2pScM/rLBpDjS +LIoGCvZ1LBdi933/iOj1/8dxGZwY6fEgcyiD2n0xAgYIniLWjEZXOMdIK5FNTNga +Tswanvp+6Noa4oIu/hl/LXvPMsouaWfSEbRe0Dshi3GpLj3YtEHoN9DHB8bn7jy5 +34By81GT41m5kq3hWP//x9kSHYSADpbovCbKbElU1qSt6vTVR4nq +-----END CERTIFICATE----- diff --git a/src/androidTest/resources/sample.key b/src/androidTest/resources/sample.key new file mode 100644 index 0000000000000000000000000000000000000000..af8f903d41f5948cda0eb46c3f471d2ed013a099 Binary files /dev/null and b/src/androidTest/resources/sample.key differ diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 2b10628355238232672abaf38cd2079b35fa68fc..d90966e8dd2b0125a56b1173b14a25bca257ba3d 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ diff --git a/src/main/java/foundation/e/cert4android/CustomCertManager.kt b/src/main/java/foundation/e/cert4android/CustomCertManager.kt index 17255304a36497bfbbb6beefe2162018c4bfa8f7..d8c58f87dbd26fb856446d03f6b06922d12349e2 100644 --- a/src/main/java/foundation/e/cert4android/CustomCertManager.kt +++ b/src/main/java/foundation/e/cert4android/CustomCertManager.kt @@ -34,6 +34,7 @@ import javax.net.ssl.X509TrustManager * @param interactive true: users will be notified in case of unknown certificates; * false: unknown certificates will be rejected (only uses custom certificate key store) * @param trustSystemCerts whether system certificates will be trusted + * @param appInForeground Whether to launch [TrustCertificateActivity] directly. The notification will always be shown. * * @constructor Creates a new instance, using a certain [CustomCertService] messenger (for testing). * Must not be run from the main thread because this constructor may request binding to [CustomCertService]. @@ -45,7 +46,10 @@ import javax.net.ssl.X509TrustManager class CustomCertManager @JvmOverloads constructor( val context: Context, val interactive: Boolean = true, - trustSystemCerts: Boolean = true + trustSystemCerts: Boolean = true, + + @Volatile + var appInForeground: Boolean = false ): X509TrustManager, Closeable { companion object { @@ -62,7 +66,7 @@ class CustomCertManager @JvmOverloads constructor( } var service: ICustomCertService? = null - private var serviceConnection: ServiceConnection? + private var serviceConn: ServiceConnection? = null private var serviceLock = Object() /** system-default trust store */ @@ -70,12 +74,8 @@ class CustomCertManager @JvmOverloads constructor( if (trustSystemCerts) CertUtils.getTrustManager(null) else null - /** Whether to launch [TrustCertificateActivity] directly. The notification will always be shown. */ - var appInForeground = false - - init { - serviceConnection = object: ServiceConnection { + val newServiceConn = object: ServiceConnection { override fun onServiceConnected(className: ComponentName, binder: IBinder) { Constants.log.fine("Connected to service") synchronized(serviceLock) { @@ -91,12 +91,15 @@ class CustomCertManager @JvmOverloads constructor( } } - if (Looper.myLooper() == Looper.getMainLooper()) + check(Looper.myLooper() != Looper.getMainLooper()) { // service is actually created after bindService() by code running in looper, so this would block - throw IllegalStateException("must not be run on main thread") + "must not be run on main thread" + } Constants.log.fine("Binding to service") - if (context.bindService(Intent(context, CustomCertService::class.java), serviceConnection, Context.BIND_AUTO_CREATE)) { + if (context.bindService(Intent(context, CustomCertService::class.java), newServiceConn, Context.BIND_AUTO_CREATE)) { + serviceConn = newServiceConn + Constants.log.fine("Waiting for service to be bound") synchronized(serviceLock) { while (service == null) @@ -110,10 +113,10 @@ class CustomCertManager @JvmOverloads constructor( } override fun close() { - serviceConnection?.let { + serviceConn?.let { try { context.unbindService(it) - serviceConnection = null + serviceConn = null } catch (e: Exception) { Constants.log.log(Level.WARNING, "Couldn't unbind CustomCertService", e) } diff --git a/src/main/java/foundation/e/cert4android/CustomCertService.kt b/src/main/java/foundation/e/cert4android/CustomCertService.kt index 210e0ab06b0d8293b2b284e191af6fc43d2ef65b..064cf246d4ed325b7c5a04f90a968973a930bfa2 100644 --- a/src/main/java/foundation/e/cert4android/CustomCertService.kt +++ b/src/main/java/foundation/e/cert4android/CustomCertService.kt @@ -12,23 +12,27 @@ import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent -import android.support.v4.app.NotificationCompat +import android.util.Log import android.widget.Toast +import androidx.core.app.NotificationCompat +import org.conscrypt.Conscrypt import java.io.ByteArrayInputStream import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.security.KeyStore import java.security.KeyStoreException +import java.security.Security import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.util.* import java.util.logging.Level +import javax.net.ssl.SSLContext import javax.net.ssl.X509TrustManager /** * The service which manages the certificates. Communications with - * the [CustomCertManager]s over IPC. + * the [CustomCertManager]s over IPC. Initializes Conscrypt when class is loaded. * * This services is both a started and a bound service. */ @@ -51,6 +55,18 @@ class CustomCertService: Service() { const val KEYSTORE_DIR = "KeyStore" const val KEYSTORE_NAME = "KeyStore.bks" + + init { + // initialize Conscrypt + Security.insertProviderAt(Conscrypt.newProvider(), 1) + + val version = Conscrypt.version() + Log.i(Constants.TAG, "Using Conscrypt/${version.major()}.${version.minor()}.${version.patch()} for TLS") + val engine = SSLContext.getDefault().createSSLEngine() + Log.i(Constants.TAG, "Enabled protocols: ${engine.enabledProtocols.joinToString(", ")}") + Log.i(Constants.TAG, "Enabled ciphers: ${engine.enabledCipherSuites.joinToString(", ")}") + } + } private lateinit var keyStoreFile: File @@ -172,7 +188,7 @@ class CustomCertService: Service() { // bound service - val binder = object: ICustomCertService.Stub() { + private val binder = object: ICustomCertService.Stub() { override fun checkTrusted(raw: ByteArray, interactive: Boolean, foreground: Boolean, callback: IOnCertificateDecision) { val cert: X509Certificate? = try { @@ -246,11 +262,7 @@ class CustomCertService: Service() { override fun abortCheck(callback: IOnCertificateDecision) { for ((cert, list) in pendingDecisions) { - val it = list.listIterator() - while (it.hasNext()) - if (it.next() == callback) - it.remove() - + list.removeAll { it == callback } if (list.isEmpty()) pendingDecisions -= cert } diff --git a/src/main/java/foundation/e/cert4android/NotificationUtils.kt b/src/main/java/foundation/e/cert4android/NotificationUtils.kt index 0e8ab99d8e5fb5f3e6cef7b265ed80a379bde73b..31a15abc66684cb1efa78af5e2231c0bfa93135b 100644 --- a/src/main/java/foundation/e/cert4android/NotificationUtils.kt +++ b/src/main/java/foundation/e/cert4android/NotificationUtils.kt @@ -12,7 +12,7 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.os.Build -import android.support.v4.app.NotificationManagerCompat +import androidx.core.app.NotificationManagerCompat object NotificationUtils { diff --git a/src/main/java/foundation/e/cert4android/TrustCertificateActivity.kt b/src/main/java/foundation/e/cert4android/TrustCertificateActivity.kt index 072ff29d7a5d52f1953ffd4f3114edebcfcb3a46..e80930e58a11de1b215f08bd0d2f65b7bb1c668f 100644 --- a/src/main/java/foundation/e/cert4android/TrustCertificateActivity.kt +++ b/src/main/java/foundation/e/cert4android/TrustCertificateActivity.kt @@ -10,85 +10,49 @@ package foundation.e.cert4android import android.content.Intent import android.os.Bundle -import android.support.v7.app.AppCompatActivity import android.view.View -import android.widget.Button -import android.widget.CheckBox -import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProviders +import foundation.e.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 class TrustCertificateActivity: AppCompatActivity() { companion object { const val EXTRA_CERTIFICATE = "certificate" - - val certFactory = CertificateFactory.getInstance("X.509")!! } + private lateinit var model: Model override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_trust_certificate) - showCertificate() + model = ViewModelProviders.of(this).get(Model::class.java) + model.processIntent(intent) + + val binding = DataBindingUtil.setContentView(this, R.layout.activity_trust_certificate) + binding.lifecycleOwner = this + binding.model = model } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - this.intent = intent - showCertificate() + model.processIntent(intent) } - private fun showCertificate() { - val raw = intent.getByteArrayExtra(EXTRA_CERTIFICATE) - (certFactory.generateCertificate(ByteArrayInputStream(raw)) as X509Certificate?)?.let { cert -> - val subject: String - try { - subject = if (cert.issuerAlternativeNames != null) { - val sb = StringBuilder() - for (altName in cert.subjectAlternativeNames.orEmpty()) { - val name = altName[1] - if (name is String) - sb.append("[").append(altName[0]).append("]").append(name).append(" ") - } - sb.toString() - } else - cert.subjectDN.name - - var tv = findViewById(R.id.issuedFor) - tv.text = subject - - tv = findViewById(R.id.issuedBy) - tv.text = cert.issuerDN.toString() - - val formatter = DateFormat.getDateInstance(DateFormat.LONG) - tv = findViewById(R.id.validity_period) - tv.text = getString(R.string.trust_certificate_validity_period_value, - formatter.format(cert.notBefore), - formatter.format(cert.notAfter)) - - tv = findViewById(R.id.fingerprint_sha1) - tv.text = fingerprint(cert, "SHA-1") - tv = findViewById(R.id.fingerprint_sha256) - tv.text = fingerprint(cert, "SHA-256") - } catch(e: CertificateParsingException) { - Constants.log.log(Level.WARNING, "Couldn't parse certificate", e) - } - } - - val btnAccept = findViewById