❗ Important! Before you proceed, please read the EUDI Wallet Reference Implementation project description
This library provides a set of classes to manage documents in an EUDI Android Wallet.
It defines the interfaces for DocumentManager and Document classes and provides a standard implementation of the DocumentManager interface using Android Identity Credential API.
It also provides a sample implementation of the DocumentManager interface that can be used to load sample documents and test the library.
The library is written in Kotlin and is available for Android.
The released software is a initial development release version:
- The initial development release is an early endeavor reflecting the efforts of a short timeboxed period, and by no means can be considered as the final product.
- The initial development release may be changed substantially over time, might introduce new features but also may change or remove existing ones, potentially breaking compatibility with your existing code.
- The initial development release is limited in functional scope.
- The initial development release may contain errors or design flaws and other problems that could cause system or other failures and data loss.
- The initial development release has reduced security, privacy, availability, and reliability standards relative to future releases. This could make the software slower, less reliable, or more vulnerable to attacks than mature software.
- The initial development release is not yet comprehensively documented.
- Users of the software must perform sufficient engineering and additional testing in order to properly evaluate their application and determine whether any of the open-sourced components is suitable for use in that application.
- We strongly recommend not putting this version of the software into production use.
- Only the latest version of the software will be supported
- Android 8 (API level 26) or higher
To include the library in your project, add the following dependencies to your app's build.gradle file.
dependencies {
implementation "eu.europa.ec.eudi:eudi-lib-android-wallet-document-manager:0.3.0-SNAPSHOT"
}
Below is a quick overview of how to use the library.
For source code documentation, see in docs directory.
The library provides a DocumentManager
class implementation to manage documents.
To create an instance of
the DocumentManager,
library provides
a Builder,
that can be used to get a default implementation of the DocumentManager
.
import eu.europa.ec.eudi.wallet.document.DocumentManager
val documentManager = DocumentManager.Builder(context)
.useEncryption(true)
.storageDir(context.noBackupFilesDir)
.enableUserAuth(true)
.userAuthTimeout(30000)
.build()
Document is an object that contains the following information:
id
document's unique identifierdocType
document's docType (example: "eu.europa.ec.eudiw.pid.1")name
document's name. This is a human-readable name.hardwareBacked
document's storage is hardware backedcreatedAt
document's creation daterequiresUserAuth
flag that indicates if the document requires user authentication to be accessednameSpacedData
retrieves the document's data, grouped by nameSpace. Values are in CBOR bytes
To retrieve the list of documents, use the getDocuments
method:
import eu.europa.ec.eudi.wallet.document.Document
val documents: List<Document> = documentManager.getDocuments()
To retrieve a document by its id, use the getDocumentById
method:
import eu.europa.ec.eudi.wallet.document.Document
val documentId = "some document id"
val document: Document = documentManager.getDocumentById(documentId)
DocumentManager also provides the deleteDocumentById
method to delete a document by its id:
import eu.europa.ec.eudi.wallet.document.Document
import eu.europa.ec.eudi.wallet.document.DeleteDocumentResult
val documentId = "some document id"
val deleteResult: DeleteDocumentResult = documentManager.deleteDocumentById(documentId)
when (deleteResult) {
is DeleteDocumentResult.Success -> {
// document deleted successfully
val proofOfDeletion = deleteResult.proofOfDeletion
}
is DeleteDocumentResult.Failure -> {
// handle error while deleting document
}
}
In order to add a new document in DocumentManager
, the following steps should be followed:
- Create an issuance request using the
createIssuanceRequest
method of theDocumentManager
class. - Send the issuance request to the issuer.
- Add the document to the
DocumentManager
using theaddDocument
method.
In order to use with the addDocument
method, document's data must be in CBOR bytes that has the IssuerSigned structure
according to ISO 23220-4 * :
IssuerSigned = {
?"nameSpaces" : IssuerNameSpaces, ; Returned data elements
"issuerAuth" : IssuerAuth ; Contains the mobile security object (MSO) for issuer data authentication
}
IssuerNameSpaces = { ; Returned data elements for each namespace
+ NameSpace => [ + IssuerSignedItemBytes ]
}
IssuerSignedItemBytes = #6.24(bstr .cbor IssuerSignedItem)
IssuerSignedItem = {
"digestID" : uint, ; Digest ID for issuer data authentication
"random" : bstr, ; Random value for issuer data authentication
"elementIdentifier" : DataElementIdentifier, ; Data element identifier
"elementValue" : DataElementValue ; Data element value
}
IssuerAuth = COSE_Sign1 ; The payload is MobileSecurityObjectBytes
*Important note: Currently, the library does not support IssuerSigned structure without the nameSpaces
field.
See the code below for an example of how to add a new document in DocumentManager
:
val docType = "eu.europa.ec.eudiw.pid.1"
val hardwareBacked = false
val attestationChallenge = byteArrayOf(
// attestation challenge bytes
// provided by the issuer
)
val requestResult =
documentManager.createIssuanceRequest(docType, hardwareBacked, attestationChallenge)
when (requestResult) {
is CreateIssuanceRequestResult.Failure -> {
val error = requestResult.throwable
// handle error while creating issuance request
}
is CreateIssuanceRequestResult.Success -> {
val request = requestResult.issuanceRequest
val docType = request.docType
// the device certificate that will be used in the signing of the document
// from the issuer while creating the MSO (Mobile Security Object)
val certificateNeedAuth = request.certificateNeedAuth
// if the issuer requires the user to prove possession of the private key corresponding to the certificateNeedAuth
// then user can use the method below to sign issuer's data and send the signature to the issuer
val signingInputFromIssuer = byteArrayOf(
// signing input bytes from the issuer
// provided by the issuer
)
val signatureResult = request.signWithAuthKey(signingInputFromIssuer)
when (signatureResult) {
is SignedWithAuthKeyResult.Success -> {
val signature = signatureResult.signature
// signature for the issuer
}
is SignedWithAuthKeyResult.Failure -> {
val error = signatureResult.throwable
// handle error while signing with auth key
}
is SignedWithAuthKeyResult.UserAuthRequired -> {
// user authentication is required to sign with auth key
val cryptoObject = signatureResult.cryptoObject
// use cryptoObject to authenticate the user
// after user authentication, the user can sign with auth key again
}
}
// ... code that sends docType and certificates to issuer and signature if required
// after receiving the MSO from the issuer, the user can start the issuance process
val issuerData: ByteArray = byteArrayOf(
// CBOR bytes of the document
)
val addResult = documentManager.addDocument(request, issuerData)
when (addResult) {
is AddDocumentResult.Failure -> {
val error = addResult.throwable
// handle error while adding document
}
is AddDocumentResult.Success -> {
val documentId = addResult.documentId
// the documentId of the newly added document
// use the documentId to retrieve the document
documentManager.getDocumentById(documentId)
}
}
}
}
Library provides also an extension method on Document class to retrieve the document's data as JSONObject.
import org.json.JSONObject
val document = documentManager.getDocumentById("some_document_id")
val documentDataAsJson: JSONObject? = document?.nameSpacedDataJSONObject
The library, also provides a SampleDocumentManager
class implementation that can be used to load
sample documents and test the library easily.
To create a new instance of the SampleDocumentManager
class, use
the SampleDocumentManager.Builder
class:
import eu.europa.ec.eudi.wallet.document.sample.SampleDocumentManager
val sampleDocumentManager = SampleDocumentManager.Builder(context)
.documentManager(documentManager) // optional if a DocumentManager instance is already created, else a default DocumentManager instance will be created
.hardwareBacked(false) // documents' keys should be stored in hardware backed keystore if supported by the device. The default value is true if device supports hardware backed keystore, else false
.build()
After creating an instance of SampleDocumentManager
, you can load the sample documents using
the loadSampleData
method like shown below:
import android.util.Base64
// Assuming that the sample data is stored in a file named sample_data.json in the raw resources directory
// in base64 encoded format and context is an instance of android.content.Context
val sampleDocumentsByteArray = context.resources.openRawResource(R.raw.sample_data).use {
val data = String(it.readBytes())
Base64.decode(data, Base64.DEFAULT)
}
documentManager.loadSampleData(sampleDocumentsByteArray)
Sample documents must be in CBOR format with the following structure:
SampleData = {
"documents" : [+Document], ; Returned documents
}
Document = {
"docType" : DocType, ; Document type returned
"issuerSigned" : IssuerSigned, ; Returned data elements signed by the issuer
}
IssuerSigned = {
"nameSpaces" : IssuerNameSpaces, ; Returned data elements
}
IssuerNameSpaces = { ; Returned data elements for each namespace
+ NameSpace => [ + IssuerSignedItemBytes ]
}
IssuerSignedItem = {
"digestID" : uint, ; Digest ID for issuer data authentication
"random" : bstr, ; Random value for issuer data authentication
"elementIdentifier" : DataElementIdentifier, ; Data element identifier
"elementValue" : DataElementValue ; Data element value
}
We welcome contributions to this project. To ensure that the process is smooth for everyone involved, follow the guidelines found in CONTRIBUTING.md.
See licenses.md for details.
Copyright (c) 2023 European Commission
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.