Skip to content

Commit

Permalink
New ASN.1 parser/generator and migrate X509Cert and TrustManager.
Browse files Browse the repository at this point in the history
This adds a new ASN1 package for parsing/encoding ASN.1 using DER
encoding. Also port X509Cert, X509CertChain for both parsing and
generation.

Also migrate TrustManager to use this and since it's now Kotlin
Multiplatform make use of TrustManager in samples/testapp for both
reader and issuer authentication.

With X509Cert now being multiplatform, move all certificate generation
code to MdocUtils.kt in identity-mdoc library so it's available to
apps. Similarly, make SignedVical.create() multi-platform.

Also remove the drag handle on ConsentModalBottomSheet since the user
is never expected to use it.

Add JVM-unit tests to check that all public accessors on X509Cert
agree with X509Certificate.

Test: New unit tests and all unit tests pass.
Test: Manually tested in samples/testapp on both Android and iOS.
Test: Manually tested appholder, appverifier, wallet.
Signed-off-by: David Zeuthen <[email protected]>
  • Loading branch information
davidz25 committed Dec 19, 2024
1 parent ef558dc commit dadbb95
Show file tree
Hide file tree
Showing 79 changed files with 4,312 additions and 2,170 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,14 @@ class TransferDocumentFragment : Fragment() {
}
val doc = viewModel.getSelectedDocuments().first { reqDoc.docType == it.docType }
if (reqDoc.readerAuth != null && reqDoc.readerAuthenticated) {
var certChain: List<X509Certificate> =
reqDoc.readerCertificateChain!!.certificates.map { it.javaX509Certificate }
.toList()
val customValidators = CustomValidators.getByDocType(doc.docType)
val result = HolderApp.trustManagerInstance.verify(
chain = certChain,
customValidators = customValidators
chain = reqDoc.readerCertificateChain!!.certificates,
)
trusted = result.isTrusted
if (result.trustChain.any()) {
certChain = result.trustChain
commonName = result.trustChain.last().issuer.name
}
commonName = certChain.last().issuerX500Principal.getCommonName("")

// Add some information about the reader certificate used
if (result.isTrusted) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.android.identity.crypto.Algorithm
import com.android.identity.crypto.Crypto
import com.android.identity.crypto.EcCurve
import com.android.identity.crypto.EcPublicKeyDoubleCoordinate
import com.android.identity.crypto.X509CertChain
import com.android.identity.crypto.javaPublicKey
import com.android.identity.crypto.javaX509Certificates
import com.android.identity.crypto.toEcPrivateKey
Expand Down Expand Up @@ -163,19 +164,19 @@ class ShowDeviceResponseFragment : Fragment() {
)
sb.append("<h3>Doctype: <font color=\"$color\">${doc.docType}</font></h3>")

var certChain = doc.issuerCertificateChain.javaX509Certificates
var certChain = doc.issuerCertificateChain
val customValidators = CustomValidators.getByDocType(doc.docType)
val result = VerifierApp.trustManagerInstance.verify(
chain = certChain,
customValidators = customValidators
chain = certChain.certificates,
//customValidators = customValidators
)
if (result.trustChain.any()){
certChain = result.trustChain
certChain = X509CertChain(result.trustChain)
}
if (!result.isTrusted) {
sb.append("${getFormattedCheck(false)}Error in certificate chain validation: ${result.error?.message}<br>")
}
val commonName = certChain.last().issuerX500Principal.getCommonName("")
val commonName = certChain.certificates.last().issuer.name
sb.append("${getFormattedCheck(result.isTrusted)}Issuer’s DS Key Recognized: ($commonName)<br>")
sb.append("${getFormattedCheck(doc.issuerSignedAuthenticated)}Issuer Signed Authenticated<br>")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.annotation.AttrRes
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.android.identity.cbor.Cbor
import com.android.identity.crypto.X509Cert
import com.android.identity.documenttype.DocumentAttributeType
import com.android.identity.documenttype.MdocDataElement
import com.android.identity.crypto.javaPublicKey
Expand Down Expand Up @@ -207,13 +208,12 @@ class ShowDocumentFragment : Fragment() {

for (doc in documents) {
sb.append("<h3>Doctype: <font color=\"$primaryColor\">${doc.docType}</font></h3>")
val cc = mutableListOf<X509Certificate>()
doc.issuerCertificateChain.certificates.forEach() { c -> cc.add(c.javaX509Certificate) }
var certChain: List<X509Certificate> = cc
val cc = doc.issuerCertificateChain.certificates
var certChain: List<X509Cert> = cc
val customValidators = CustomValidators.getByDocType(doc.docType)
val result = VerifierApp.trustManagerInstance.verify(
chain = certChain,
customValidators = customValidators
//customValidators = customValidators
)
if (result.trustChain.any()) {
certChain = result.trustChain
Expand All @@ -222,7 +222,7 @@ class ShowDocumentFragment : Fragment() {
sb.append("${getFormattedCheck(false)}Error in certificate chain validation: ${result.error?.message}<br>")
}

val commonName = certChain.last().issuerX500Principal.getCommonName("")
val commonName = certChain.last().issuer.name
sb.append("${getFormattedCheck(result.isTrusted)}Issuer’s DS Key Recognized: ($commonName)<br>")
sb.append("${getFormattedCheck(doc.issuerSignedAuthenticated)}Issuer Signed Authenticated<br>")
var macOrSignatureString = "MAC"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package com.android.identity.android.securearea.cloud
import android.content.Context
import android.content.pm.PackageManager
import androidx.test.InstrumentationRegistry
import com.android.identity.asn1.ASN1Integer
import com.android.identity.crypto.Algorithm
import com.android.identity.crypto.X509CertChain
import com.android.identity.crypto.Crypto
import com.android.identity.crypto.EcCurve
import com.android.identity.crypto.X500Name
import com.android.identity.crypto.X509Cert
import com.android.identity.crypto.create
import com.android.identity.crypto.X509KeyUsage
import com.android.identity.crypto.javaX509Certificate
import com.android.identity.securearea.AttestationExtension
import com.android.identity.securearea.CreateKeySettings
Expand Down Expand Up @@ -69,19 +71,19 @@ class CloudSecureAreaTest {
val attestationKeySignatureAlgorithm = attestationKey.curve.defaultSigningAlgorithm
val attestationKeyCertificates = X509CertChain(
listOf(
X509Cert.create(
attestationKey.publicKey,
attestationKey,
null,
attestationKeySignatureAlgorithm,
"1",
attestationKeySubject,
attestationKeySubject,
attestationKeyValidFrom,
attestationKeyValidUntil,
setOf(),
listOf()
X509Cert.Builder(
publicKey = attestationKey.publicKey,
signingKey = attestationKey,
signatureAlgorithm = attestationKeySignatureAlgorithm,
serialNumber = ASN1Integer(1L),
subject = X500Name.fromName(attestationKeySubject),
issuer = X500Name.fromName(attestationKeySubject),
validFrom = attestationKeyValidFrom,
validUntil = attestationKeyValidUntil
)
.includeSubjectKeyIdentifier()
.setKeyUsage(setOf(X509KeyUsage.KEY_CERT_SIGN))
.build(),
)
)

Expand All @@ -92,19 +94,19 @@ class CloudSecureAreaTest {
val cloudBindingKeySignatureAlgorithm = cloudBindingKeyAttestationKey.curve.defaultSigningAlgorithm
val cloudBindingKeyAttestationCertificates = X509CertChain(
listOf(
X509Cert.create(
cloudBindingKeyAttestationKey.publicKey,
cloudBindingKeyAttestationKey,
null,
cloudBindingKeySignatureAlgorithm,
"1",
cloudBindingKeySubject,
cloudBindingKeySubject,
cloudBindingKeyValidFrom,
cloudBindingKeyValidUntil,
setOf(),
listOf()
X509Cert.Builder(
publicKey = cloudBindingKeyAttestationKey.publicKey,
signingKey = cloudBindingKeyAttestationKey,
signatureAlgorithm = cloudBindingKeySignatureAlgorithm,
serialNumber = ASN1Integer(1L),
subject = X500Name.fromName(cloudBindingKeySubject),
issuer = X500Name.fromName(cloudBindingKeySubject),
validFrom = cloudBindingKeyValidFrom,
validUntil = cloudBindingKeyValidUntil
)
.includeSubjectKeyIdentifier()
.setKeyUsage(setOf(X509KeyUsage.KEY_CERT_SIGN))
.build(),
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import com.android.identity.crypto.Crypto
import com.android.identity.crypto.EcCurve
import com.android.identity.crypto.EcPublicKey
import com.android.identity.crypto.EcSignature
import com.android.identity.crypto.fromDer
import com.android.identity.crypto.fromJavaX509Certificates
import com.android.identity.crypto.javaX509Certificate
import com.android.identity.securearea.AttestationExtension
Expand Down Expand Up @@ -356,7 +355,7 @@ open class CloudSecureArea(
val request1 = E2EESetupRequest1(
eDeviceKey.publicKey.toCoseKey(),
deviceNonce,
EcSignature.fromDer(EcCurve.P256, derSignature),
EcSignature.fromDerEncoded(EcCurve.P256.bitSize, derSignature),
response0.serverState
)
response = runBlocking { communicate(serverUrl, request1.toCbor()) }
Expand Down Expand Up @@ -620,7 +619,7 @@ open class CloudSecureArea(
val derSignatureLocal = s.sign()

val request1 = SignRequest1(
EcSignature.fromDer(EcCurve.P256, derSignatureLocal),
EcSignature.fromDerEncoded(EcCurve.P256.bitSize, derSignatureLocal),
(keyUnlockData as? CloudKeyUnlockData)?.passphrase,
response0.serverState
)
Expand Down Expand Up @@ -741,7 +740,7 @@ open class CloudSecureArea(
val derSignatureLocal = s.sign()

val request1 = KeyAgreementRequest1(
EcSignature.fromDer(EcCurve.P256, derSignatureLocal),
EcSignature.fromDerEncoded(EcCurve.P256.bitSize, derSignatureLocal),
(keyUnlockData as? CloudKeyUnlockData)?.passphrase,
response0.serverState
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.android.identity.android.mdoc.engagement.QrEngagementHelper
import com.android.identity.android.mdoc.transport.DataTransport
import com.android.identity.android.mdoc.transport.DataTransportOptions
import com.android.identity.android.mdoc.transport.DataTransportTcp
import com.android.identity.asn1.ASN1Integer
import com.android.identity.cbor.Bstr
import com.android.identity.cbor.Cbor.encode
import com.android.identity.cbor.CborArray
Expand All @@ -41,9 +42,10 @@ import com.android.identity.crypto.Crypto.createEcPrivateKey
import com.android.identity.crypto.EcCurve
import com.android.identity.crypto.EcPrivateKey
import com.android.identity.crypto.EcPublicKey
import com.android.identity.crypto.X500Name
import com.android.identity.crypto.X509Cert
import com.android.identity.crypto.X509CertChain
import com.android.identity.crypto.create
import com.android.identity.crypto.X509KeyUsage
import com.android.identity.mdoc.credential.MdocCredential
import com.android.identity.mdoc.mso.MobileSecurityObjectGenerator
import com.android.identity.mdoc.mso.StaticAuthDataGenerator
Expand Down Expand Up @@ -81,6 +83,7 @@ import java.util.Calendar
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import kotlin.random.Random
import kotlin.time.Duration.Companion.days

@Suppress("deprecation")
class DeviceRetrievalHelperTest {
Expand Down Expand Up @@ -174,21 +177,21 @@ class DeviceRetrievalHelperTest {
msoGenerator.addDigestIdsForNamespace(nameSpaceName, digests)
}
val validFrom = now()
val validUntil = fromEpochMilliseconds(
validFrom.toEpochMilliseconds() + 5L * 365 * 24 * 60 * 60 * 1000
)
val validUntil = validFrom + 5.days
documentSignerKey = createEcPrivateKey(EcCurve.P256)
documentSignerCert = X509Cert.create(
documentSignerKey.publicKey,
documentSignerKey,
null,
Algorithm.ES256,
"1",
"CN=State Of Utopia",
"CN=State Of Utopia",
validFrom,
validUntil, setOf(), listOf()
documentSignerCert = X509Cert.Builder(
publicKey = documentSignerKey.publicKey,
signingKey = documentSignerKey,
signatureAlgorithm = documentSignerKey.curve.defaultSigningAlgorithm,
serialNumber = ASN1Integer(1L),
subject = X500Name.fromName("CN=State of Utopia"),
issuer = X500Name.fromName("CN=State of Utopia"),
validFrom = validFrom,
validUntil = validUntil
)
.includeSubjectKeyIdentifier()
.setKeyUsage(setOf(X509KeyUsage.DIGITAL_SIGNATURE))
.build()
val mso = msoGenerator.generate()
val taggedEncodedMso = encode(Tagged(24, Bstr(mso)))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ fun ConsentModalBottomSheet(
ModalBottomSheet(
onDismissRequest = { onCancel() },
sheetState = sheetState,
dragHandle = null,
containerColor = MaterialTheme.colorScheme.surface
) {
Column(
Expand Down Expand Up @@ -172,7 +173,7 @@ private fun ButtonSection(
@Composable
private fun RelyingPartySection(relyingParty: ConsentRelyingParty) {
Column(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (relyingParty.trustPoint != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.android.identity.securearea.cloud

import com.android.identity.asn1.ASN1Integer
import com.android.identity.cbor.Cbor
import com.android.identity.cbor.CborArray
import com.android.identity.cbor.annotation.CborSerializable
Expand All @@ -8,10 +9,9 @@ import com.android.identity.crypto.Algorithm
import com.android.identity.crypto.Crypto
import com.android.identity.crypto.EcCurve
import com.android.identity.crypto.EcPrivateKey
import com.android.identity.crypto.X500Name
import com.android.identity.crypto.X509Cert
import com.android.identity.crypto.X509CertChain
import com.android.identity.crypto.X509CertificateExtension
import com.android.identity.crypto.create
import com.android.identity.crypto.javaX509Certificates
import com.android.identity.securearea.AttestationExtension
import com.android.identity.securearea.KeyPurpose
Expand Down Expand Up @@ -233,27 +233,26 @@ class CloudSecureAreaServer(
DateTimePeriod(years = 10),
TimeZone.currentSystemDefault()
)
val attestationExtension =
X509CertificateExtension(
AttestationExtension.ATTESTATION_OID,
false,
AttestationExtension.encode(request1.deviceChallenge)
)
val cloudBindingKeyAttestation = X509CertChain(
listOf(
X509Cert.create(
cloudBindingKey.publicKey,
cloudRootAttestationKey,
null,
cloudRootAttestationKeySignatureAlgorithm,
"1",
"CN=Cloud Secure Area Cloud Binding Key",
cloudRootAttestationKeyIssuer,
cloudBindingKeyValidFrom,
cloudBindingKeyValidUntil,
setOf(),
listOf(attestationExtension)
),
X509Cert.Builder(
publicKey = cloudBindingKey.publicKey,
signingKey = cloudRootAttestationKey,
signatureAlgorithm = cloudRootAttestationKeySignatureAlgorithm,
serialNumber = ASN1Integer(1L),
subject = X500Name.fromName("CN=Cloud Secure Area Cloud Binding Key"),
issuer = X500Name.fromName(cloudRootAttestationKeyIssuer),
validFrom = cloudBindingKeyValidFrom,
validUntil = cloudBindingKeyValidUntil
)
.includeSubjectKeyIdentifier()
.setAuthorityKeyIdentifierToCertificate(cloudRootAttestationKeyCertification.certificates[0])
.addExtension(
oid = AttestationExtension.ATTESTATION_OID,
critical = false,
value = AttestationExtension.encode(request1.deviceChallenge)
)
.build()
) + cloudRootAttestationKeyCertification.certificates
)
state.cloudBindingKey = cloudBindingKey.toCoseKey()
Expand Down Expand Up @@ -477,25 +476,24 @@ class CloudSecureAreaServer(

val keyInfo = secureArea.getKeyInfo("CloudKey")

val attestationCert = X509Cert.create(
keyInfo.publicKey,
attestationKey,
attestationKeyCertification.certificates[0],
attestationKeySignatureAlgorithm,
"1",
"CN=Cloud Secure Area Key",
attestationKeyIssuer,
Instant.fromEpochMilliseconds(state.validFromMillis),
Instant.fromEpochMilliseconds(state.validUntilMillis),
setOf(),
listOf(
X509CertificateExtension(
AttestationExtension.ATTESTATION_OID,
false,
AttestationExtension.encode(state.challenge!!)
)
val attestationCert = X509Cert.Builder(
publicKey = keyInfo.publicKey,
signingKey = attestationKey,
signatureAlgorithm = attestationKeySignatureAlgorithm,
serialNumber = ASN1Integer(1L),
subject = X500Name.fromName("CN=Cloud Secure Area Key"),
issuer = X500Name.fromName(attestationKeyIssuer),
validFrom = Instant.fromEpochMilliseconds(state.validFromMillis),
validUntil = Instant.fromEpochMilliseconds(state.validUntilMillis)
)
)
.includeSubjectKeyIdentifier()
.setAuthorityKeyIdentifierToCertificate(attestationKeyCertification.certificates[0])
.addExtension(
oid = AttestationExtension.ATTESTATION_OID,
critical = false,
value = AttestationExtension.encode(state.challenge!!)
)
.build()

state.cloudKeyStorage = storageEngine.toCbor()
val response1 = CreateKeyResponse1(
Expand Down
Loading

0 comments on commit dadbb95

Please sign in to comment.