diff --git a/appverifier/src/main/java/com/android/mdl/appreader/VerifierApp.kt b/appverifier/src/main/java/com/android/mdl/appreader/VerifierApp.kt index 8aa1e1506..ac5afd621 100644 --- a/appverifier/src/main/java/com/android/mdl/appreader/VerifierApp.kt +++ b/appverifier/src/main/java/com/android/mdl/appreader/VerifierApp.kt @@ -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 @@ -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)) } } @@ -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") diff --git a/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificateDetailsScreen.kt b/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificateDetailsScreen.kt index 4e33d6563..b2cd48229 100644 --- a/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificateDetailsScreen.kt +++ b/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificateDetailsScreen.kt @@ -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 = {} ) diff --git a/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificatesFragment.kt b/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificatesFragment.kt index 45d433863..8fb953074 100644 --- a/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificatesFragment.kt +++ b/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificatesFragment.kt @@ -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 @@ -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 @@ -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 diff --git a/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificatesScreen.kt b/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificatesScreen.kt index 983094261..bf25020aa 100644 --- a/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificatesScreen.kt +++ b/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificatesScreen.kt @@ -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", @@ -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 ) ) ), diff --git a/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificatesViewModel.kt b/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificatesViewModel.kt index 414fa2108..88872c4b8 100644 --- a/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificatesViewModel.kt +++ b/appverifier/src/main/java/com/android/mdl/appreader/settings/CaCertificatesViewModel.kt @@ -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) } } @@ -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()) } } diff --git a/appverifier/src/main/java/com/android/mdl/appreader/settings/CertificateItem.kt b/appverifier/src/main/java/com/android/mdl/appreader/settings/CertificateItem.kt index 0dcf1e7d9..c0ac6e3db 100644 --- a/appverifier/src/main/java/com/android/mdl/appreader/settings/CertificateItem.kt +++ b/appverifier/src/main/java/com/android/mdl/appreader/settings/CertificateItem.kt @@ -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( @@ -17,6 +17,6 @@ data class CertificateItem( val sha1Fingerprint: String, val docTypes: List, val supportsDelete: Boolean, - val certificate: X509Certificate? + val trustPoint: TrustPoint? ) { } \ No newline at end of file diff --git a/appverifier/src/main/java/com/android/mdl/appreader/settings/Mappers.kt b/appverifier/src/main/java/com/android/mdl/appreader/settings/Mappers.kt index b614bddad..e0dd13874 100644 --- a/appverifier/src/main/java/com/android/mdl/appreader/settings/Mappers.kt +++ b/appverifier/src/main/java/com/android/mdl/appreader/settings/Mappers.kt @@ -1,5 +1,6 @@ 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 @@ -7,19 +8,18 @@ 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 = emptyList()): CertificateItem { - val subject = this.subjectX500Principal - val issuer = this.issuerX500Principal +fun TrustPoint.toCertificateItem(docTypes: List = 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 = "" @@ -32,13 +32,13 @@ fun X509Certificate.toCertificateItem(docTypes: List = 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 ) } diff --git a/identity/src/main/java/com/android/identity/trustmanagement/TrustManager.kt b/identity/src/main/java/com/android/identity/trustmanagement/TrustManager.kt index d953312b6..817c39f9b 100644 --- a/identity/src/main/java/com/android/identity/trustmanagement/TrustManager.kt +++ b/identity/src/main/java/com/android/identity/trustmanagement/TrustManager.kt @@ -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 = mutableMapOf() + private val certificates: MutableMap = 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 = emptyList(), + var trustPoints: List = 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 { + fun getAllTrustPoints(): List { 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, customValidators: List = 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 ) @@ -119,16 +121,16 @@ class TrustManager() { } - private fun createCertificateChain(chain: List): List{ - val result = chain.toMutableList() + private fun getAllTrustPoints(chain: List): List { + val result = mutableListOf() // 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) } } @@ -138,13 +140,14 @@ class TrustManager() { /** * Find a CA Certificate for a certificate chain. */ - private fun findCaCertificate(chain: List): X509Certificate? { - var result: X509Certificate? = null + private fun findCaCertificate(chain: List): 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]!! } } diff --git a/identity/src/main/java/com/android/identity/trustmanagement/TrustManagerUtil.kt b/identity/src/main/java/com/android/identity/trustmanagement/TrustManagerUtil.kt index 4744bdf10..f3d59880b 100644 --- a/identity/src/main/java/com/android/identity/trustmanagement/TrustManagerUtil.kt +++ b/identity/src/main/java/com/android/identity/trustmanagement/TrustManagerUtil.kt @@ -38,22 +38,24 @@ internal object TrustManagerUtil { private const val KEY_CERT_SIGN = 5 /** - * Get the Subject Key Identifier Extension from the - * X509 certificate in hexadecimal format. + * Get the Subject Key Identifier Extension from the X509 certificate + * in hexadecimal format. */ fun getSubjectKeyIdentifier(certificate: X509Certificate): String { val extensionValue = certificate.getExtensionValue(Extension.subjectKeyIdentifier.id) + ?: return "" val octets = DEROctetString.getInstance(extensionValue).octets val subjectKeyIdentifier = SubjectKeyIdentifier.getInstance(octets) return Util.toHex(subjectKeyIdentifier.keyIdentifier) } /** - * Get the Authority Key Identifier Extension from the - * X509 certificate in hexadecimal format. + * Get the Authority Key Identifier Extension from the X509 certificate + * in hexadecimal format. */ fun getAuthorityKeyIdentifier(certificate: X509Certificate): String { val extensionValue = certificate.getExtensionValue(Extension.authorityKeyIdentifier.id) + ?: return "" val octets = DEROctetString.getInstance(extensionValue).octets val authorityKeyIdentifier = AuthorityKeyIdentifier.getInstance(octets) return Util.toHex(authorityKeyIdentifier.keyIdentifier) @@ -62,13 +64,12 @@ internal object TrustManagerUtil { /** * Check whether a certificate is self-signed */ - fun isSelfSigned(certificate: X509Certificate): Boolean{ + fun isSelfSigned(certificate: X509Certificate): Boolean { return certificate.issuerX500Principal.name == certificate.subjectX500Principal.name } /** - * Check that the key usage is the creation of digital - * signatures. + * Check that the key usage is the creation of digital signatures. */ fun checkKeyUsageDocumentSigner(certificate: X509Certificate) { if (!hasKeyUsage(certificate, DIGITAL_SIGNATURE)) { @@ -77,13 +78,12 @@ internal object TrustManagerUtil { } /** - * Check the validity period of a certificate (based on - * the system date). + * Check the validity period of a certificate (based on the system date). */ fun checkValidity(certificate: X509Certificate) { // check if the certificate is currently valid - // NOTE does not check if it is valid within the validity period of the issuing - // CA + // NOTE does not check if it is valid within the validity period of + // the issuing CA certificate.checkValidity() // NOTE throws multiple exceptions derived from CertificateException } @@ -110,8 +110,8 @@ internal object TrustManagerUtil { } /** - * Check that the issuer in [certificate] is equal to - * the subject in [caCertificate]. + * Check that the issuer in [certificate] is equal to the subject in + * [caCertificate]. */ fun checkCaIsIssuer(certificate: X509Certificate, caCertificate: X509Certificate) { val issuerName = X500Name(certificate.issuerX500Principal.name) @@ -122,8 +122,8 @@ internal object TrustManagerUtil { } /** - * Verify the signature of the [certificate] with the - * public key of the [caCertificate]. + * Verify the signature of the [certificate] with the public key of the + * [caCertificate]. */ fun verifySignature(certificate: X509Certificate, caCertificate: X509Certificate) { try { diff --git a/identity/src/main/java/com/android/identity/trustmanagement/TrustPoint.kt b/identity/src/main/java/com/android/identity/trustmanagement/TrustPoint.kt new file mode 100644 index 000000000..c823c6e74 --- /dev/null +++ b/identity/src/main/java/com/android/identity/trustmanagement/TrustPoint.kt @@ -0,0 +1,17 @@ +package com.android.identity.trustmanagement + +import java.security.cert.X509Certificate + +/** + * Class used for the representation of a trusted CA [X509Certificate], a name + * suitable for display and an icon to display the certificate + * + * @param certificate an X509 certificate + * @param displayName a name suitable for display of the X509 certificate + * @param displayIcon an icon that represents + */ +data class TrustPoint( + val certificate: X509Certificate, + val displayName: String? = null, + val displayIcon: ByteArray? = null +) diff --git a/identity/src/test/java/com/android/identity/trustmanagement/TrustManagerTest.kt b/identity/src/test/java/com/android/identity/trustmanagement/TrustManagerTest.kt index 5102c5e24..b75ef8e84 100644 --- a/identity/src/test/java/com/android/identity/trustmanagement/TrustManagerTest.kt +++ b/identity/src/test/java/com/android/identity/trustmanagement/TrustManagerTest.kt @@ -168,7 +168,7 @@ class TrustManagerTest { val trustManager = TrustManager() // act (add certificate and verify chain) - trustManager.addCertificate(mdlCaCertificate) + trustManager.addTrustPoint(TrustPoint(mdlCaCertificate)) val result = trustManager.verify(listOf(mdlDsCertificate)) // assert @@ -183,8 +183,8 @@ class TrustManagerTest { val trustManager = TrustManager() // act (add intermediate and CA certificate and verify chain) - trustManager.addCertificate(intermediateCertificate) - trustManager.addCertificate(caCertificate) + trustManager.addTrustPoint(TrustPoint(intermediateCertificate)) + trustManager.addTrustPoint(TrustPoint(caCertificate)) val result = trustManager.verify(listOf(dsCertificate)) // assert @@ -199,7 +199,7 @@ class TrustManagerTest { val trustManager = TrustManager() // act (add intermediate certificate (without CA) and verify chain) - trustManager.addCertificate(intermediateCertificate) + trustManager.addTrustPoint(TrustPoint(intermediateCertificate)) val result = trustManager.verify(listOf(dsCertificate)) // assert