...
 
Commits (3)
......@@ -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}"
}
......@@ -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
......@@ -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'
......
android.library=true
......@@ -172,7 +172,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 +246,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
}
......
......@@ -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()
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<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)
}
}
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()
......@@ -110,17 +74,70 @@ 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"
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))}"
} catch(e: Exception) {
e.message ?: "Couldn't create message digest"
}
fun hexString(data: ByteArray): String {
val str = data.mapTo(LinkedList()) { String.format("%02x", it) }
return str.joinToString(":")
}
private fun hexString(data: ByteArray): String {
val str = data.mapTo(LinkedList()) { String.format("%02x", it) }
return str.joinToString(":")
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_margin="@dimen/activity_margin">
<LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="@string/trust_certificate_unknown_certificate_found"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<androidx.cardview.widget.CardView
<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">
<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">
<LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="8dp">
android:layout_height="wrap_content">
<LinearLayout
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="8dp">
android:layout_marginBottom="16dp"
android:text="@string/trust_certificate_unknown_certificate_found"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextView.Heading"
android:layout_marginBottom="16dp"
android:text="@string/trust_certificate_x509_certificate_details"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
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"
tools:text="CN=example.com"/>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="8dp">
<TextView
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
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"
tools:text="CN=example.com"/>
android:orientation="vertical"
android:layout_margin="8dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
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"
tools:text="1.1.1000 – 2.2.2000"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextView.Heading"
android:layout_marginBottom="16dp"
android:text="@string/trust_certificate_x509_certificate_details"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
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"
tools:text="SHA-1: abcdef"/>
<TextView
android:id="@+id/fingerprint_sha256"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
tools:text="SHA-256: abcdef"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="@string/trust_certificate_issued_for"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="@{model.issuedFor}"
tools:text="CN=example.com"/>
<CheckBox
android:id="@+id/fingerprint_ok"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:layout_marginBottom="8dp"
android:text="@string/trust_certificate_fingerprint_verified"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="@string/trust_certificate_issued_by"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="@{model.issuedBy}"
tools:text="CN=example.com"/>
<androidx.appcompat.widget.ButtonBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="@string/trust_certificate_validity_period"/>
<TextView
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"/>
<Button
android:id="@+id/accept"
android:layout_width="wrap_content"
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="@string/trust_certificate_fingerprints"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:text="@string/trust_certificate_accept"
android:onClick="acceptCertificate"
android:enabled="false"/>
<Button
android:id="@+id/reject"
android:layout_width="wrap_content"
android:layout_marginTop="4dp"
android:text="@{model.sha1}"
tools:text="SHA-1: abcdef"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.AppCompat.Button.Borderless"
android:text="@string/trust_certificate_reject"
android:onClick="rejectCertificate"/>
android:layout_marginTop="4dp"
android:layout_marginBottom="16dp"
android:text="@{model.sha256}"
tools:text="SHA-256: abcdef"/>
</androidx.appcompat.widget.ButtonBarLayout>
<CheckBox
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"/>
</LinearLayout>
<androidx.appcompat.widget.ButtonBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
</androidx.cardview.widget.CardView>
<Button
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="@{model.verifiedByUser}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/trust_certificate_reset_info"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.AppCompat.Button.Borderless"
android:text="@string/trust_certificate_reject"
android:onClick="rejectCertificate"/>
</androidx.appcompat.widget.ButtonBarLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/trust_certificate_reset_info"/>
</LinearLayout>
</ScrollView>
\ No newline at end of file
</LinearLayout>
</ScrollView>
</layout>
......@@ -4,6 +4,8 @@
<string name="certificate_notification_connection_security">Seguridad de conexión</string>
<string name="certificate_notification_user_interaction">Por favor, revisa el certificado</string>
<string name="service_rejected_temporarily">Certificado rechazado temporalmente</string>
<string name="trust_certificate_unknown_certificate_found">cert4android ha encontrado un certificado desconocido. ¿Quieres que sea válido?</string>
<string name="trust_certificate_x509_certificate_details">Detalles del certificado X509</string>
<string name="trust_certificate_issued_for">Expedido para</string>
......