Loading build.gradle +7 −2 Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:3.3.1' classpath 'com.android.tools.build:gradle:3.3.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:${dokka_version}" } Loading @@ -33,6 +33,8 @@ android { targetSdkVersion 28 } dataBinding.enabled = true lintOptions { disable 'MissingTranslation', 'ExtraTranslation' // translations from Transifex are not always up to date disable "OnClick" // doesn't recognize Kotlin onClick methods Loading @@ -48,10 +50,13 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' implementation 'androidx.lifecycle:lifecycle-livedata:2.0.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test:rules:1.1.1' androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.12.1' androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.12.2' androidTestImplementation 'commons-io:commons-io:2.6' androidTestImplementation 'org.apache.commons:commons-lang3:3.8.1' Loading src/main/java/at/bitfire/cert4android/TrustCertificateActivity.kt +79 −62 Original line number Diff line number Diff line Loading @@ -11,84 +11,48 @@ package at.bitfire.cert4android import android.content.Intent import android.os.Bundle 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 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 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<ActivityTrustCertificateBinding>(this, R.layout.activity_trust_certificate) binding.lifecycleOwner = this binding.model = model } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) this.intent = intent showCertificate() } 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<TextView>(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) } model.processIntent(intent) } val btnAccept = findViewById<Button>(R.id.accept) val cb = findViewById<CheckBox>(R.id.fingerprint_ok) cb.setOnCheckedChangeListener { _, state -> btnAccept.isEnabled = state } } fun acceptCertificate(view: View) { sendDecision(true) finish() Loading @@ -110,7 +74,58 @@ class TrustCertificateActivity: AppCompatActivity() { } private fun fingerprint(cert: X509Certificate, algorithm: String) = class Model: ViewModel() { companion object { val certFactory = CertificateFactory.getInstance("X.509")!! } val issuedFor = MutableLiveData<String>() val issuedBy = MutableLiveData<String>() val validFrom = MutableLiveData<String>() val validTo = MutableLiveData<String>() val sha1 = MutableLiveData<String>() val sha256 = MutableLiveData<String>() val verifiedByUser = MutableLiveData<Boolean>() fun processIntent(intent: Intent?) { intent?.getByteArrayExtra(EXTRA_CERTIFICATE)?.let { raw -> thread { val cert = certFactory.generateCertificate(ByteArrayInputStream(raw)) as? X509Certificate ?: return@thread try { val 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 issuedFor.postValue(subject) issuedBy.postValue(cert.issuerDN.toString()) val formatter = DateFormat.getDateInstance(DateFormat.LONG) validFrom.postValue(formatter.format(cert.notBefore)) validTo.postValue(formatter.format(cert.notAfter)) sha1.postValue(fingerprint(cert, SHA1.digestAlgorithm)) sha256.postValue(fingerprint(cert, SHA256.digestAlgorithm)) } catch(e: CertificateParsingException) { Constants.log.log(Level.WARNING, "Couldn't parse certificate", e) } } } } fun fingerprint(cert: X509Certificate, algorithm: String) = try { val md = MessageDigest.getInstance(algorithm) "$algorithm: ${hexString(md.digest(cert.encoded))}" Loading @@ -118,9 +133,11 @@ class TrustCertificateActivity: AppCompatActivity() { e.message ?: "Couldn't create message digest" } private fun hexString(data: ByteArray): String { fun hexString(data: ByteArray): String { val str = data.mapTo(LinkedList()) { String.format("%02x", it) } return str.joinToString(":") } } } No newline at end of file src/main/res/layout/activity_trust_certificate.xml +121 −113 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="model" type="at.bitfire.cert4android.TrustCertificateActivity.Model"/> </data> <ScrollView android:layout_height="match_parent" android:layout_width="match_parent" android:layout_margin="@dimen/activity_margin"> Loading Loading @@ -42,10 +49,10 @@ android:textStyle="bold" android:text="@string/trust_certificate_issued_for"/> <TextView android:id="@+id/issuedFor" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:text="@{model.issuedFor}" tools:text="CN=example.com"/> <TextView Loading @@ -54,10 +61,10 @@ android:textStyle="bold" android:text="@string/trust_certificate_issued_by"/> <TextView android:id="@+id/issuedBy" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:text="@{model.issuedBy}" tools:text="CN=example.com"/> <TextView Loading @@ -66,10 +73,10 @@ android:textStyle="bold" android:text="@string/trust_certificate_validity_period"/> <TextView android:id="@+id/validity_period" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:text="@{@string/trust_certificate_validity_period_value(model.validFrom, model.validTo)}" tools:text="1.1.1000 – 2.2.2000"/> <TextView Loading @@ -78,23 +85,25 @@ android:textStyle="bold" android:text="@string/trust_certificate_fingerprints"/> <TextView android:id="@+id/fingerprint_sha1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:text="@{model.sha1}" tools:text="SHA-1: abcdef"/> <TextView android:id="@+id/fingerprint_sha256" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:layout_marginBottom="16dp" android:text="@{model.sha256}" tools:text="SHA-256: abcdef"/> <CheckBox android:id="@+id/fingerprint_ok" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" android:layout_marginBottom="8dp" android:checked="@={model.verifiedByUser}" android:text="@string/trust_certificate_fingerprint_verified"/> <androidx.appcompat.widget.ButtonBarLayout Loading @@ -102,16 +111,14 @@ android:layout_height="wrap_content"> <Button android:id="@+id/accept" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/Widget.AppCompat.Button.Borderless.Colored" android:text="@string/trust_certificate_accept" android:onClick="acceptCertificate" android:enabled="false"/> android:enabled="@{model.verifiedByUser}"/> <Button android:id="@+id/reject" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/Widget.AppCompat.Button.Borderless" Loading @@ -132,3 +139,4 @@ </LinearLayout> </ScrollView> </layout> Loading
build.gradle +7 −2 Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:3.3.1' classpath 'com.android.tools.build:gradle:3.3.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:${dokka_version}" } Loading @@ -33,6 +33,8 @@ android { targetSdkVersion 28 } dataBinding.enabled = true lintOptions { disable 'MissingTranslation', 'ExtraTranslation' // translations from Transifex are not always up to date disable "OnClick" // doesn't recognize Kotlin onClick methods Loading @@ -48,10 +50,13 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' implementation 'androidx.lifecycle:lifecycle-livedata:2.0.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test:rules:1.1.1' androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.12.1' androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.12.2' androidTestImplementation 'commons-io:commons-io:2.6' androidTestImplementation 'org.apache.commons:commons-lang3:3.8.1' Loading
src/main/java/at/bitfire/cert4android/TrustCertificateActivity.kt +79 −62 Original line number Diff line number Diff line Loading @@ -11,84 +11,48 @@ package at.bitfire.cert4android import android.content.Intent import android.os.Bundle 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 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 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<ActivityTrustCertificateBinding>(this, R.layout.activity_trust_certificate) binding.lifecycleOwner = this binding.model = model } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) this.intent = intent showCertificate() } 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<TextView>(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) } model.processIntent(intent) } val btnAccept = findViewById<Button>(R.id.accept) val cb = findViewById<CheckBox>(R.id.fingerprint_ok) cb.setOnCheckedChangeListener { _, state -> btnAccept.isEnabled = state } } fun acceptCertificate(view: View) { sendDecision(true) finish() Loading @@ -110,7 +74,58 @@ class TrustCertificateActivity: AppCompatActivity() { } private fun fingerprint(cert: X509Certificate, algorithm: String) = class Model: ViewModel() { companion object { val certFactory = CertificateFactory.getInstance("X.509")!! } val issuedFor = MutableLiveData<String>() val issuedBy = MutableLiveData<String>() val validFrom = MutableLiveData<String>() val validTo = MutableLiveData<String>() val sha1 = MutableLiveData<String>() val sha256 = MutableLiveData<String>() val verifiedByUser = MutableLiveData<Boolean>() fun processIntent(intent: Intent?) { intent?.getByteArrayExtra(EXTRA_CERTIFICATE)?.let { raw -> thread { val cert = certFactory.generateCertificate(ByteArrayInputStream(raw)) as? X509Certificate ?: return@thread try { val 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 issuedFor.postValue(subject) issuedBy.postValue(cert.issuerDN.toString()) val formatter = DateFormat.getDateInstance(DateFormat.LONG) validFrom.postValue(formatter.format(cert.notBefore)) validTo.postValue(formatter.format(cert.notAfter)) sha1.postValue(fingerprint(cert, SHA1.digestAlgorithm)) sha256.postValue(fingerprint(cert, SHA256.digestAlgorithm)) } catch(e: CertificateParsingException) { Constants.log.log(Level.WARNING, "Couldn't parse certificate", e) } } } } fun fingerprint(cert: X509Certificate, algorithm: String) = try { val md = MessageDigest.getInstance(algorithm) "$algorithm: ${hexString(md.digest(cert.encoded))}" Loading @@ -118,9 +133,11 @@ class TrustCertificateActivity: AppCompatActivity() { e.message ?: "Couldn't create message digest" } private fun hexString(data: ByteArray): String { fun hexString(data: ByteArray): String { val str = data.mapTo(LinkedList()) { String.format("%02x", it) } return str.joinToString(":") } } } No newline at end of file
src/main/res/layout/activity_trust_certificate.xml +121 −113 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="model" type="at.bitfire.cert4android.TrustCertificateActivity.Model"/> </data> <ScrollView android:layout_height="match_parent" android:layout_width="match_parent" android:layout_margin="@dimen/activity_margin"> Loading Loading @@ -42,10 +49,10 @@ android:textStyle="bold" android:text="@string/trust_certificate_issued_for"/> <TextView android:id="@+id/issuedFor" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:text="@{model.issuedFor}" tools:text="CN=example.com"/> <TextView Loading @@ -54,10 +61,10 @@ android:textStyle="bold" android:text="@string/trust_certificate_issued_by"/> <TextView android:id="@+id/issuedBy" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:text="@{model.issuedBy}" tools:text="CN=example.com"/> <TextView Loading @@ -66,10 +73,10 @@ android:textStyle="bold" android:text="@string/trust_certificate_validity_period"/> <TextView android:id="@+id/validity_period" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:text="@{@string/trust_certificate_validity_period_value(model.validFrom, model.validTo)}" tools:text="1.1.1000 – 2.2.2000"/> <TextView Loading @@ -78,23 +85,25 @@ android:textStyle="bold" android:text="@string/trust_certificate_fingerprints"/> <TextView android:id="@+id/fingerprint_sha1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:text="@{model.sha1}" tools:text="SHA-1: abcdef"/> <TextView android:id="@+id/fingerprint_sha256" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:layout_marginBottom="16dp" android:text="@{model.sha256}" tools:text="SHA-256: abcdef"/> <CheckBox android:id="@+id/fingerprint_ok" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" android:layout_marginBottom="8dp" android:checked="@={model.verifiedByUser}" android:text="@string/trust_certificate_fingerprint_verified"/> <androidx.appcompat.widget.ButtonBarLayout Loading @@ -102,16 +111,14 @@ android:layout_height="wrap_content"> <Button android:id="@+id/accept" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/Widget.AppCompat.Button.Borderless.Colored" android:text="@string/trust_certificate_accept" android:onClick="acceptCertificate" android:enabled="false"/> android:enabled="@{model.verifiedByUser}"/> <Button android:id="@+id/reject" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/Widget.AppCompat.Button.Borderless" Loading @@ -132,3 +139,4 @@ </LinearLayout> </ScrollView> </layout>