Skip to content

Commit ce1206b

Browse files
committed
Start Differentiating Credential Types
Added structure for additional credential types by moving existing code to a credential package and creating abstract classes for Credential and SecureAreaBoundCredential, with MdocCredential as the first implementation. All tests pass + tested manually by provisioning and presenting in wallet and appholder. Signed-off-by: Suzanna Jiwani <suzannaj@google.com>
1 parent 99bebc7 commit ce1206b

File tree

34 files changed

+829
-513
lines changed

34 files changed

+829
-513
lines changed

appholder/src/main/java/com/android/identity/wallet/GetCredentialActivity.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import com.android.identity.android.mdoc.util.CredmanUtil
1212
import com.android.identity.android.mdoc.util.CredmanUtil.Companion.generateClientIdHash
1313
import com.android.identity.android.mdoc.util.CredmanUtil.Companion.generatePublicKeyHash
1414
import com.android.identity.android.securearea.AndroidKeystoreKeyUnlockData
15-
import com.android.identity.document.Credential
15+
import com.android.identity.credential.MdocCredential
1616
import com.android.identity.document.DocumentRequest
1717
import com.android.identity.document.NameSpacedData
1818
import com.android.identity.crypto.Algorithm
@@ -41,7 +41,7 @@ import java.util.StringTokenizer
4141
class GetCredentialActivity : FragmentActivity() {
4242

4343
fun addDeviceNamespaces(documentGenerator : DocumentGenerator,
44-
authKey : Credential,
44+
authKey : MdocCredential,
4545
unlockData: KeyUnlockData?) {
4646
documentGenerator.setDeviceNamespacesSignature(
4747
NameSpacedData.Builder().build(),
@@ -51,7 +51,7 @@ class GetCredentialActivity : FragmentActivity() {
5151
Algorithm.ES256)
5252
}
5353

54-
fun doBiometricAuth(authKey : Credential,
54+
fun doBiometricAuth(authKey : MdocCredential,
5555
forceLskf : Boolean,
5656
onBiometricAuthCompleted: (unlockData: KeyUnlockData?) -> Unit) {
5757
var title = "To share your credential we need to check that it's you."
@@ -130,7 +130,7 @@ class GetCredentialActivity : FragmentActivity() {
130130
val credential = document.findCredential(
131131
ProvisioningUtil.CREDENTIAL_DOMAIN,
132132
Timestamp.now()
133-
) ?: throw IllegalStateException("No credential")
133+
) as MdocCredential? ?: throw IllegalStateException("No credential")
134134
val staticAuthData = StaticAuthDataParser(credential.issuerProvidedData).parse()
135135
val mergedIssuerNamespaces = MdocUtil.mergeIssuerNamesSpaces(
136136
documentRequest, nameSpacedData, staticAuthData

appholder/src/main/java/com/android/identity/wallet/HolderApp.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import android.content.Context
55
import com.android.identity.android.securearea.AndroidKeystoreSecureArea
66
import com.android.identity.android.storage.AndroidStorageEngine
77
import com.android.identity.android.util.AndroidLogPrinter
8+
import com.android.identity.credential.CredentialFactory
9+
import com.android.identity.credential.MdocCredential
810
import com.android.identity.document.DocumentStore
911
import com.android.identity.documenttype.DocumentTypeRepository
1012
import com.android.identity.documenttype.knowntypes.DrivingLicense
@@ -85,7 +87,10 @@ class HolderApp: Application() {
8587

8688
secureAreaRepository.addImplementation(androidKeystoreSecureArea)
8789
secureAreaRepository.addImplementation(softwareSecureArea)
88-
return DocumentStore(storageEngine, secureAreaRepository)
90+
91+
var credentialFactory = CredentialFactory()
92+
credentialFactory.addCredentialImplementation(MdocCredential::class)
93+
return DocumentStore(storageEngine, secureAreaRepository, credentialFactory)
8994
}
9095
}
9196

appholder/src/main/java/com/android/identity/wallet/support/AndroidKeystoreSecureAreaSupport.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import com.android.identity.android.securearea.UserAuthenticationType
2121
import com.android.identity.android.securearea.userAuthenticationTypeSet
2222
import com.android.identity.cbor.Cbor
2323
import com.android.identity.cbor.CborMap
24-
import com.android.identity.document.Credential
24+
import com.android.identity.credential.MdocCredential
2525
import com.android.identity.crypto.Algorithm
2626
import com.android.identity.securearea.CreateKeySettings
2727
import com.android.identity.crypto.EcCurve
@@ -51,7 +51,7 @@ class AndroidKeystoreSecureAreaSupport(
5151
)
5252

5353
override fun Fragment.unlockKey(
54-
authKey: Credential,
54+
authKey: MdocCredential,
5555
onKeyUnlocked: (unlockData: KeyUnlockData?) -> Unit,
5656
onUnlockFailure: (wasCancelled: Boolean) -> Unit
5757
) {

appholder/src/main/java/com/android/identity/wallet/support/SecureAreaSupport.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import android.content.Context
44
import androidx.compose.runtime.Composable
55
import androidx.fragment.app.Fragment
66
import com.android.identity.android.securearea.AndroidKeystoreSecureArea
7-
import com.android.identity.document.Credential
7+
import com.android.identity.credential.MdocCredential
88
import com.android.identity.document.Document
99
import com.android.identity.securearea.CreateKeySettings
1010
import com.android.identity.securearea.KeyUnlockData
@@ -35,7 +35,7 @@ interface SecureAreaSupport {
3535
* there is a provided way to navigate using the [findNavController] function.
3636
*/
3737
fun Fragment.unlockKey(
38-
authKey: Credential,
38+
authKey: MdocCredential,
3939
onKeyUnlocked: (unlockData: KeyUnlockData?) -> Unit,
4040
onUnlockFailure: (wasCancelled: Boolean) -> Unit
4141
)

appholder/src/main/java/com/android/identity/wallet/support/SecureAreaSupportNull.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import androidx.compose.runtime.remember
1111
import androidx.compose.ui.Modifier
1212
import androidx.compose.ui.unit.dp
1313
import androidx.fragment.app.Fragment
14-
import com.android.identity.document.Credential
14+
import com.android.identity.credential.MdocCredential
1515
import com.android.identity.securearea.CreateKeySettings
1616
import com.android.identity.securearea.KeyUnlockData
1717
import com.android.identity.util.Timestamp
@@ -40,7 +40,7 @@ class SecureAreaSupportNull : SecureAreaSupport {
4040
}
4141

4242
override fun Fragment.unlockKey(
43-
authKey: Credential,
43+
authKey: MdocCredential,
4444
onKeyUnlocked: (unlockData: KeyUnlockData?) -> Unit,
4545
onUnlockFailure: (wasCancelled: Boolean) -> Unit
4646
) {

appholder/src/main/java/com/android/identity/wallet/support/SoftwareKeystoreSecureAreaSupport.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import androidx.lifecycle.repeatOnLifecycle
2020
import androidx.navigation.fragment.findNavController
2121
import co.nstant.`in`.cbor.CborBuilder
2222
import com.android.identity.cbor.Cbor
23-
import com.android.identity.document.Credential
23+
import com.android.identity.credential.MdocCredential
2424
import com.android.identity.crypto.Algorithm
2525
import com.android.identity.crypto.CertificateChain
2626
import com.android.identity.crypto.Crypto
@@ -56,7 +56,7 @@ class SoftwareKeystoreSecureAreaSupport : SecureAreaSupport {
5656
private val screenState = SoftwareKeystoreSecureAreaSupportState()
5757

5858
override fun Fragment.unlockKey(
59-
authKey: Credential,
59+
authKey: MdocCredential,
6060
onKeyUnlocked: (unlockData: KeyUnlockData?) -> Unit,
6161
onUnlockFailure: (wasCancelled: Boolean) -> Unit
6262
) {
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.android.identity.wallet.transfer
22

3-
import com.android.identity.document.Credential
3+
import com.android.identity.credential.MdocCredential
44

55
sealed class AddDocumentToResponseResult {
66

@@ -9,6 +9,6 @@ sealed class AddDocumentToResponseResult {
99
) : AddDocumentToResponseResult()
1010

1111
data class DocumentLocked(
12-
val authKey: Credential
12+
val authKey: MdocCredential
1313
) : AddDocumentToResponseResult()
1414
}

appholder/src/main/java/com/android/identity/wallet/transfer/TransferManager.kt

+7-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import android.view.View
99
import android.widget.ImageView
1010
import androidx.lifecycle.LiveData
1111
import androidx.lifecycle.MutableLiveData
12-
import com.android.identity.document.Credential
12+
import com.android.identity.credential.MdocCredential
1313
import com.android.identity.document.DocumentRequest
1414
import com.android.identity.document.NameSpacedData
1515
import com.android.identity.mdoc.mso.StaticAuthDataParser
@@ -163,7 +163,7 @@ class TransferManager private constructor(private val context: Context) {
163163
docType: String,
164164
issuerSignedEntriesToRequest: MutableMap<String, Collection<String>>,
165165
deviceResponseGenerator: DeviceResponseGenerator,
166-
authKey: Credential?,
166+
authKey: MdocCredential?,
167167
authKeyUnlockData: KeyUnlockData?,
168168
) = suspendCancellableCoroutine { continuation ->
169169
var result: AddDocumentToResponseResult
@@ -181,11 +181,14 @@ class TransferManager private constructor(private val context: Context) {
181181

182182
val request = DocumentRequest(dataElements)
183183

184-
val credentialToUse: Credential
184+
val credentialToUse: MdocCredential
185185
if (authKey != null) {
186186
credentialToUse = authKey
187187
} else {
188-
credentialToUse = document.findCredential(ProvisioningUtil.CREDENTIAL_DOMAIN, Timestamp.now())
188+
credentialToUse = document.findCredential(
189+
ProvisioningUtil.CREDENTIAL_DOMAIN,
190+
Timestamp.now()
191+
) as MdocCredential?
189192
?: throw IllegalStateException("No credential available")
190193
}
191194

appholder/src/main/java/com/android/identity/wallet/util/ProvisioningUtil.kt

+5-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.android.identity.cbor.Tagged
1414
import com.android.identity.cbor.toDataItem
1515
import com.android.identity.cose.Cose
1616
import com.android.identity.cose.CoseNumberLabel
17+
import com.android.identity.credential.MdocCredential
1718
import com.android.identity.document.Document
1819
import com.android.identity.document.DocumentUtil
1920
import com.android.identity.document.NameSpacedData
@@ -117,7 +118,7 @@ class ProvisioningUtil private constructor(
117118
validUntil
118119
)
119120

120-
val pendingCredsCount = DocumentUtil.managedCredentialHelper(
121+
val pendingCredsCount = DocumentUtil.managedMdocCredentialHelper(
121122
document,
122123
secureArea,
123124
settings,
@@ -132,7 +133,8 @@ class ProvisioningUtil private constructor(
132133
return
133134
}
134135

135-
for (pendingCred in document.pendingCredentials) {
136+
for (pendingCred in document.pendingCredentials.filter { it.domain == CREDENTIAL_DOMAIN }) {
137+
pendingCred as MdocCredential
136138
val msoGenerator = MobileSecurityObjectGenerator(
137139
"SHA-256",
138140
docType,
@@ -288,6 +290,7 @@ class ProvisioningUtil private constructor(
288290
?: throw IllegalStateException("No Secure Area with id ${authKeySecureAreaIdentifier} for document ${it.name}")
289291

290292
val credentials = certifiedCredentials.map { key ->
293+
key as MdocCredential
291294
val info = authKeySecureArea.getKeyInfo(key.alias)
292295
DocumentInformation.KeyData(
293296
counter = key.credentialCounter.toInt(),

appholder/src/main/java/com/android/identity/wallet/viewmodel/TransferDocumentViewModel.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import androidx.lifecycle.AndroidViewModel
88
import androidx.lifecycle.LiveData
99
import androidx.lifecycle.MutableLiveData
1010
import androidx.lifecycle.viewModelScope
11-
import com.android.identity.document.Credential
11+
import com.android.identity.credential.MdocCredential
1212
import com.android.identity.mdoc.request.DeviceRequestParser
1313
import com.android.identity.mdoc.response.DeviceResponseGenerator
1414
import com.android.identity.securearea.KeyUnlockData
@@ -102,7 +102,7 @@ class TransferDocumentViewModel(val app: Application) : AndroidViewModel(app) {
102102

103103
fun sendResponseForSelection(
104104
onResultReady: (result: AddDocumentToResponseResult) -> Unit,
105-
authKey: Credential? = null,
105+
authKey: MdocCredential? = null,
106106
authKeyUnlockData: KeyUnlockData? = null
107107
) {
108108
val elementsToSend = signedElements.collect()

gradle/libs.versions.toml

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[versions]
22
compile-sdk = "34"
3+
kotlin-reflect = "1.9.23"
34
min-sdk = "26"
45
kotlin = "1.8.20"
56
gradle-plugin = "7.4.0"
@@ -67,6 +68,8 @@
6768
androidx-navigation-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
6869
androidx-navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" }
6970

71+
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" }
72+
7073
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref="kotlinx-coroutines" }
7174
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref="kotlinx-coroutines" }
7275
kotlinx-coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref="kotlinx-coroutines" }

identity-android/src/androidTest/java/com/android/identity/android/document/AndroidKeystoreSecureAreaDocumentStoreTest.kt

+9-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import com.android.identity.android.securearea.AndroidKeystoreCreateKeySettings
2121
import com.android.identity.android.securearea.AndroidKeystoreSecureArea
2222
import com.android.identity.android.securearea.UserAuthenticationType
2323
import com.android.identity.android.storage.AndroidStorageEngine
24+
import com.android.identity.credential.CredentialFactory
25+
import com.android.identity.credential.MdocCredential
2426
import com.android.identity.document.Document
2527
import com.android.identity.document.DocumentStore
2628
import com.android.identity.crypto.javaX509Certificate
@@ -43,6 +45,7 @@ class AndroidKeystoreSecureAreaDocumentStoreTest {
4345
private lateinit var storageEngine: StorageEngine
4446
private lateinit var secureArea: SecureArea
4547
private lateinit var secureAreaRepository: SecureAreaRepository
48+
private lateinit var credentialFactory: CredentialFactory
4649

4750
@Before
4851
fun setup() {
@@ -52,11 +55,13 @@ class AndroidKeystoreSecureAreaDocumentStoreTest {
5255
secureAreaRepository = SecureAreaRepository()
5356
secureArea = AndroidKeystoreSecureArea(context, storageEngine)
5457
secureAreaRepository.addImplementation(secureArea)
58+
credentialFactory = CredentialFactory()
59+
credentialFactory.addCredentialImplementation(MdocCredential::class)
5560
}
5661

5762
@Test
5863
fun testBasic() {
59-
val documentStore = DocumentStore(storageEngine, secureAreaRepository)
64+
val documentStore = DocumentStore(storageEngine, secureAreaRepository, credentialFactory)
6065
var document: Document? = documentStore.createDocument(
6166
"testDocument"
6267
)
@@ -65,7 +70,7 @@ class AndroidKeystoreSecureAreaDocumentStoreTest {
6570

6671
// Create pending credential and check its attestation
6772
val authKeyChallenge = byteArrayOf(20, 21, 22)
68-
val pendingCredential = document.createCredential(
73+
val pendingCredential = MdocCredential.create(
6974
CREDENTIAL_DOMAIN,
7075
secureArea,
7176
AndroidKeystoreCreateKeySettings.Builder(authKeyChallenge)
@@ -74,7 +79,8 @@ class AndroidKeystoreSecureAreaDocumentStoreTest {
7479
setOf(UserAuthenticationType.LSKF, UserAuthenticationType.BIOMETRIC)
7580
)
7681
.build(),
77-
null
82+
null,
83+
document
7884
)
7985
Assert.assertFalse(pendingCredential.isCertified)
8086
val parser =

identity-android/src/androidTest/java/com/android/identity/android/mdoc/deviceretrieval/DeviceRetrievalHelperTest.kt

+17-11
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ import com.android.identity.cose.Cose
3232
import com.android.identity.cose.Cose.coseSign1Sign
3333
import com.android.identity.cose.CoseLabel
3434
import com.android.identity.cose.CoseNumberLabel
35-
import com.android.identity.document.Credential
35+
import com.android.identity.credential.CredentialFactory
36+
import com.android.identity.credential.MdocCredential
37+
import com.android.identity.credential.SecureAreaBoundCredential
3638
import com.android.identity.document.Document
3739
import com.android.identity.document.DocumentStore
3840
import com.android.identity.document.NameSpacedData
@@ -96,7 +98,7 @@ class DeviceRetrievalHelperTest {
9698
private lateinit var mSecureArea: SecureArea
9799
private lateinit var mSecureAreaRepository: SecureAreaRepository
98100
private lateinit var mDocument: Document
99-
private lateinit var mCredential: Credential
101+
private lateinit var mMdocCredential: SecureAreaBoundCredential
100102
private lateinit var mTimeSigned: Timestamp
101103
private lateinit var mTimeValidityBegin: Timestamp
102104
private lateinit var mTimeValidityEnd: Timestamp
@@ -114,9 +116,12 @@ class DeviceRetrievalHelperTest {
114116
mSecureAreaRepository = SecureAreaRepository()
115117
mSecureArea = SoftwareSecureArea(mStorageEngine)
116118
mSecureAreaRepository.addImplementation(mSecureArea)
119+
var credentialFactory = CredentialFactory()
120+
credentialFactory.addCredentialImplementation(MdocCredential::class)
117121
val documentStore = DocumentStore(
118122
mStorageEngine,
119-
mSecureAreaRepository
123+
mSecureAreaRepository,
124+
credentialFactory
120125
)
121126

122127
// Create the document...
@@ -135,21 +140,22 @@ class DeviceRetrievalHelperTest {
135140
mTimeSigned = ofEpochMilli(nowMillis)
136141
mTimeValidityBegin = ofEpochMilli(nowMillis + 3600 * 1000)
137142
mTimeValidityEnd = ofEpochMilli(nowMillis + 10 * 86400 * 1000)
138-
mCredential = mDocument.createCredential(
143+
mMdocCredential = MdocCredential.create(
139144
CREDENTIAL_DOMAIN,
140145
mSecureArea,
141146
SoftwareCreateKeySettings.Builder(ByteArray(0))
142147
.setKeyPurposes(setOf(KeyPurpose.SIGN, KeyPurpose.AGREE_KEY))
143148
.build(),
144-
null
149+
null,
150+
mDocument
145151
)
146-
Assert.assertFalse(mCredential.isCertified)
152+
Assert.assertFalse(mMdocCredential.isCertified)
147153

148154
// Generate an MSO and issuer-signed data for this credential.
149155
val msoGenerator = MobileSecurityObjectGenerator(
150156
"SHA-256",
151157
MDL_DOCTYPE,
152-
mCredential.attestation.certificates[0].publicKey
158+
mMdocCredential.attestation.certificates[0].publicKey
153159
)
154160
msoGenerator.setValidityInfo(mTimeSigned, mTimeValidityBegin, mTimeValidityEnd, null)
155161
val issuerNameSpaces = generateIssuerNameSpaces(
@@ -213,7 +219,7 @@ class DeviceRetrievalHelperTest {
213219
).generate()
214220

215221
// Now that we have issuer-provided authentication data we certify the credential.
216-
mCredential.certify(
222+
mMdocCredential.certify(
217223
issuerProvidedAuthenticationData,
218224
mTimeValidityBegin,
219225
mTimeValidityEnd
@@ -377,7 +383,7 @@ class DeviceRetrievalHelperTest {
377383
val generator = DeviceResponseGenerator(
378384
Constants.DEVICE_RESPONSE_STATUS_OK
379385
)
380-
val staticAuthData = StaticAuthDataParser(mCredential.issuerProvidedData)
386+
val staticAuthData = StaticAuthDataParser(mMdocCredential.issuerProvidedData)
381387
.parse()
382388
val deviceSignedData = NameSpacedData.Builder().build()
383389
val mergedIssuerNamespaces: Map<String, List<ByteArray>> =
@@ -395,8 +401,8 @@ class DeviceRetrievalHelperTest {
395401
.setIssuerNamespaces(mergedIssuerNamespaces)
396402
.setDeviceNamespacesSignature(
397403
deviceSignedData,
398-
mCredential.secureArea,
399-
mCredential.alias,
404+
mMdocCredential.secureArea,
405+
mMdocCredential.alias,
400406
null,
401407
Algorithm.ES256
402408
)

0 commit comments

Comments
 (0)