Skip to content

Commit

Permalink
TrustManager: encapsulate certificates in TrustPoints with display na…
Browse files Browse the repository at this point in the history
…mes and icons

Signed-off-by: Kees Geluk <[email protected]>
  • Loading branch information
keesgeluk authored and davidz25 committed Dec 13, 2023
1 parent a6e12b9 commit 406bcd0
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.preference.PreferenceManager
import com.android.identity.storage.GenericStorageEngine
import com.android.identity.storage.StorageEngine
import com.android.identity.trustmanagement.TrustManager
import com.android.identity.trustmanagement.TrustPoint
import com.android.mdl.appreader.settings.UserPreferences
import com.android.mdl.appreader.util.KeysAndCertificates
import com.google.android.material.color.DynamicColors
Expand Down Expand Up @@ -45,10 +46,10 @@ class VerifierApp : Application() {
certificateStorageEngineInstance = certificateStorageEngine
certificateStorageEngineInstance.enumerate().forEach {
val certificate = parseCertificate(certificateStorageEngineInstance.get(it)!!)
trustManagerInstance.addCertificate(certificate)
trustManagerInstance.addTrustPoint(TrustPoint(certificate))
}
KeysAndCertificates.getTrustedIssuerCertificates(this).forEach {
trustManagerInstance.addCertificate(it)
trustManagerInstance.addTrustPoint(TrustPoint(it))
}
}

Expand All @@ -57,14 +58,13 @@ class VerifierApp : Application() {
private lateinit var userPreferencesInstance: UserPreferences
lateinit var trustManagerInstance: TrustManager
lateinit var certificateStorageEngineInstance: StorageEngine

fun isDebugLogEnabled(): Boolean {
return userPreferencesInstance.isDebugLoggingEnabled()
}
}

/**
* Parse a byte array an X509 certificate
* Parse a byte array as an X509 certificate
*/
private fun parseCertificate(certificateBytes: ByteArray): X509Certificate {
return CertificateFactory.getInstance("X509")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ private fun PreviewCaCertificatesScreen() {
sha1Fingerprint = "9D 80 9B CF 63 AA86 29 E9 3C 78 9A EA DA 15 56 7E BF 56 D8",
docTypes = listOf("Doc type 1", "Doc type 2"),
supportsDelete = true,
certificate = null
trustPoint = null
),
onDeleteCertificate = {}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.android.identity.trustmanagement.TrustPoint
import com.android.mdl.appreader.VerifierApp
import com.android.mdl.appreader.theme.ReaderAppTheme
import com.android.mdl.appreader.trustmanagement.getSubjectKeyIdentifier
Expand Down Expand Up @@ -76,7 +77,7 @@ class CaCertificatesFragment : Fragment() {
this.requireContext().contentResolver.openInputStream(uri).use { inputStream ->
if (inputStream != null) {
val certificate = parseCertificate(inputStream.readBytes())
VerifierApp.trustManagerInstance.addCertificate(certificate)
VerifierApp.trustManagerInstance.addTrustPoint(TrustPoint(certificate))
VerifierApp.certificateStorageEngineInstance.put(
certificate.getSubjectKeyIdentifier(),
certificate.encoded
Expand All @@ -101,7 +102,7 @@ class CaCertificatesFragment : Fragment() {
}
val text = clipboard.primaryClip?.getItemAt(0)?.text!!
val certificate = parseCertificate(text.toString().toByteArray())
VerifierApp.trustManagerInstance.addCertificate(certificate)
VerifierApp.trustManagerInstance.addTrustPoint(TrustPoint(certificate))
VerifierApp.certificateStorageEngineInstance.put(
certificate.getSubjectKeyIdentifier(),
certificate.encoded
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ private fun PreviewCaCertificatesScreen() {
sha1Fingerprint = "9D 80 9B CF 63 AA86 29 E9 3C 78 9A EA DA 15 56 7E BF 56 D8",
docTypes = emptyList(),
supportsDelete = false,
certificate = null
trustPoint = null
),
CertificateItem(
title = "Test 2",
Expand All @@ -132,7 +132,7 @@ private fun PreviewCaCertificatesScreen() {
sha1Fingerprint = "9D 80 9B CF 63 AA86 29 E9 3C 78 9A EA DA 15 56 7E BF 56 D8",
docTypes = emptyList(),
supportsDelete = false,
certificate = null
trustPoint = null
)
)
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class CaCertificatesViewModel() : ViewModel() {
val currentCertificateItem = _currentCertificateItem.asStateFlow()
fun loadCertificates() {
val certificates =
VerifierApp.trustManagerInstance.getAllCertificates().map { it.toCertificateItem() }
VerifierApp.trustManagerInstance.getAllTrustPoints().map { it.toCertificateItem() }
_screenState.update { it.copy(certificates = certificates) }
}

Expand All @@ -29,9 +29,9 @@ class CaCertificatesViewModel() : ViewModel() {
}

fun deleteCertificate() {
_currentCertificateItem.value?.certificate?.let {
VerifierApp.trustManagerInstance.removeCertificate(it)
VerifierApp.certificateStorageEngineInstance.delete(it.getSubjectKeyIdentifier())
_currentCertificateItem.value?.trustPoint?.let {
VerifierApp.trustManagerInstance.removeTrustPoint(it)
VerifierApp.certificateStorageEngineInstance.delete(it.certificate.getSubjectKeyIdentifier())
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.android.mdl.appreader.settings

import java.security.cert.X509Certificate
import com.android.identity.trustmanagement.TrustPoint
import java.util.Date

data class CertificateItem(
Expand All @@ -17,6 +17,6 @@ data class CertificateItem(
val sha1Fingerprint: String,
val docTypes: List<String>,
val supportsDelete: Boolean,
val certificate: X509Certificate?
val trustPoint: TrustPoint?
) {
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
package com.android.mdl.appreader.settings

import com.android.identity.trustmanagement.TrustPoint
import com.android.mdl.appreader.VerifierApp
import com.android.mdl.appreader.trustmanagement.getCommonName
import com.android.mdl.appreader.trustmanagement.getOrganisation
import com.android.mdl.appreader.trustmanagement.getSubjectKeyIdentifier
import com.android.mdl.appreader.trustmanagement.organisationalUnit
import java.lang.StringBuilder
import java.security.MessageDigest
import java.security.cert.X509Certificate

fun X509Certificate.toCertificateItem(docTypes: List<String> = emptyList()): CertificateItem {
val subject = this.subjectX500Principal
val issuer = this.issuerX500Principal
fun TrustPoint.toCertificateItem(docTypes: List<String> = emptyList()): CertificateItem {
val subject = this.certificate.subjectX500Principal
val issuer = this.certificate.issuerX500Principal
val sha255Fingerprint = hexWithSpaces(
MessageDigest.getInstance("SHA-256").digest(
this.encoded
this.certificate.encoded
)
)
val sha1Fingerprint = hexWithSpaces(
MessageDigest.getInstance("SHA-1").digest(
this.encoded
this.certificate.encoded
)
)
val defaultValue = "<Not part of certificate>"
Expand All @@ -32,13 +32,13 @@ fun X509Certificate.toCertificateItem(docTypes: List<String> = emptyList()): Cer
commonNameIssuer = issuer.getCommonName(defaultValue),
organisationIssuer = issuer.getOrganisation(defaultValue),
organisationalUnitIssuer = issuer.organisationalUnit(defaultValue),
notBefore = this.notBefore,
notAfter = this.notAfter,
notBefore = this.certificate.notBefore,
notAfter = this.certificate.notAfter,
sha255Fingerprint = sha255Fingerprint,
sha1Fingerprint = sha1Fingerprint,
docTypes = docTypes,
supportsDelete = VerifierApp.certificateStorageEngineInstance.get(this.getSubjectKeyIdentifier()) != null ,
certificate = this
supportsDelete = VerifierApp.certificateStorageEngineInstance.get(this.certificate.getSubjectKeyIdentifier()) != null ,
trustPoint = this
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,91 +20,93 @@ import java.security.cert.PKIXCertPathChecker
import java.security.cert.X509Certificate

/**
* This class is used for the verification of a certificate
* chain.
* This class is used for the verification of a certificate chain.
*
* The user of the class can add trust roots using method
* [addCertificate]. At this moment certificates of type
* [X509Certificate] are supported.
* The user of the class can add trust roots using method [addTrustPoint].
* At this moment certificates of type [X509Certificate] are supported.
*
* The Subject Key Identifier (extension 2.5.29.14 in the
* [X509Certificate]) is used as the primary key / unique
* identifier of the root CA certificate. In the verification
* of the chain this will be matched with the Authority Key
* Identifier (extension 2.5.29.35) of the certificate issued
* by this root CA.
* The Subject Key Identifier (extension 2.5.29.14 in the [X509Certificate])
* is used as the primary key / unique identifier of the root CA certificate.
* In the verification of the chain this will be matched with the Authority
* Key Identifier (extension 2.5.29.35) of the certificate issued by this
* root CA.
*/
class TrustManager() {

private val certificates: MutableMap<String, X509Certificate> = mutableMapOf()
private val certificates: MutableMap<String, TrustPoint> = mutableMapOf()

/**
* Nested class containing the result of the verification
* of a certificate chain.
* Nested class containing the result of the verification of a certificate
* chain.
*/
class TrustResult(
var isTrusted: Boolean,
var trustChain: List<X509Certificate> = emptyList(),
var trustPoints: List<TrustPoint> = emptyList(),
var error: Throwable? = null
)

/**
* Add a certificate to the [TrustManager].
* Add a [TrustPoint] to the [TrustManager].
*/
fun addCertificate(certificate: X509Certificate) {
val key = TrustManagerUtil.getSubjectKeyIdentifier(certificate)
certificates[key] = certificate
fun addTrustPoint(trustPoint: TrustPoint) {
val key = TrustManagerUtil.getSubjectKeyIdentifier(trustPoint.certificate)
if (key != "") {
// only certificates with the Subject Key Identifier extension will be added
certificates[key] = trustPoint
}
}

/**
* Get all the certificates in the [TrustManager].
* Get all the [TrustPoint]s in the [TrustManager].
*/
fun getAllCertificates(): List<X509Certificate> {
fun getAllTrustPoints(): List<TrustPoint> {
return certificates.values.toList()
}

/**
* Remove a certificate from the [TrustManager].
* Remove a [TrustPoint] from the [TrustManager].
*/
fun removeCertificate(certificate: X509Certificate) {
val key = TrustManagerUtil.getSubjectKeyIdentifier(certificate)
fun removeTrustPoint(trustPoint: TrustPoint) {
val key = TrustManagerUtil.getSubjectKeyIdentifier(trustPoint.certificate)
certificates.remove(key)
}

/**
* Verify a certificate chain (without the self-signed
* root certificate) with the possibility of custom
* validations on the certificates ([customValidators]),
* for instance the country code in certificate chain
* of the mDL, like implemented in the CountryValidator
* in the reader app.
* Verify a certificate chain (without the self-signed root certificate)
* with the possibility of custom validations on the certificates
* ([customValidators]), for instance the country code in certificate chain
* of the mDL, like implemented in the CountryValidator in the reader app.
*
* @param [chain] the certificate chain without the
* self-signed root certificate
* @param [customValidators] optional parameter with
* custom validators
* @param [chain] the certificate chain without the self-signed root
* certificate
* @param [customValidators] optional parameter with custom validators
* @return [TrustResult] a class that returns a verdict
* [TrustResult.isTrusted], optionally [TrustResult.trustChain]:
* the complete certificate chain, including the root
* certificate and optionally [TrustResult.error]: an
* error message when the certificate chain is not trusted.
* [TrustResult.isTrusted], optionally [TrustResult.trustPoints] the found
* trusted root certificates with their display names and icons, optionally
* [TrustResult.trustChain], the complete certificate chain, including the
* root/intermediate certificate(s), and optionally [TrustResult.error]:
* an error message when the certificate chain is not trusted.
*/
fun verify(
chain: List<X509Certificate>,
customValidators: List<PKIXCertPathChecker> = emptyList()
): TrustResult {
try {
val completeChain = createCertificateChain(chain)
val trustPoints = getAllTrustPoints(chain)
val completeChain = chain.plus(trustPoints.map { it.certificate })
try {
validateCertificationTrustPath(completeChain, customValidators)
return TrustResult(
isTrusted = true,
trustPoints = trustPoints,
trustChain = completeChain
)
} catch (e: Throwable) {
// there are validation errors, but the trust chain could be built.
return TrustResult(
isTrusted = false,
trustPoints = trustPoints,
trustChain = completeChain,
error = e
)
Expand All @@ -119,16 +121,16 @@ class TrustManager() {

}

private fun createCertificateChain(chain: List<X509Certificate>): List<X509Certificate>{
val result = chain.toMutableList()
private fun getAllTrustPoints(chain: List<X509Certificate>): List<TrustPoint> {
val result = mutableListOf<TrustPoint>()

// only an exception if not a single CA certificate is found
var caCertificate: X509Certificate? = findCaCertificate(chain)
var caCertificate: TrustPoint? = findCaCertificate(chain)
?: throw CertificateException("No trusted root certificate could not be found")
result.add(caCertificate!!)
while (caCertificate != null && !TrustManagerUtil.isSelfSigned(caCertificate)){
caCertificate = findCaCertificate(listOf(caCertificate))
if (caCertificate!= null){
while (caCertificate != null && !TrustManagerUtil.isSelfSigned(caCertificate.certificate)) {
caCertificate = findCaCertificate(listOf(caCertificate.certificate))
if (caCertificate != null) {
result.add(caCertificate)
}
}
Expand All @@ -138,13 +140,14 @@ class TrustManager() {
/**
* Find a CA Certificate for a certificate chain.
*/
private fun findCaCertificate(chain: List<X509Certificate>): X509Certificate? {
var result: X509Certificate? = null
private fun findCaCertificate(chain: List<X509Certificate>): TrustPoint? {
var result: TrustPoint? = null

chain.forEach { cert ->
run {
val key = TrustManagerUtil.getAuthorityKeyIdentifier(cert)
if (certificates.containsKey(key)) {
// only certificates with an Authority Key Identifier extension will be matched
if (key != "" && certificates.containsKey(key)) {
result = certificates[key]!!
}
}
Expand Down
Loading

0 comments on commit 406bcd0

Please sign in to comment.