Skip to content

Commit 7d5facb

Browse files
committed
Start Differentiating Credential Types
Added structure for additional credential types by moving existing code to a credential package and creating classes for Credential, SecureAreaBoundCredential, and MdocCredential. 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 7d5facb

File tree

35 files changed

+1016
-632
lines changed

35 files changed

+1016
-632
lines changed

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

+8-10
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ 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
1615
import com.android.identity.document.DocumentRequest
1716
import com.android.identity.document.NameSpacedData
1817
import com.android.identity.crypto.Algorithm
18+
import com.android.identity.mdoc.credential.MdocCredential
1919
import com.android.identity.mdoc.mso.StaticAuthDataParser
2020
import com.android.identity.mdoc.response.DeviceResponseGenerator
2121
import com.android.identity.mdoc.response.DocumentGenerator
@@ -30,32 +30,30 @@ import com.android.identity.wallet.util.log
3030
import com.google.android.gms.identitycredentials.GetCredentialResponse
3131
import com.google.android.gms.identitycredentials.IntentHelper
3232
import com.google.android.gms.identitycredentials.IntentHelper.EXTRA_CREDENTIAL_ID
33-
import com.google.android.gms.identitycredentials.IntentHelper.extractCallingAppInfo
3433
import com.google.android.gms.identitycredentials.IntentHelper.extractGetCredentialRequest
3534
import com.google.android.gms.identitycredentials.IntentHelper.setGetCredentialException
3635
import com.google.android.gms.identitycredentials.IntentHelper.setGetCredentialResponse
3736
import org.json.JSONObject
38-
import java.security.PublicKey
3937
import java.util.StringTokenizer
4038

4139
class GetCredentialActivity : FragmentActivity() {
4240

4341
fun addDeviceNamespaces(documentGenerator : DocumentGenerator,
44-
authKey : Credential,
42+
credential : MdocCredential,
4543
unlockData: KeyUnlockData?) {
4644
documentGenerator.setDeviceNamespacesSignature(
4745
NameSpacedData.Builder().build(),
48-
authKey.secureArea,
49-
authKey.alias,
46+
credential.secureArea,
47+
credential.alias,
5048
unlockData,
5149
Algorithm.ES256)
5250
}
5351

54-
fun doBiometricAuth(authKey : Credential,
52+
fun doBiometricAuth(credential : MdocCredential,
5553
forceLskf : Boolean,
5654
onBiometricAuthCompleted: (unlockData: KeyUnlockData?) -> Unit) {
5755
var title = "To share your credential we need to check that it's you."
58-
var unlockData = AndroidKeystoreKeyUnlockData(authKey.alias)
56+
var unlockData = AndroidKeystoreKeyUnlockData(credential.alias)
5957
var cryptoObject = unlockData.getCryptoObjectForSigning(Algorithm.ES256)
6058

6159
val promptInfoBuilder = BiometricPrompt.PromptInfo.Builder()
@@ -89,7 +87,7 @@ class GetCredentialActivity : FragmentActivity() {
8987
Logger.d("TAG", "onAuthenticationError $errorCode $errString")
9088
// TODO: "Use LSKF"... without this delay, the prompt won't work correctly
9189
Handler(Looper.getMainLooper()).postDelayed({
92-
doBiometricAuth(authKey, true, onBiometricAuthCompleted)
90+
doBiometricAuth(credential, true, onBiometricAuthCompleted)
9391
}, 100)
9492
}
9593
}
@@ -130,7 +128,7 @@ class GetCredentialActivity : FragmentActivity() {
130128
val credential = document.findCredential(
131129
ProvisioningUtil.CREDENTIAL_DOMAIN,
132130
Timestamp.now()
133-
) ?: throw IllegalStateException("No credential")
131+
) as MdocCredential? ?: throw IllegalStateException("No credential")
134132
val staticAuthData = StaticAuthDataParser(credential.issuerProvidedData).parse()
135133
val mergedIssuerNamespaces = MdocUtil.mergeIssuerNamesSpaces(
136134
documentRequest, nameSpacedData, staticAuthData

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ 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
89
import com.android.identity.document.DocumentStore
910
import com.android.identity.documenttype.DocumentTypeRepository
1011
import com.android.identity.documenttype.knowntypes.DrivingLicense
1112
import com.android.identity.documenttype.knowntypes.EUPersonalID
1213
import com.android.identity.documenttype.knowntypes.VaccinationDocument
1314
import com.android.identity.documenttype.knowntypes.VehicleRegistration
15+
import com.android.identity.mdoc.credential.MdocCredential
1416
import com.android.identity.securearea.SecureAreaRepository
1517
import com.android.identity.securearea.software.SoftwareSecureArea
1618
import com.android.identity.storage.GenericStorageEngine
@@ -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/authconfirmation/AuthConfirmationFragment.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -120,17 +120,17 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() {
120120

121121
val secureAreaSupport = SecureAreaSupport.getInstance(
122122
requireContext(),
123-
result.authKey.secureArea
123+
result.credential.secureArea
124124
)
125125
with(secureAreaSupport) {
126126
unlockKey(
127-
authKey = result.authKey,
127+
credential = result.credential,
128128
onKeyUnlocked = { keyUnlockData ->
129129
viewModel.sendResponseForSelection(
130130
onResultReady = {
131131
onSendResponseResult(it)
132132
},
133-
result.authKey,
133+
result.credential,
134134
keyUnlockData
135135
)
136136
},

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ 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
2524
import com.android.identity.crypto.Algorithm
2625
import com.android.identity.securearea.CreateKeySettings
2726
import com.android.identity.crypto.EcCurve
27+
import com.android.identity.mdoc.credential.MdocCredential
2828
import com.android.identity.securearea.KeyPurpose
2929
import com.android.identity.securearea.KeyUnlockData
3030
import com.android.identity.util.Timestamp
@@ -51,12 +51,12 @@ class AndroidKeystoreSecureAreaSupport(
5151
)
5252

5353
override fun Fragment.unlockKey(
54-
authKey: Credential,
54+
credential: MdocCredential,
5555
onKeyUnlocked: (unlockData: KeyUnlockData?) -> Unit,
5656
onUnlockFailure: (wasCancelled: Boolean) -> Unit
5757
) {
58-
val keyInfo = authKey.secureArea.getKeyInfo(authKey.alias) as AndroidKeystoreKeyInfo
59-
val unlockData = AndroidKeystoreKeyUnlockData(authKey.alias)
58+
val keyInfo = credential.secureArea.getKeyInfo(credential.alias) as AndroidKeystoreKeyInfo
59+
val unlockData = AndroidKeystoreKeyUnlockData(credential.alias)
6060

6161
val allowLskf = keyInfo.userAuthenticationTypes.contains(UserAuthenticationType.LSKF)
6262
val allowBiometric = keyInfo.userAuthenticationTypes.contains(UserAuthenticationType.BIOMETRIC)
@@ -74,7 +74,7 @@ class AndroidKeystoreSecureAreaSupport(
7474
.withCancelledCallback {
7575
if (allowLskfUnlock) {
7676
val runnable = {
77-
unlockKey(authKey, onKeyUnlocked, onUnlockFailure)
77+
unlockKey(credential, onKeyUnlocked, onUnlockFailure)
7878
}
7979
// Without this delay, the prompt won't reshow
8080
Handler(Looper.getMainLooper()).postDelayed(runnable, 100)

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ 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
87
import com.android.identity.document.Document
8+
import com.android.identity.mdoc.credential.MdocCredential
99
import com.android.identity.securearea.CreateKeySettings
1010
import com.android.identity.securearea.KeyUnlockData
1111
import com.android.identity.securearea.SecureArea
@@ -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+
credential: 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.mdoc.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+
credential: 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,13 +20,13 @@ 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
2423
import com.android.identity.crypto.Algorithm
2524
import com.android.identity.crypto.CertificateChain
2625
import com.android.identity.crypto.Crypto
2726
import com.android.identity.securearea.CreateKeySettings
2827
import com.android.identity.crypto.EcCurve
2928
import com.android.identity.crypto.EcPrivateKey
29+
import com.android.identity.mdoc.credential.MdocCredential
3030
import com.android.identity.securearea.KeyPurpose
3131
import com.android.identity.securearea.KeyUnlockData
3232
import com.android.identity.securearea.software.SoftwareCreateKeySettings
@@ -56,7 +56,7 @@ class SoftwareKeystoreSecureAreaSupport : SecureAreaSupport {
5656
private val screenState = SoftwareKeystoreSecureAreaSupportState()
5757

5858
override fun Fragment.unlockKey(
59-
authKey: Credential,
59+
credential: 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.mdoc.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 credential: MdocCredential
1313
) : AddDocumentToResponseResult()
1414
}

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

+9-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ 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
1312
import com.android.identity.document.DocumentRequest
1413
import com.android.identity.document.NameSpacedData
1514
import com.android.identity.mdoc.mso.StaticAuthDataParser
@@ -19,6 +18,7 @@ import com.android.identity.mdoc.response.DeviceResponseGenerator
1918
import com.android.identity.mdoc.response.DocumentGenerator
2019
import com.android.identity.mdoc.util.MdocUtil
2120
import com.android.identity.crypto.Algorithm
21+
import com.android.identity.mdoc.credential.MdocCredential
2222
import com.android.identity.securearea.KeyLockedException
2323
import com.android.identity.securearea.KeyPurpose
2424
import com.android.identity.securearea.KeyUnlockData
@@ -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+
credential: 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
185-
if (authKey != null) {
186-
credentialToUse = authKey
184+
val credentialToUse: MdocCredential
185+
if (credential != null) {
186+
credentialToUse = credential
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

+11-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.android.identity.crypto.Certificate
2222
import com.android.identity.crypto.CertificateChain
2323
import com.android.identity.crypto.EcCurve
2424
import com.android.identity.crypto.toEcPrivateKey
25+
import com.android.identity.mdoc.credential.MdocCredential
2526
import com.android.identity.mdoc.mso.MobileSecurityObjectGenerator
2627
import com.android.identity.mdoc.mso.StaticAuthDataGenerator
2728
import com.android.identity.mdoc.util.MdocUtil
@@ -119,9 +120,14 @@ class ProvisioningUtil private constructor(
119120

120121
val pendingCredsCount = DocumentUtil.managedCredentialHelper(
121122
document,
122-
secureArea,
123-
settings,
124123
CREDENTIAL_DOMAIN,
124+
{toBeReplaced -> MdocCredential(
125+
toBeReplaced,
126+
CREDENTIAL_DOMAIN,
127+
secureArea,
128+
settings,
129+
docType
130+
)},
125131
now,
126132
numCreds.toInt(),
127133
maxUsagesPerCred.toInt(),
@@ -132,7 +138,8 @@ class ProvisioningUtil private constructor(
132138
return
133139
}
134140

135-
for (pendingCred in document.pendingCredentials) {
141+
for (pendingCred in document.pendingCredentials.filter { it.domain == CREDENTIAL_DOMAIN }) {
142+
pendingCred as MdocCredential
136143
val msoGenerator = MobileSecurityObjectGenerator(
137144
"SHA-256",
138145
docType,
@@ -288,6 +295,7 @@ class ProvisioningUtil private constructor(
288295
?: throw IllegalStateException("No Secure Area with id ${authKeySecureAreaIdentifier} for document ${it.name}")
289296

290297
val credentials = certifiedCredentials.map { key ->
298+
key as MdocCredential
291299
val info = authKeySecureArea.getKeyInfo(key.alias)
292300
DocumentInformation.KeyData(
293301
counter = key.credentialCounter.toInt(),

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

+3-3
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.mdoc.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+
credential: MdocCredential? = null,
106106
authKeyUnlockData: KeyUnlockData? = null
107107
) {
108108
val elementsToSend = signedElements.collect()
@@ -117,7 +117,7 @@ class TransferDocumentViewModel(val app: Application) : AndroidViewModel(app) {
117117
signedDocument.documentType,
118118
issuerSignedEntries,
119119
responseGenerator,
120-
authKey,
120+
credential,
121121
authKeyUnlockData,
122122
)
123123
}

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" }

0 commit comments

Comments
 (0)