Skip to content

Commit fd19fe0

Browse files
committed
Integrating Direct Access Credentials into Wallets
Added provisioning steps in appholder and identity-issuance to support certifying DirectAccessCredentials as well as dynamic registration in appholder and wallet to switch AID registration from the app to the SE when the phone is powered off.
1 parent 40a3335 commit fd19fe0

File tree

42 files changed

+869
-99
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+869
-99
lines changed

appholder/src/main/AndroidManifest.xml

+15-7
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<uses-feature android:name="android.hardware.camera"
2727
android:required="true" />
2828

29+
<uses-permission android:name="Manifest.permission.RECEIVE_BOOT_COMPLETED" />
2930
<uses-permission android:name="android.permission.CAMERA" />
3031
<uses-permission android:name="android.permission.INTERNET" />
3132
<!-- As of API 31 BLE doesn't need location permission, but WiFi aware still requires it. -->
@@ -92,8 +93,16 @@
9293
</intent-filter>
9394
</activity>
9495

96+
<receiver
97+
android:name=".dynamicregistration.PowerOnReceiver"
98+
android:enabled="true"
99+
android:exported="true">
100+
<intent-filter>
101+
<action android:name="android.intent.action.BOOT_COMPLETED" />
102+
</intent-filter>
103+
</receiver>
95104

96-
<service
105+
<service
97106
android:name=".util.NfcEngagementHandler"
98107
android:exported="true"
99108
android:label="@string/nfc_engagement_service_desc"
@@ -109,18 +118,17 @@
109118
</service>
110119

111120
<service
112-
android:name=".util.NfcDataTransferHandler"
121+
android:name=".dynamicregistration.OffHostNfcDataTransferHandler"
113122
android:exported="true"
114-
android:label="@string/nfc_data_transfer_service_desc"
123+
android:label="@string/nfc_engagement_service_desc"
115124
android:permission="android.permission.BIND_NFC_SERVICE">
116125
<intent-filter>
117-
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
118-
<action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
126+
<action android:name="android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE" />
119127
</intent-filter>
120128

121129
<meta-data
122-
android:name="android.nfc.cardemulation.host_apdu_service"
123-
android:resource="@xml/nfc_data_transfer_apdu_service" />
130+
android:name="android.nfc.cardemulation.off_host_apdu_service"
131+
android:resource="@xml/off_host_nfc_engagement_apdu_service" />
124132
</service>
125133

126134
<provider

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class GetCredentialActivity : FragmentActivity() {
126126
val nameSpacedData = document!!.applicationData.getNameSpacedData("documentData")
127127

128128
val credential = document.findCredential(
129-
ProvisioningUtil.CREDENTIAL_DOMAIN,
129+
ProvisioningUtil.MDOC_CREDENTIAL_DOMAIN,
130130
Clock.System.now()
131131
) as MdocCredential? ?: throw IllegalStateException("No credential")
132132
val staticAuthData = StaticAuthDataParser(credential.issuerProvidedData).parse()

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

+31-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package com.android.identity.wallet
22

33
import android.app.Application
44
import android.content.Context
5+
import android.content.Intent
6+
import android.content.IntentFilter
7+
import com.android.identity.android.direct_access.DirectAccess
8+
import com.android.identity.android.direct_access.DirectAccessCredential
59
import com.android.identity.android.securearea.AndroidKeystoreSecureArea
610
import com.android.identity.android.storage.AndroidStorageEngine
711
import com.android.identity.android.util.AndroidLogPrinter
@@ -22,17 +26,19 @@ import com.android.identity.trustmanagement.TrustManager
2226
import com.android.identity.trustmanagement.TrustPoint
2327
import com.android.identity.util.Logger
2428
import com.android.identity.wallet.document.KeysAndCertificates
29+
import com.android.identity.wallet.dynamicregistration.PowerOffReceiver
2530
import com.android.identity.wallet.util.PeriodicKeysRefreshWorkRequest
2631
import com.android.identity.wallet.util.PreferencesHelper
2732
import com.google.android.material.color.DynamicColors
28-
import kotlinx.io.files.Path
2933
import org.bouncycastle.jce.provider.BouncyCastleProvider
3034
import java.io.ByteArrayInputStream
3135
import java.security.Security
3236
import java.security.cert.CertificateFactory
3337
import java.security.cert.X509Certificate
38+
import kotlinx.io.files.Path
3439

3540
class HolderApp: Application() {
41+
private lateinit var powerOffReceiver: PowerOffReceiver
3642

3743
private val documentTypeRepository by lazy {
3844
DocumentTypeRepository()
@@ -70,17 +76,32 @@ class HolderApp: Application() {
7076
KeysAndCertificates.getTrustedReaderCertificates(this).forEach {
7177
trustManagerInstance.addTrustPoint(TrustPoint(X509Cert(it.encoded)))
7278
}
79+
powerOffReceiver = PowerOffReceiver()
80+
registerReceiver(powerOffReceiver, IntentFilter(Intent.ACTION_SHUTDOWN))
81+
}
82+
83+
override fun onTerminate() {
84+
super.onTerminate()
85+
unregisterReceiver(powerOffReceiver)
7386
}
7487

7588
companion object {
7689

7790
lateinit var documentTypeRepositoryInstance: DocumentTypeRepository
7891
lateinit var trustManagerInstance: TrustManager
7992
lateinit var certificateStorageEngineInstance: StorageEngine
93+
// Use lazy access to prevent delays at app startup when DirectAccessOmapiTransport needs to
94+
// wait for connection to SE.
95+
val isDirectAccessSupported by lazy {
96+
DirectAccess.isDirectAccessSupported
97+
}
98+
8099
fun createDocumentStore(
81100
context: Context,
82-
secureAreaRepository: SecureAreaRepository
101+
secureAreaRepository: SecureAreaRepository,
83102
): DocumentStore {
103+
DirectAccess.warmupTransport()
104+
84105
val storageFile = Path(PreferencesHelper.getKeystoreBackedStorageLocation(context).path)
85106
val storageEngine = AndroidStorageEngine.Builder(context, storageFile).build()
86107

@@ -90,11 +111,17 @@ class HolderApp: Application() {
90111
secureAreaRepository.addImplementation(androidKeystoreSecureArea)
91112
secureAreaRepository.addImplementation(softwareSecureArea)
92113

93-
var credentialFactory = CredentialFactory()
114+
val credentialFactory = CredentialFactory()
94115
credentialFactory.addCredentialImplementation(MdocCredential::class) {
95116
document, dataItem -> MdocCredential(document, dataItem)
96117
}
97-
return DocumentStore(storageEngine, secureAreaRepository, credentialFactory)
118+
credentialFactory.addCredentialImplementation(DirectAccessCredential::class) {
119+
document, dataItem -> DirectAccessCredential(document, dataItem)
120+
}
121+
return DocumentStore(
122+
storageEngine,
123+
secureAreaRepository,
124+
credentialFactory)
98125
}
99126
}
100127

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

+7
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ import androidx.navigation.findNavController
1313
import androidx.navigation.ui.NavigationUI
1414
import androidx.navigation.ui.NavigationUI.setupActionBarWithNavController
1515
import androidx.navigation.ui.setupWithNavController
16+
import com.android.identity.android.direct_access.DirectAccess
1617
import com.android.identity.mdoc.origininfo.OriginInfo
1718
import com.android.identity.mdoc.origininfo.OriginInfoDomain
19+
import com.android.identity.util.AndroidInitializer
1820
import com.android.identity.util.Logger
1921
import com.android.identity.wallet.databinding.ActivityMainBinding
2022
import com.android.identity.wallet.util.PreferencesHelper
2123
import com.android.identity.wallet.document.DocumentManager
24+
import com.android.identity.wallet.dynamicregistration.AidRegistrationUtil
2225
import com.android.identity.wallet.util.log
2326
import com.android.identity.wallet.util.logError
2427
import com.android.identity.wallet.util.logInfo
@@ -39,6 +42,8 @@ class MainActivity : AppCompatActivity() {
3942

4043
override fun onCreate(savedInstanceState: Bundle?) {
4144
super.onCreate(savedInstanceState)
45+
AndroidInitializer.initialize(applicationContext)
46+
DirectAccess.warmupTransport()
4247
val color = SurfaceColors.SURFACE_2.getColor(this)
4348
window.statusBarColor = color
4449
window.navigationBarColor = color
@@ -49,6 +54,8 @@ class MainActivity : AppCompatActivity() {
4954
setupNfc()
5055
onNewIntent(intent)
5156
Logger.isDebugEnabled = PreferencesHelper.isDebugLoggingEnabled()
57+
// route aids to host by default
58+
AidRegistrationUtil.routeAidsToHost(this)
5259
}
5360

5461
private fun setupNfc() {

appholder/src/main/java/com/android/identity/wallet/document/DocumentInformation.kt

+13-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ data class DocumentInformation(
1212
val documentColor: Int,
1313
val maxUsagesPerKey: Int,
1414
val lastTimeUsed: String,
15-
val authKeys: List<KeyData>
15+
val authKeys: List<KeyData>,
16+
val daCreds: List<DirectAccessCredInfo> // used in document info screen to show da cred
1617
) {
1718

1819
data class KeyData(
@@ -27,5 +28,16 @@ data class DocumentInformation(
2728
val isHardwareBacked: Boolean,
2829
val secureAreaDisplayName: String
2930
)
31+
32+
// maps to DocumentInfoScreenState.DaKeyInformation
33+
data class DirectAccessCredInfo(
34+
val counter: Int,
35+
val validFrom: String,
36+
val validUntil: String,
37+
val domain: String,
38+
val issuerDataBytesCount: Int,
39+
val usagesCount: Int,
40+
val secureAreaDisplayName: String
41+
)
3042
}
3143

appholder/src/main/java/com/android/identity/wallet/document/DocumentManager.kt

+26-6
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@ import android.graphics.BitmapFactory
77
import co.nstant.`in`.cbor.CborBuilder
88
import co.nstant.`in`.cbor.model.DataItem
99
import co.nstant.`in`.cbor.model.UnicodeString
10+
import com.android.identity.android.direct_access.DirectAccess
11+
import com.android.identity.android.direct_access.DirectAccessCredential
1012
import com.android.identity.cbor.Cbor
1113
import com.android.identity.document.Document
1214
import com.android.identity.document.NameSpacedData
1315
import com.android.identity.documenttype.DocumentAttributeType
16+
import com.android.identity.wallet.HolderApp
17+
import com.android.identity.wallet.R
1418
import com.android.identity.wallet.selfsigned.SelfSignedDocumentData
1519
import com.android.identity.wallet.util.Field
1620
import com.android.identity.wallet.util.FormatUtil
17-
import com.android.identity.wallet.HolderApp
1821
import com.android.identity.wallet.util.ProvisioningUtil
1922
import com.android.identity.wallet.util.ProvisioningUtil.Companion.toDocumentInformation
2023
import com.android.identity.wallet.util.log
2124
import com.android.identity.wallet.util.logError
22-
import com.android.identity.wallet.R
2325
import com.android.mdl.app.credman.IdentityCredentialEntry
2426
import com.android.mdl.app.credman.IdentityCredentialField
2527
import com.android.mdl.app.credman.IdentityCredentialRegistry
@@ -30,6 +32,7 @@ import java.util.Locale
3032
class DocumentManager private constructor(private val context: Context) {
3133
val client = IdentityCredentialManager.Companion.getClient(context)
3234
companion object {
35+
private const val TAG = "DocumentManager"
3336

3437
@SuppressLint("StaticFieldLeak")
3538
@Volatile
@@ -133,12 +136,28 @@ class DocumentManager private constructor(private val context: Context) {
133136
}
134137

135138

136-
fun deleteCredentialByName(documentName: String) {
137-
val document = getDocumentInformation(documentName)
138-
document?.let {
139+
fun deleteDocumentByName(documentName: String) {
140+
val documentInformation = getDocumentInformation(documentName)
141+
var documentHasDirectAccessCreds = false
142+
var documentSlot = 0
143+
documentInformation?.let {
139144
val documentStore = ProvisioningUtil.getInstance(context).documentStore
145+
documentStore.lookupDocument(documentName)?.let {
146+
val certifiedCredentials = it.certifiedCredentials
147+
val pendingCredentials = it.pendingCredentials
148+
for (credential in (pendingCredentials + certifiedCredentials)) {
149+
if (credential is DirectAccessCredential) {
150+
documentHasDirectAccessCreds = true
151+
documentSlot = credential.documentSlot
152+
break
153+
}
154+
}
155+
}
140156
documentStore.deleteDocument(documentName)
141157
}
158+
if (documentHasDirectAccessCreds) {
159+
DirectAccess.clearDocumentSlot(documentSlot)
160+
}
142161
registerDocuments()
143162
}
144163

@@ -345,6 +364,7 @@ class DocumentManager private constructor(private val context: Context) {
345364
fun refreshCredentials(documentName: String) {
346365
val documentInformation = requireNotNull(getDocumentInformation(documentName))
347366
val document = requireNotNull(getDocumentByName(documentName))
348-
ProvisioningUtil.getInstance(context).refreshCredentials(document, documentInformation.docType)
367+
ProvisioningUtil.getInstance(context).refreshMdocCredentials(document, documentInformation.docType)
368+
ProvisioningUtil.getInstance(context).refreshDaCredentials(document, documentInformation.docType, 0)
349369
}
350370
}

0 commit comments

Comments
 (0)