Important! Before you proceed, please read the EUDI Wallet Reference Implementation project description
This repository contains the EUDI Wallet Kit library for iOS. 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-ios-wallet-kit]
B[eudi-lib-ios-wallet-storage] --> |Wallet Storage|A
C[eudi-lib-ios-iso18013-data-transfer] --> |Transfer Manager|A
D[eudi-lib-ios-openid4vci-swift] --> |OpenId4Vci Manager|A
E[eudi-lib-ios-siop-openid4vp-swift] --> |OpenId4Vp Manager|A
F[eudi-lib-ios-iso18013-security] --> |Mdoc Security|C
G[eudi-lib-ios-iso18013-data-model] --> |Mdoc Data Model|C
H[eudi-lib-ios-presentation-exchange-swift] --> E
The library provides the following functionality:
- Document management
- Storage encryption
- Using iOS Secure Enclave for generating/storing documents' keypair
- Enforcing device user authentication when retrieving documents' private keys
- Document issuance
- Support for OpenId4VCI 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
- Support for deferred issuing
- Support for OpenId4VCI document issuance
- Proximity document presentation
- Support for ISO-18013-5 device retrieval
- QR device engagement
- BLE data transfer
- Support for ISO-18013-5 device retrieval
- Remote document presentation
- OpenId4VP document transfer
- For pre-registered verifiers
- Dynamic registration of verifiers
- OpenId4VP document transfer
The library is written in Swift and is compatible with iOS 14 or higher. It is distributed as a Swift package and can be included in any iOS project.
It is based on the following specifications:
- ISO/IEC 18013-5 – Published
- Presentation Exchange v2.0.0 - Published
- OpenID4VP – Draft 18
- SIOPv2 – Draft
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 to not put this version of the software into production use.
- Only the latest version of the software will be supported
To use EUDI Wallet Kit, add the following dependency to your Package.swift:
dependencies: [
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-wallet-kit.git", .upToNextMajor(from: "0.6.6"))
]
Then add the Eudi Wallet package to your target's dependencies:
dependencies: [
.product(name: "EudiWalletKit", package: "eudi-lib-ios-wallet-kit"),
]
Detailed documentation is provided in the DocC documentation here
The EudiWallet class provides a unified API for the two user attestation presentation flows. It is initialized with a document storage manager instance. For SwiftUI apps, the wallet instance can be added as an environmentObject
to be accessible from all views. A KeyChain implementation of document storage is available.
The wallet developer can customize cryptographic key operations by passing SecureArea
instances to the wallet, otherwise the wallet-kit creates 'SecureEnclave' (default) and 'Software' secure areas. The wallet developer can specify key create options per doc-type such as curve type, secure area name, and key unlock policy.
let wallet = try! EudiWallet(serviceName: "my_wallet_app",
trustedReaderCertificates: [Data(name: "eudi_pid_issuer_ut", ext: "der")!] )
The EudiWallet class provides a set of methods to work with documents.
The loadDocuments
method returns documents with a specific status from storage.
The following example shows how to retrieve issued documents:
public func loadDocuments() async throws {
let documents = try await wallet.loadDocuments(status: .issued)
}
To retrieve documents of all statuses use the loadAllDocuments
method.
let documents = try await wallet.loadAllDocuments()
The loadDocument(id:status:)
method returns a document with a given id and status.
The following example shows how to retrieve a document:
let document = try await wallet.loadDocument(id: documentId, status: .issued)
The read-only property storage
is an instance of a StorageManager class.
Currently the keychain implementation is used. It provides document management functionality using the iOS KeyChain.
The storage model provides the following models for the supported well-known document types:
DocType | Model |
---|---|
eu.europa.ec.eudiw.pid.1 | EuPidModel |
org.iso.18013.5.1.mDL | IsoMdlModel |
Since the issued mDoc documents retrieved expose only basic metadata and the raw data, they must be decoded to the corresponding CBOR models. The library provides the StorageManager\toClaimsModel
function to decode document raw CBOR data to strongly-typed models conforming to DocClaimsDecodable protocol.
The loading functions automatically update the StorageManager
members. The decoded issued documents are available in the docModels
property. The deferred and pending documents are available in the StorageManager\deferredDocuments
and StorageManager\pendingDocuments
properties respectively.
For other document types the GenericMdocModel is provided.
The deleteDocument(id:)
method that deletes a document with the given id.
The following example shows how to delete a document:
try await wallet.deleteDocument(id: documentId)
The library provides the functionality to issue documents using OpenID4VCI.
To issue a document
using this functionality, EudiWallet must be property initialized.
If userAuthenticationRequired
is true, user authentication is required. The authentication prompt message has localisation key "issue_document".
After issuing a document, the document data and corresponding private key are stored in the wallet storage.
When the document docType to be issued use the issueDocument(docType:keyOptions:)
method.
- Currently, only mso_mdoc format is supported
The following example shows how to issue an EUDI Personal ID document using OpenID4VCI:
do {
let doc = try await userWallet.issueDocument(docType: EuPidModel.euPidDocType, keyOptions: KeyOptions(secureAreaName: "SecureEnclave", accessControl: [.requireUserPresence])])
// document has been added to wallet storage, you can display it
}
catch {
// display error
}
The library provides the resolveOfferUrlDocTypes(uriOffer:)
method that resolves the credential offer URI.
The method returns the resolved OfferedIssuanceModel
object that contains the offer's data (offered document types, issuer name and transaction code specification for pre-authorized flow). The offer's data can be displayed to the
user.
The following example shows how to resolve a credential offer:
func resolveOfferUrlDocTypes(uriOffer: String) async throws -> OfferedIssuanceModel {
return try await wallet.resolveOfferUrlDocTypes(uriOffer: uriOffer)
}
After user acceptance of the offer, the selected documents can be issued using the issueDocumentsByOfferUrl(offerUri:docTypes:docTypeKeyOptions:txCodeValue:)
method.
The txCodeValue
parameter is not used in the case of the authorization code flow.
The following example shows how to issue documents by offer URL:
let documents = try await walletController.issueDocumentsByOfferUrl(offerUri: uri, docTypes: docOffers,
docTypeKeyOptions: [EuPidModel.euPidDocType : KeyOptions(secureAreaName: "SecureEnclave", accessControl: [.requireUserPresence])], txCodeValue: txCodeValue )
For the authorization code flow to work, the redirect URI must be specified specified by setting the the openID4VciRedirectUri
property.
The user is redirected in an authorization web view to the issuer's authorization endpoint. After the user authenticates and authorizes the request, the issuer redirects the user back to the application with an authorization code. The library exchanges the authorization code for an access token and issues the document.
When Issuer supports the pre-authorization code flow, the resolved offer will also contain the corresponding
information. Specifically, the txCodeSpec
field in the OfferedIssuanceModel
object will contain:
- The input mode, whether it is NUMERIC or TEXT
- The expected length of the input
- The description of the input
From the user's perspective, the application must provide a way to input the transaction code.
After user acceptance of the offer, the selected documents can be issued using the issueDocumentsByOfferUrl(offerUri:docTypes:docTypeKeyOptions:txCodeValue:)
method.
When the transaction code is provided, the issuance process can be resumed by calling the above-mentioned method and passing the transaction code in the txCodeValue
parameter.
Wallet kit supports the Dynamic PID based issuance
After calling issueDocument(docType:keyOptions: KeyOptions:)
or issueDocumentsByOfferUrl(offerUri:docTypes:docTypeKeyOptions:txCodeValue:)
the wallet application need to check if the doc is pending and has a authorizePresentationUrl
property. If the property is present, the application should perform the OpenID4VP presentation using the presentation URL. On success, the resumePendingIssuance(pendingDoc:, webUrl:)
method should be called with the authorization URL provided by the server.
if let urlString = newDocs.last?.authorizePresentationUrl {
// perform openid4vp presentation using the urlString
// on success call resumePendingIssuance using the authorization url
The presentation service protocol abstracts the presentation flow. The BlePresentationService and OpenId4VpService classes implement the proximity and remote presentation flows respectively. The PresentationSession class is used to wrap the presentation service and provide @Published properties for SwiftUI screens. The following example code demonstrates the initialization of a SwiftUI view with a new presentation session of a selected flow type.
let session = eudiWallet.beginPresentation(flow: flow)
// pass the session to a SwiftUI view
ShareView(presentationSession: session)
On view appearance the attestations are presented with the receiveRequest method. For the BLE (proximity) case the deviceEngagement property is populated with the QR code to be displayed on the holder device.
.task {
if presentationSession.flow.isProximity { await presentationSession.startQrEngagement() }
_ = await presentationSession.receiveRequest()
}
After the request is received the presentationSession.disclosedDocuments
contains the requested attested items. The selected state of the items can be modified via UI binding. Finally, the response is sent with the following code:
// Send the disclosed document items after biometric authentication (FaceID or TouchID)
// if the user cancels biometric authentication, onCancel method is called
await presentationSession.sendResponse(userAccepted: true,
itemsToSend: presentationSession.disclosedDocuments.items, onCancel: { dismiss() }, onSuccess: {
if let url = $0 {
// handle URL
}
})
The SwiftLog library is used for logging. The library provides a default logger that logs to the console. The main app configures logging outputs such as file logging. To use the logger create a logger instance with the desired label. The logger can be used to log messages with different log levels.
import Logging
// Create a logger with a label
let logger = Logger(label: "com.example.BestExampleApp.main")
// log an info message
logger.info("Hello World!")
Detailed documentation is provided in the DocC documentation here
The detailed functionality of the wallet kit is implemented in the following Swift Packages: MdocDataModel18013, MdocSecurity18013, MdocDataTransfer18013 and SiopOpenID4VP OpenID4VCI
A reference application that demonstrates the usage of this library is App Wallet UI.
We welcome contributions to this project. To ensure that the process is smooth for everyone involved, follow the guidelines found in CONTRIBUTING.md.
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.