The Wallet Core Library supports custom key management through the implementation of SecureArea interfaces. This document details the process of integrating custom key management with the library.
To enable custom key management, you must implement the following interfaces and classes:
- The
com.android.identity.securearea.SecureArea
interface abstracts the underlying secure area responsible for creating key material and other security-related objects used in identity documents. Authentication may be required to access a key, which is managed through thecom.android.identity.securearea.KeyLockedException
andcom.android.identity.securearea.KeyUnlockData
types. Applications must implement SecureArea-specific mechanisms to handle the necessary authentication. - The
com.android.identity.securearea.CreateKeySettings
class serves as the base for key creation settings. It is compatible with any SecureArea implementation, though most implementations will provide their own versions with additional, specific settings. - The
com.android.identity.securearea.KeyUnlockData
interface provides the information necessary to operate on a key that requires unlocking. - The
com.android.identity.securearea.KeyInfo
class contains details about a key. Specific SecureArea implementations may extend this class to include additional implementation-specific key information.
Using a custom SecureArea implementation involves focusing on three key areas:
- Configuring the wallet instance to integrate the custom SecureArea implementation.
- Managing events during the issuance of documents with OpenId4VCI, where key creation and unlocking are necessary.
- Managing events during document presentation with OpenId4VP or proximity presentation via BLE, where key unlocking is needed.
For the purposes of this document and the examples provided, we will refer to the following custom SecureArea implementations:
MySecureArea
class implementing theSecureArea
interface.MyCreateKeySettings
class extending theCreateKeySettings
class.MyKeyUnlockData
class implementing theKeyUnlockData
interface.MyKeyInfo
class extending theKeyInfo
class.
To register the MySecureArea
implementation with the Wallet Core Library, you must configure the
wallet instance as shown below:
val walletConfig = EudiWalletConfig()
.configureOpenId4Vci {
// Configuration for OpenId4VCI
}
.configureOpenId4Vp {
// Configuration for OpenId4VP
}
val wallet = EudiWallet(context, walletConfig) {
withSecureAreas(
listOf(
MySecureArea()
)
)
}
For detailed information on EudiWallet configuration, please refer to the EudiWallet documentation
Note:If the MySecureArea
instance is required, it can be retrieved from the wallet instance
using the following code:
val mySecureArea = wallet.secureAreaRepository.implementations
.filterIsInstance<MySecureArea>()
.first()
When issuing a document with OpenId4VCI, the Wallet Core Library triggers events that require custom key management. There are two key events that necessitate this:
-
IssueEvent.DocumentRequiresCreateSettings
: This event occurs when creating a document for a specific offered document that requires a key. In the case of custom key management, you must create the correspondingCreateDocumentSettings
object to accompany theSecureArea
implementation. This object defines in whichSecureArea
and how the key should be created. ThecreateDocumentSettings
is then used to resume the issuance process. -
IssueEvent.DocumentRequiresUserAuth
: This event is triggered when key unlocking is needed to sign the proof of possession for the document's key, which is required to issue the document. To unlock the key, you must create the appropriateKeyUnlockData
object that corresponds to theSecureArea
implementation. This object specifies how the key should be unlocked (e.g., via password, biometric data, or authorization tokens). ThekeyUnlockData
is then used to resume the issuance process.
The following code snippet demonstrates how to handle these events with custom key management using
the custom MySecureArea
implementation:
val onIssueEvent = OnIssueEvent { event ->
when (event) {
is IssueEvent.DocumentRequiresCreateSettings -> {
// triggered when creating a document for a given offered document
val offeredDocument = event.offeredDocument
// create the createDocumentSettings for the document
// using custom implementation MySecureArea for keys
val mySecureArea = wallet.secureAreaRepository.implementations
.filterIsInstance<MySecureArea>()
.first()
val createDocumentSettings = CreateDocumentSettings(
// set the identifier of the desired secure area
secureAreaIdentifier = mySecureArea.identifier,
// set the CreateKeySettings for the key based on the secure area
createKeySettings = MyCreateKeySettings(
// set custom settings for creating the key
// with MySecureArea implementation
)
)
// resume the issuance process with the createDocumentSettings
event.resume(createDocumentSettings)
// or cancel the issuance process
event.cancel("User canceled the issuance process")
}
is IssueEvent.DocumentRequiresUserAuth -> {
// triggered when user authentication is required to issue a document
// Holds the document object that requires user authentication
// and the algorithm that is going to be used for signing the proof of possession,
// as well as methods for resuming the issuance process or canceling it
// Document and signing algorithm are provided
// to use if required for creating the keyUnlockData
val signingAlgorithm = event.signingAlgorithm
val document = event.document
//
// --> show prompt if needed to unlock the key <--
//
// create the keyUnlockData to unlock the key.
val keyUnlockData = MyKeyUnlockData(
// set the extra information for the key unlock data
// e.g. password, biometric data, authorization tokens etc.
)
// to resume the issuance process, after authenticating user, call
// resume with the keyUnlockData
event.resume(keyUnlockData)
// or cancel the issuance process by calling cancel method
event.cancel("User canceled the issuance process")
}
else -> {
// handling rest of issuing events
}
}
}
When presenting a document using OpenId4VP or through proximity presentation with BLE, a
TransferEvent.RequestReceived
event is triggered upon receiving a request. The request contains
the documents and items to be disclosed. To disclose the requested documents, you must unlock the
keys associated with those documents. To do this, you need to create the appropriate KeyUnlockData
object, which corresponds to the SecureArea
implementation. This object specifies how the key
should be unlocked (e.g., using a password, biometric data, authorization tokens, etc.). The
keyUnlockData
is then used to generate the response.
The following code snippet illustrates how to handle the TransferEvent.RequestReceived
event and
disclose the requested documents with custom key management using a custom SecureArea
implementation when key unlocking is required:
/**
* Keeps the processed request to be used later for generating the response
*/
lateinit var processedRequest: RequestProcessor.ProcessedRequest.Success
/**
* Example function that demonstrates how to get information about the requested documents
* and show them to the user. The function should be called when the request is received.
*/
fun showRequestedDocuments() {
// shows requested documents to user
processedRequest.requestedDocuments.forEach { requestedDocument ->
// get document if needed to show more information such as document's name and docType
val document =
wallet.getDocumentById(requestedDocument.documentId) as? IssuedDocument
val documentName = document?.name
val docType = (document?.format as? MsoMdocFormat)?.docType
// show requested fields for this document
val docItems =
requestedDocument.requestedItems.forEach { (docItem, intentToRetain) ->
val nameSpace = docItem.namespace
val elementIdentifier = docItem.elementIdentifier
}
}
}
/**
* Example function that demonstrates how to generate and send a response based on the
* selected documents. The function should be called after the user has selected the
* documents to disclose.
*
* @param selectedDocuments A map of selected documents and the selected items to disclose
*/
fun discloseDocuments(selectedDocuments: Map<IssuedDocument, List<DocItem>>) {
val disclosedDocuments = DisclosedDocuments(
selectedDocuments.map { (document, docItems) ->
// should block until the user has unlock the key to sign the response
// One can use information from document to determine how to
// create the MyKeyUnlockData object, such as:
val secureArea = document.secureArea as MySecureArea
val keyAlias = document.keyAlias
val keyInfo = document.keyInfo as MyKeyInfo
//
// --> show prompt if needed to unlock the key <--
//
// create and return the keyUnlockData to unlock the key of the document
val keyUnlockData = MyKeyUnlockData(
// set the extra information for the key unlock data
// e.g. password, biometric data, authorization tokens etc.
)
DisclosedDocument(
documentId = document.id,
disclosedItems = docItems,
keyUnlockData = keyUnlockData
)
})
val response = processedRequest.generateResponse(disclosedDocuments).getOrThrow()
wallet.sendResponse(response)
}
/**
* Example transfer event listener that demonstrates how to handle the request received event
* and show the requested documents to the user.
*/
val transferEventListener = TransferEvent.Listener { event ->
when (event) {
is TransferEvent.RequestReceived -> try {
// get the processed request
processedRequest = event.processedRequest.getOrThrow()
// the request has been received and processed
showRequestedDocuments()
} catch (e: Throwable) {
// An error occurred
// handle the error
}
// handle other events
else -> {}
}
}
// Add the transfer event listener to the wallet
wallet.addTransferEventListener(transferEventListener)