用于 iOS 的 EUDI Wallet Kit 库

重要提示! 在继续之前,请阅读 EUDI Wallet 参考实现项目描述


EUDI ISO iOS Wallet Kit 库

License Swift Lines of Code Duplicated Lines (%) Reliability Rating Vulnerabilities

概述

此存储库包含用于 iOS 的 EUDI Wallet Kit 库。该库是 EUDI Wallet 参考实现项目的一部分。

该库充当协调器,协调实现 EUDI Wallet 功能所需的各种组件。最重要的是,它提供了一个简化的 API,应用程序可以使用它来实现 EUDI Wallet 功能。

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 
加载

该库提供以下功能

该库用 Swift 编写,兼容 iOS 14 或更高版本。它作为一个 Swift 包分发,可以包含在任何 iOS 项目中。

它基于以下规范

免责声明

发布的软件是初始开发版本

安装

要使用 EUDI Wallet Kit,请将以下依赖项添加到您的 Package.swift

dependencies: [
    .package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-wallet-kit.git", .upToNextMajor(from: "0.6.6"))
]

然后将 Eudi Wallet 包添加到您的目标的依赖项中

dependencies: [
    .product(name: "EudiWalletKit", package: "eudi-lib-ios-wallet-kit"),
]

参考

详细文档在 DocC 文档中提供,点击此处

初始化

EudiWallet 类为两个用户证明展示流程提供了一个统一的 API。它使用文档存储管理器实例进行初始化。对于 SwiftUI 应用程序,可以将 wallet 实例添加为 environmentObject,以便可以从所有视图访问。文档存储的 KeyChain 实现可用。

钱包开发者可以通过将 SecureArea 实例传递给 wallet 来自定义加密密钥操作,否则 wallet-kit 会创建“SecureEnclave”(默认)和“Software”安全区域。钱包开发者可以为每种文档类型指定密钥创建选项,例如曲线类型、安全区域名称和密钥解锁策略。

let wallet = try! EudiWallet(serviceName: "my_wallet_app",
   trustedReaderCertificates: [Data(name: "eudi_pid_issuer_ut", ext: "der")!] )

管理文档

EudiWallet 类提供了一组用于处理文档的方法。

加载文档

loadDocuments 方法从存储中返回具有特定状态的文档。

以下示例展示了如何检索已颁发的文档

 public func loadDocuments() async throws {
    let documents = try await wallet.loadDocuments(status: .issued)
  }

要检索所有状态的文档,请使用 loadAllDocuments 方法。

let documents = try await wallet.loadAllDocuments()

loadDocument(id:status:) 方法返回具有给定 id 和状态的文档。

以下示例展示了如何检索文档

let document = try await wallet.loadDocument(id: documentId, status: .issued)

存储管理器

只读属性 storageStorageManager 类的一个实例。目前使用 keychain 实现。它使用 iOS KeyChain 提供文档管理功能。

存储模型为支持的已知文档类型提供以下模型

文档类型 (DocType) 模型 (Model)
eu.europa.ec.eudiw.pid.1 EuPidModel
org.iso.18013.5.1.mDL IsoMdlModel

由于检索到的已颁发 mDoc 文档仅公开基本元数据和原始数据,因此必须将其解码为相应的 CBOR 模型。该库提供了 StorageManager\toClaimsModel 函数,用于将文档原始 CBOR 数据解码为符合 DocClaimsDecodable 协议的强类型模型。

加载函数自动更新 StorageManager 成员。解码后的已颁发文档可在 docModels 属性中使用。延迟和待处理的文档分别在 StorageManager\deferredDocumentsStorageManager\pendingDocuments 属性中使用。

对于其他文档类型,提供了 GenericMdocModel

删除文档

deleteDocument(id:) 方法用于删除具有给定 id 的文档。

以下示例展示了如何删除文档

try await wallet.deleteDocument(id: documentId)

使用 OpenID4VCI 颁发文档

该库提供了使用 OpenID4VCI 颁发文档的功能。

要使用此功能颁发文档,必须正确初始化 EudiWallet。如果 userAuthenticationRequired 为 true,则需要用户身份验证。身份验证提示消息具有本地化键 "issue_document"。颁发文档后,文档数据和相应的私钥将存储在 wallet 存储中。

按文档类型 (docType) 颁发文档

当要颁发的文档类型 (docType) 时,使用 issueDocument(docType:keyOptions:) 方法。

以下示例展示了如何使用 OpenID4VCI 颁发 EUDI 个人 ID 文档

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
}

您还可以通过传递配置 identifier 参数 identifier 来颁发文档。可以使用 getIssuerMetadata 方法从颁发者的元数据中检索配置标识符。

  // get current issuer metadata
  let configuration = try await wallet.getIssuerMetadata()
  ...
  let doc = try await userWallet.issueDocument(identifier: "eu.europa.ec.eudi.pid_vc_sd_jwt")

解析凭证提供 (Credential offer)

该库提供了 resolveOfferUrlDocTypes(uriOffer:) 方法,用于解析凭证提供 URI。该方法返回已解析的 OfferedIssuanceModel 对象,其中包含提供的数据(提供的文档类型、颁发者名称和预授权流程的事务代码规范)。可以将提供的数据显示给用户。

以下示例展示了如何解析凭证提供

 func resolveOfferUrlDocTypes(uriOffer: String) async throws -> OfferedIssuanceModel {
    return try await wallet.resolveOfferUrlDocTypes(uriOffer: uriOffer)
  }

在用户接受提供后,可以使用 issueDocumentsByOfferUrl(offerUri:docTypes:docTypeKeyOptions:txCodeValue:) 方法颁发选定的文档。在授权码流程的情况下,不使用 txCodeValue 参数。以下示例展示了如何通过提供 URL 颁发文档

 let documents = try await walletController.issueDocumentsByOfferUrl(offerUri: uri,  docTypes: docOffers,
   docTypeKeyOptions: [EuPidModel.euPidDocType : KeyOptions(secureAreaName: "SecureEnclave", accessControl: [.requireUserPresence])], txCodeValue: txCodeValue )

授权码流程

要使授权码流程正常工作,必须通过设置 openID4VciRedirectUri 属性来指定重定向 URI。用户在授权 web 视图中被重定向到颁发者的授权端点。在用户进行身份验证并授权请求后,颁发者会将用户重定向回应用程序,并提供授权码。该库将授权码交换为访问令牌并颁发文档。

预授权码流程

当颁发者支持预授权码流程时,解析后的提供也会包含相应的信息。具体来说,OfferedIssuanceModel 对象中的 txCodeSpec 字段将包含

从用户的角度来看,应用程序必须提供一种输入事务代码的方法。

在用户接受提供后,可以使用 issueDocumentsByOfferUrl(offerUri:docTypes:docTypeKeyOptions:txCodeValue:) 方法颁发选定的文档。当提供事务代码时,可以通过调用上述方法并在 txCodeValue 参数中传递事务代码来恢复颁发过程。

动态颁发

Wallet kit 支持动态的基于 PID 的颁发

在调用 issueDocument(docType:keyOptions: KeyOptions:)issueDocumentsByOfferUrl(offerUri:docTypes:docTypeKeyOptions:txCodeValue:) 后,钱包应用程序需要检查文档是否处于待处理状态并具有 authorizePresentationUrl 属性。如果存在该属性,应用程序应使用展示 URL 执行 OpenID4VP 展示。成功后,应使用服务器提供的授权 URL 调用 resumePendingIssuance(pendingDoc:, webUrl:) 方法。

if let urlString = newDocs.last?.authorizePresentationUrl { 
	// perform openid4vp presentation using the urlString 
	// on success call resumePendingIssuance using the authorization url  

展示服务

展示服务协议 抽象了展示流程。BlePresentationServiceOpenId4VpService 类分别实现了近距离和远程展示流程。PresentationSession 类用于包装展示服务,并为 SwiftUI 屏幕提供 @Published 属性。以下示例代码演示了使用所选 流程类型 的新展示会话初始化 SwiftUI 视图。

let session = eudiWallet.beginPresentation(flow: flow)
// pass the session to a SwiftUI view
ShareView(presentationSession: session)

在视图出现时,会使用 receiveRequest 方法展示证明。对于 BLE(近距离)情况,deviceEngagement 属性会填充要在持有者设备上显示的 QR 码。

.task {
	 if presentationSession.flow.isProximity { await presentationSession.startQrEngagement() }
	 _ = await presentationSession.receiveRequest()
}

收到请求后,presentationSession.disclosedDocuments 包含请求的已证明项目。可以通过 UI 绑定修改项目的选定状态。最后,使用以下代码发送响应

// 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
       }
		})

日志记录

SwiftLog 库用于日志记录。该库提供了一个默认的日志记录器,该记录器会记录到控制台。主应用程序配置日志记录输出,例如文件日志记录。要使用日志记录器,请使用所需的标签创建一个日志记录器实例。日志记录器可用于记录具有不同日志级别的消息。

import Logging
// Create a logger with a label
let logger = Logger(label: "com.example.BestExampleApp.main")
// log an info message
logger.info("Hello World!")

参考

详细文档在 DocC 文档中提供,点击此处

依赖项

钱包套件的详细功能在以下 Swift 包中实现:MdocDataModel18013MdocSecurity18013MdocDataTransfer18013SiopOpenID4VP OpenID4VCI

参考应用程序

一个演示如何使用该库的参考应用程序是 App Wallet UI

如何贡献

我们欢迎大家为本项目做出贡献。为了确保所有参与者流程顺利,请遵循 CONTRIBUTING.md 中的指南。

许可详情

版权所有 (c) 2023 欧盟委员会

根据 Apache 许可证 2.0 版(“许可证”)获得许可;除非符合许可证的规定,否则您不得使用此文件。您可以在以下位置获得许可证的副本:

https://apache.ac.cn/licenses/LICENSE-2.0

除非适用法律要求或书面同意,否则软件是按“原样”分发的,不附带任何形式的明示或暗示的担保或条件。请参阅许可证中关于权限和限制的特定语言。