Skip to content

Latest commit

 

History

History
977 lines (792 loc) · 35.7 KB

README.md

File metadata and controls

977 lines (792 loc) · 35.7 KB

EUDI Wallet Core library for Android

Important! Before you proceed, please read the EUDI Wallet Reference Implementation project description

Overview

This repository contains the EUDI Wallet Core library for Android. The library is a part of the EUDI Wallet Reference Implementation project.

This library acts as a coordinator by orchestrating the various components that are required to implement the EUDI Wallet functionality. On top of that, it provides a simplified API that can be used by the application to implement the EUDI Wallet functionality.

graph TD;
    A[eudi-lib-android-wallet-core]
    B[eudi-lib-android-wallet-document-manager] -->  |DocumentManager|A 
    C[eudi-lib-android-iso18013-data-transfer] --> |TransferManager|A 
    D[eudi-lib-jvm-openid4vci-kt] --> |OpenId4VciManager|A 
    E[eudi-lib-jvm-siop-openid4vp-kt] --> |OpenId4VpManager|A 
    F[com.android.identity:identity-credential-android] --> |SecureArea,StorageEngine|B 
    F --> C 
    H[eudi-lib-jvm-presentation-exchange] --> E 
Loading

The library provides the following functionality:

  • Document management
    • Storage encryption
    • Using device secure area for generating/storing documents' keypair
    • Enforcing device user authentication when retrieving documents' private keys
  • Document issuance
    • Support for OpenId4VCI Draft 13 document issuance
      • Authorization Code Flow
      • Pre-authorization Code Flow
      • Support for mso_mdoc format
      • Support for sd-jwt-vc format
      • Support credential offer
      • Support for DPoP JWT in authorization
      • Support for JWT and CWT proof types
  • Proximity document presentation
    • Support for ISO-18013-5 device retrieval
      • QR device engagement
      • NFC device engagement
      • BLE data transfer
      • NFC data transfer
      • Wifi-Aware data transfer
  • Remote document presentation
    • OpenId4VP document transfer
      • For pre-registered verifiers
      • Dynamic registration of verifiers

The library is written in Kotlin and is compatible with Java. It is distributed as a Maven package and can be included in any Android project that uses Android 8 (API level 26) or higher.

❗ Disclaimer

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

Requirements

  • Android 8 (API level 26) or higher

Dependencies

In order to use snapshot versions add the following to your project's settings.gradle file:

dependencyResolutionManagement {
    repositories {
        // ...
        maven {
            url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")
            mavenContent { snapshotsOnly() }
        }
        // ...
    }
}

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-core:0.9.4-SNAPSHOT"
    implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
}

How to Use

Initialize the library

The library must be initialized before it can be used. The initialization must be done only once in the application.

In order to initialize the library, you need to provide a configuration object. The configuration object is created using the EudiWalletConfig.Builder class. The builder allows you to configure the following options:

  • documentsStorageDir method allows you to specify the directory where the documents are stored. The default value is the application's no-backup files directory.
  • encryptDocumentsInStorage method allows you to specify if the documents should be encrypted in the storage. The default value is true.
  • useHardwareToStoreKeys method allows you to specify if the StrongBox Android Keystore should be used, if available. The default value is true.
  • bleTransferMode method allows you to specify the BLE transfer mode. The BLE transfer mode can be one of the following:
    • EudiWalletConfig.BLE_SERVER_PERIPHERAL_MODE - the device will act as a BLE server
    • EudiWalletConfig.BLE_CLIENT_CENTRAL_MODE - the device will act as a BLE client The default value is EudiWalletConfig.BLE_SERVER_PERIPHERAL_MODE.
  • bleClearCacheEnabled method allows you to specify if the BLE cache should be cleared after the transfer. The default value is false.
  • userAuthenticationRequired method allows you to specify if the user authentication is required when using documents' keys. The default value is false.
  • userAuthenticationTimeOut method allows you to specify the user authentication timeout in milliseconds. If the value is 0, the user authentication is required for every use of the key, otherwise it's required within the given amount of milliseconds. The default value is 30000.
  • trustedReaderCertificates method allows you to specify the list of trusted reader certificates. The default value is an empty list.
  • openId4VpConfig method allows you to specify the configuration for OpenID4VP. The default value is null.
  • openId4VciConfig method allows you to specify the configuration for OpenID4VCI. The default value is null.

The following example shows how to initialize the library:

import eu.europa.ec.eudi.wallet.EudiWallet
import eu.europa.ec.eudi.wallet.EudiWalletConfig
import java.security.cert.X509Certificate

val storageDir = applicationContext.noBackupFilesDir
val verifierApiUri = "https://verifier-api-uri"
val config = EudiWalletConfig.Builder(applicationContext)
    .bleTransferMode(
        EudiWalletConfig.BLE_SERVER_PERIPHERAL_MODE,
        EudiWalletConfig.BLE_CLIENT_CENTRAL_MODE
    )
    .trustedReaderCertificates(
        listOf<X509Certificate>(
            // list of trusted reader certificates
        )
    )
    .documentsStorageDir(storageDir)
    .encryptDocumentsInStorage(true)
    .userAuthenticationRequired(false)
    .userAuthenticationTimeOut(30_000L)
    .useHardwareToStoreKeys(true)
    .openId4VpConfig {
        withClientIdSchemes(
            listOf(
                ClientIdScheme.Preregistered(
                    listOf(
                        PreregisteredVerifier(
                            "VerifierClientId",
                            "VerifierLegalName",
                            "https://example.com"
                        )
                    )
                ),
                ClientIdScheme.X509SanDns
            )
        )
        withScheme(
            listOf(
                "eudi-openid4vp",
                "mdoc-openid4vp"
            )
        )
        withEncryptionAlgorithms(listOf(EncryptionAlgorithm.ECDH_ES))
        withEncryptionMethods(listOf(EncryptionMethod.A128CBC_HS256))
    }
    .openId4VciConfig {
        withIssuerUrl("https://issuer.example.com")
        withClientId("wallet-client-id")
        authFlowRedirectionURI("eudi-openid4ci://authorize")
        useStrongBoxIfSupported(false)
        useDPoP(false)
        parUsage(ParUsage.IF_SUPPORTED)
        proofTypes(Config.ProofType.JWT, Config.ProofType.CWT)
    }
    .build()

EudiWallet.init(applicationContext, config)

To initialize the library with the default configuration, you can use the following code:

val config = EudiWalletConfig.Builder(applicationContext).build()
EudiWallet.init(applicationContext, config)

Library initialization is recommended to be done in the Application.onCreate method.

Manage documents

Document is a data structure that contains the following information:

  • id document's unique identifier
  • docType 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 backed
  • createdAt document's creation date
  • requiresUserAuth flag that indicates if the document requires user authentication to be accessed
  • nameSpacedData retrieves the document's data, grouped by nameSpace. Values are in CBOR bytes

The library provides a set of methods to work with documents.

Listing documents

The EudiWallet.getDocuments method that returns the list of documents stored in the library.

The following example shows how to list documents:

val documents = EudiWallet.getDocuments()

Retrieving a document

The EudiWallet.getDocumentById method that returns a document with the given id.

The following example shows how to retrieve a document:

val document = EudiWallet.getDocumentById(documentId)

Deleting a document

The EudiWallet.deleteDocumentById method that deletes a document with the given id.

The following example shows how to delete a document:

val result = EudiWallet.deleteDocumentById(documentId)

when (result) {
    is EudiWallet.DeleteDocumentResult.Success -> {
        // document deleted
    }
    is EudiWallet.DeleteDocumentResult.Failure -> {
        // error
        val cause = result.throwable
    }
}

Issuing/Adding a document

Adding a document is a two-step process. First, you need to create an issuanceRequest using the method EudiWallet.createIssuanceRequest. The issuanceRequest holds the public certificate that will be used from the issuer to sign the document.

Later, when document's data is available, you can create the document using the method EudiWallet.addDocument to add the document to document storage.

The following example shows how to add a document:

val docType = "eu.europa.ec.eudiw.pid.1"
val hardwareBacked = false
val attestationChallenge = byteArrayOf(
    // attestation challenge bytes
    // provided by the issuer
)
val requestResult =
    EudiWallet.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

        // ... code that sends certificate to issuer and receives document's data

        val issuerData: ByteArray = byteArrayOf() // CBOR bytes received from issuer

        val addResult = EudiWallet.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)
            }
        }
    }
}

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.

Currently, only one document can be added at a time.

Loading sample documents

The library provides the EudiWallet.loadSampleData method that allows to load sample documents in document storage, without the need to issue them. The signing of the documents is done by the library internally, using a predefined key pair.

This method is intended for testing purposes only.

The following example shows how to load sample documents:

val sampleData = byteArrayOf() // CBOR bytes with sample documents
val result = EudiWallet.loadSampleData(sampleData)

when (result) {
    is LoadSampleResult.Success -> {
        // sample documents loaded
    }
    is LoadSampleResult.Failure -> {
        // error
        val cause = result.throwable
    }
}

Sample documents must be in CBOR format with the following structure:

Data = {
 "documents" : [+Document] ; Sample documents
}
Document = {
 "docType" : DocType, ; Document type returned
 "issuerSigned" : IssuerSigned ; Data elements
}
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
}

Issue document using OpenID4VCI

The library provides the functionality to issue documents using OpenID4VCI. To issue a document using this functionality, EudiWallet must be initialized with the openId4VciConfig configuration. See the Initialize the library section.

Resolving Credential offer

The library provides the EudiWallet.resolveDocumentOffer method that resolves the credential offer URI. The method returns the resolved Offer object that contains the offer's data. The offer's data can be displayed to the user.

The following example shows how to resolve a credential offer:

val offerUri = "https://issuer.com/?credential_offer=..."
EudiWallet.resolveDocumentOffer(offerUri) { result ->

    when (result) {
        is OfferResult.Success -> {
            val offer: Offer = result.offer
            // display the offer's data to the user
            val issuerName = offer.issuerName
            val offeredDocuments: List<OfferedDocument> = offer.offeredDocuments
            // ...
        }
        is OfferResult.Failure -> {
            val error = result.throwable
            // handle error while resolving the offer
        }
    }
}

There is also the availability for the EudiWallet.resolveDocumentOffer method to specify the executor in which the onResolvedOffer callback is executed, by assigning the executor parameter. If the executor parameter is null, the callback will be executed on the main thread.

val executor = Executors.newSingleThreadExecutor()
EudiWallet.resolveDocumentOffer(offerUri, executor) { result ->
    // ...
}

Issuing a document

There are two ways to issue a document using OpenID4VCI:

  1. Using the EudiWallet.issueDocumentByDocType method, when the document's docType is known.
  2. Using the EudiWallet.issueDocumentByOffer or EudiWallet.issueDocumentByOfferUri methods, when an OpenId4VCI offer is given.

Important Notes:

  • Currently, only mso_mdoc format is supported
  • Currently, only ES256 algorithm is supported for signing OpenId4CVI proof of possession of the publicKey.

The following example shows how to issue a document using OpenID4VCI:

val onIssueEvent = OnIssueEvent { event ->
    when (event) {
        is IssueEvent.Started -> {
            // indicates that OpenId4VCI process is started 
            // and holds the total number of documents to be issued
            val numberOfDocumentsToBeIssued: Int = event.total
        }

        is IssueEvent.Finished -> {
            // triggered when the OpenId4VCI process is finished
            // and holds the documentIds of the issued documents
            val issuedDocumentIds: List<String> = event.issuedDocuments
        }

        is IssueEvent.Failure -> {
            // triggered when an error occurs during the OpenId4VCI process
            // and holds the error
            val cause = event.cause
        }

        is IssueEvent.DocumentIssued -> {
            // triggered each time a document is issued
            // and holds information about the issued document
            val documentId: String = event.documentId
            val documentName: String = event.name
            val docType: String = event.docType
        }

        is IssueEvent.DocumentFailed -> {
            // triggered when an error occurs during the issuance of a document
            // and holds the error and information about the failed to issue document
            val documentName: String = event.name
            val docType: String = event.docType
            val cause: Throwable = event.cause
        }

        is IssueEvent.DocumentRequiresUserAuth -> {
            // triggered when user authentication is required to issue a document
            // Holds the crypto object that is used for user authentication
            // and information about the document that requires user authentication
            // as well as methods for resuming the issuance process or canceling it
            val cryptoObject = event.cryptoObject
            val documentName: String = event.name
            val docType: String = event.docType

            // to resume the issuance process, after authenticating user,  call
            event.resume()

            // or cancel the issuance process by calling
            event.cancel()
        }
    }
}

EudiWallet.issueDocumentByDocType("eu.europa.ec.eudiw.pid.1", onIssueEvent)
// or
EudiWallet.issueDocumentByOfferUri("https://issuer.com/?credential_offer=...", onIssueEvent)
// or given a resolved offer object
EudiWallet.issueDocumentByOffer(offer, onIssueEvent)

There's also available for issueDocumentByDocType, issueDocumentByOfferUri and issueDocumentByOffer methods to specify the executor in which the onIssueEvent callback is executed, by assigning the executor parameter. If the executor parameter is null, the callback will be executed on the main thread.

val executor = Executors.newSingleThreadExecutor()
EudiWallet.issueDocumentByDocType("eu.europa.ec.eudiw.pid.1", executor) { event ->
    // ...
}
Authorization code flow

For the authorization code flow to work, the application must handle the redirect URI. The redirect URI is the URI that the Issuer will redirect the user to after the user has authenticated and authorized. The redirect URI must be handled by the application and resume the issuance process by calling the EudiWallet.resumeOpenId4VciWithAuthorization. Also, the redirect uri declared in the OpenId4VCI configuration must be declared in the application's manifest file.

<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <!-- rest of manifest -->
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <!-- rest of activity -->
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>

                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>

                <data android:scheme="eudi-openid4ci" android:host="authorize"/>
            </intent-filter>
        </activity>
    </application>
</manifest>
// ...
EudiWalletConfig.Builder(applicationContext)
// ... 
    .openId4VciConfig {
        // ...
        authFlowRedirectionURI("eudi-openid4ci://authorize")
        // ...
    }
//...
class SomeActivity : AppCompatActivity() {
    // ...
    override fun onResume() {
        super.onResume()
        // check if intent is from the redirect uri to resume the issuance process
        // ...
        // then call
        EudiWallet.resumeOpenId4VciWithAuthorization(intent)
    }
    // ...
}

Transfer documents

The library supports the following 3 ways to transfer documents:

  1. Offline document transfer between devices over BLE, according to the ISO 18013-5 specification
    • Device engagement using QR code
    • NFC device engagement
  2. Document retrieval to a website according to the ISO 18013-7 specification
    • RestAPI using app link
  3. Document retrieval using OpenID4VP

Transfer process is asynchronous. During the transfer, events are emitted that indicate the current state of the transfer. The following events are emitted:

  1. TransferEvent.QrEngagementReady: The QR code is ready to be displayed. Get the QR code from event.qrCode.
  2. TransferEvent.Connecting: The devices are connecting. Use this event to display a progress indicator.
  3. TransferEvent.Connected: The devices are connected.
  4. TransferEvent.RequestReceived: A request is received. Get the parsed request from event.requestedDocumentData and the initial request as received by the verifier from event.request.
  5. TransferEvent.ResponseSent: A response is sent.
  6. TransferEvent.Redirect: This event prompts to redirect the user to the given Redirect URI. Get the Redirect URI from event.redirectUri. This event maybe be returned when OpenId4Vp is used as a transmission channel.
  7. TransferEvent.Disconnected: The devices are disconnected.
  8. TransferEvent.Error: An error occurred. Get the Throwable error from event.error.

Attaching a TransferEvent.Listener

To receive events from the EudiWallet, you must attach a TransferEvent.Listener to it:

The following example demonstrates how to implement a TransferEvent.Listener and attach it to the EudiWallet object.

val transferEventListener = TransferEvent.Listener { event ->
    when (event) {
        is TransferEvent.QrEngagementReady -> {
            // event when the qr code is ready to be displayed. Get the qr code from event.qrCode
        }

        is TransferEvent.Connected -> {
            // event when the devices are connected
        }

        is TransferEvent.RequestReceived -> {
            // event when a request is received. Get the parsed request from event.requestedDocumentData.
            // use the received request and send the appropriate response by providing the disclosed documents.

            val disclosedDocuments = DisclosedDocuments(
                listOf(
                    // add the disclosed documents here
                )
            )
            when (val responseResult = EudiWallet.sendResponse(disclosedDocuments)) {
                is ResponseResult.Failure -> {
                    // handle the failure
                }
                is ResponseResult.Success -> {
                    // response has been send successfully
                }
                is ResponseResult.UserAuthRequired -> {
                    // user authentication is required. Get the crypto object from responseResult.cryptoObject
                    val cryptoObject = responseResult.cryptoObject
                }
            }
        }

        is TransferEvent.ResponseSent -> {
            // event when a response is sent
        }

        is TransferEvent.Redirect -> {
            // event that prompts to redirect the user to the given Redirect URI (event.redirectUri)
            // the event may be return when OpenId4Vp is used as a transmission channel. 
        }

        is TransferEvent.Disconnected -> {
            // event when the devices are disconnected
            // presentation can be stopped here
            EudiWallet.stopPresentation()
        }
        is TransferEvent.Error -> {
            // event when an error occurs. Get the error from event.error
            val error: Throwable = event.error
            // handle error 
            // stop presentation
            EudiWallet.stopPresentation()
        }
    }
}

EudiWallet.addTransferEventListener(transferEventListener)

Initiating transfer

  1. BLE transfer using QR Engagement

    Once the a transfer event listener is attached, use the EudiWallet.startQrEngagement() method to start the QR code engagement.

    EudiWallet.startQrEngagement()
    
    //... other code
    
    // in event listener when the qr code is ready to be displayed
    when (event) {
        is TransferEvent.QrEngagementReady -> {
            // show the qr code to the user
            val qrCode: QrCode = event.qrCode
            val qrBitmap = qrCode.asBitmap(/* size */) // get the qr code as bitmap
            // - or -
            val qrView = qrCode.asView(/* context, width */) // get the qr code as view
        }
        // ... rest of the code
    }
  2. BLE transfer using NFC Engagement

In order to use NFC for engagement, you must add the service DefaultNfcEngagementService to your application's manifest file, like shown below:

```xml

<application>
    <!-- rest of manifest -->
    <service android:exported="true" android:label="@string/nfc_engagement_service_desc"
        android:name="eu.europa.ec.eudi.wallet.util.DefaultNfcEngagementService"
        android:permission="android.permission.BIND_NFC_SERVICE">
        <intent-filter>
            <action android:name="android.nfc.action.NDEF_DISCOVERED" />
            <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
        </intent-filter>

        <!-- the following "@xml/nfc_engagement_apdu_service" in meta-data is provided by the library -->
        <meta-data android:name="android.nfc.cardemulation.host_apdu_service"
            android:resource="@xml/nfc_engagement_apdu_service" />
    </service>

</application>
```

You can enable or disable the NFC engagement in your app by calling the EudiWallet.enableNFCEngagement(Activity) and EudiWallet.disableNFCEngagement(Activity) methods.

In the example below, the NFC engagement is enabled when activity is resumed and disabled when the activity is paused.

```kotlin
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    override fun onResume() {
        super.onResume()
        EudiWallet.enableNFCEngagement(this)
    }

    override fun onPause() {
        super.onPause()
        EudiWallet.disableNFCEngagement(this)
    }
}
```
  1. RestAPI using app link

    To enable 18013-7 REST API functionality, declare to your app's manifest file (AndroidManifest.xml) an Intent Filter for your MainActivity:

    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="mdoc" android:host="*" />
    </intent-filter>

    and set launchMode="singleTask" for this activity.

    To initiate the transfer using an app link (reverse engagement), use the EudiWallet.startEngagementFromIntent(Intent) method.

    The method receives as a parameter an Intent that contains the data for the device engagement.

    The example below demonstrates how to use the EudiWallet.startEngagementFromIntent(Intent) method to initiate the device engagement and transfer.

    class MainActivity : AppCompatActivity() {
    
        // ... rest of activity code
    
        override fun onResume() {
            super.onResume()
            EudiWallet.startEngagementFromIntent(intent)
        }
    
        override fun onNewIntent(intent: Intent) {
            super.onNewIntent(intent)
            EudiWallet.startEngagementFromIntent(intent)
        }
    }
  2. OpenID4VP

    To use the OpenID4VP functionality, the configuration that is used to initialize the library must contain the openId4VpConfig. See the Initialize the library section.

    Then, declare to your app's manifest file (AndroidManifest.xml) the following Intent Filters for your MainActivity, for the scheme:

    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="mdoc-openid4vp" android:host="*" />
    </intent-filter> 

    Also set launchMode="singleTask" for this activity.

    To receive events for the Openid4VP transfer process, you must attach a TransferEvent.Listener, as in the following example:

    import android.content.Intent
    import android.os.Bundle
    import androidx.appcompat.app.AppCompatActivity
    import eu.europa.ec.eudi.iso18013.transfer.DisclosedDocument
    import eu.europa.ec.eudi.iso18013.transfer.DisclosedDocuments
    import eu.europa.ec.eudi.iso18013.transfer.ResponseResult
    import eu.europa.ec.eudi.iso18013.transfer.TransferEvent
    import eu.europa.ec.eudi.wallet.EudiWallet
    
    class MainActivity : AppCompatActivity() {
     
       val transferEventListener = TransferEvent.Listener { event ->
           when (event) {
               is TransferEvent.RequestReceived -> {
                   // event when a request is received. Get the parsed request from event.requestedDocumentData.
                   // use the received request to send the appropriate response by defining the disclosed documents.
                   val disclosedDocuments = DisclosedDocuments(
                       listOf(
                           // add the disclosed documents here
                       )
                   )
                   when (val responseResult = EudiWallet.sendResponse(disclosedDocuments)) {
                       is ResponseResult.Failure -> {
                           // handle the failure
                       }
                       is ResponseResult.Success -> {
                           // response has been send successfully
                       }
                       is ResponseResult.UserAuthRequired -> {
                           // user authentication is required. Get the crypto object from responseResult.cryptoObject
                           val cryptoObject = responseResult.cryptoObject
                       }
                   }
               }
    
              else -> {
                  // rest of event handling
              }
          }
      }
      // ... rest of activity code
     
      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          EudiWallet.addTransferEventListener(transferEventListener)
      }
     
      override fun onResume() {
          super.onResume()
          handleOpenId4VpIntent(intent)
      }
     
      override fun onNewIntent(intent: Intent) {
          super.onNewIntent(intent)
          setIntent(null)
          handleOpenId4VpIntent(intent)
      }
    
      private fun handleOpenId4VpIntent(intent: Intent) {
          when (intent.scheme) {
              "mdoc-openid4vp" -> EudiWallet.startEngagementFromIntent(intent)
              else -> {
                  // do nothing
              }
          }
      }
    }

Receiving request and sending response

When a request is received, the TransferEvent.RequestReceived event is triggered. The parsed request can be retrieved from event.requestedDocumentData, while the initial request, as received by the verifier, can be retrieved from event.request.

The parsed request contains a list of RequestedDocument objects, which can be used to show the user what documents are requested. Also, a selectively disclosure option can be implemented using the requested documents, so user can choose which of the documents to share.

Then, a DisclosedDocuments object must be created with the list of documents to be disclosed and the response can be sent using the EudiWallet.sendResponse(DisclosedDocuments) method.

The method returns a ResponseResult object, which can be one of the following:

  1. ResponseResult.Failure: The response creation failed. The error can be retrieved from responseResult.error.
  2. ResponseResult.Success: The response has been sent successfully.
  3. ResponseResult.UserAuthRequired: The response creation requires user authentication. The CryptoObject can be retrieved from responseResult.cryptoObject. After success authentication the response can be sent again, using the EudiWallet.sendResponse(DisclosedDocuments) method.

The following example demonstrates the above steps:

val transferEventListener = TransferEvent.Listener { event ->
    when (event) {

        is TransferEvent.RequestReceived -> {
            // event when a request is received. Get the parsed request from event.requestedDocumentData
            // send the response

            val disclosedDocuments = DisclosedDocuments(
                listOf(
                    // add the disclosed documents here
                )
            )
            when (val responseResult = EudiWallet.sendResponse(disclosedDocuments)) {
                is ResponseResult.Failure -> {
                    // handle the failure
                }
                is ResponseResult.Success -> {
                    // the response has been sent successfully
                }
                is ResponseResult.UserAuthRequired -> {
                    // user authentication is required. Get the crypto object from responseResult.cryptoObject
                    val cryptoObject = responseResult.cryptoObject
                }
            }
        }
        // handle other events
    }
}

How to contribute

We welcome contributions to this project. To ensure that the process is smooth for everyone involved, follow the guidelines found in CONTRIBUTING.md.

License

Third-party component licenses

See licenses.md for details.

License 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.