Skip to content

Commit

Permalink
Merge pull request #142 from niscy-eudiw/main
Browse files Browse the repository at this point in the history
Refactor display properties in CredentialConfiguration and DocMetadata to use MdocDataModel18013.DisplayMetadata; update related methods for consistency. Fix sd-jwt presentation; update dependencies
  • Loading branch information
phisakel authored Jan 15, 2025
2 parents 9167f16 + 267ef36 commit 8a2996d
Show file tree
Hide file tree
Showing 15 changed files with 210 additions and 95 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.1'
xcode-version: '16.2'
- name: Get swift version
run: swift --version
- uses: actions/checkout@v4
Expand Down
38 changes: 19 additions & 19 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "b7f3ea1e15f2adb0ee4350749f60dab058802620dc55729771e591c8f579c1c1",
"originHash" : "cf7d8c7fcdc745a795543217c97125f08d241c6b0f7f6b94e856c137b671c172",
"pins" : [
{
"identity" : "blueecc",
Expand All @@ -24,44 +24,44 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
"state" : {
"revision" : "678d442c6f7828def400a70ae15968aef67ef52d",
"version" : "1.8.3"
"revision" : "729e01bc9b9dab466ac85f21fb9ee2bc1c61b258",
"version" : "1.8.4"
}
},
{
"identity" : "eudi-lib-ios-iso18013-data-model",
"kind" : "remoteSourceControl",
"location" : "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-model.git",
"state" : {
"revision" : "ef353c447c7716c1fe5f5905ff98e089f5a29d43",
"version" : "0.5.1"
"revision" : "885c4decfc8bdfc5525c993d08a854c2c760058e",
"version" : "0.5.3"
}
},
{
"identity" : "eudi-lib-ios-iso18013-data-transfer",
"kind" : "remoteSourceControl",
"location" : "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer.git",
"state" : {
"revision" : "bb00a75018ad9220880cea3790454ab02fffa6db",
"version" : "0.5.1"
"revision" : "b26036d44f91d46501acfba833bfd9745e7ea1be",
"version" : "0.5.3"
}
},
{
"identity" : "eudi-lib-ios-iso18013-security",
"kind" : "remoteSourceControl",
"location" : "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-security.git",
"state" : {
"revision" : "d3d9935b24b05902f840471bb70f651caa7f4cb5",
"version" : "0.4.0"
"revision" : "48cab4f3fb92b70412a808c1a902d0c00db092e8",
"version" : "0.4.3"
}
},
{
"identity" : "eudi-lib-ios-openid4vci-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-openid4vci-swift.git",
"state" : {
"revision" : "276778d6e4abfefa7df7628bfaf24af7bd9d3a6f",
"version" : "0.10.0"
"revision" : "43d2d1fa97f4c167304f791d0c9439ebef870915",
"version" : "0.10.1"
}
},
{
Expand All @@ -87,17 +87,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-wallet-storage.git",
"state" : {
"revision" : "4c5c7c6c0a55a139e2b7c54eda0bdb9b9e09a4d4",
"version" : "0.4.3"
"revision" : "5423d19017fdfc70d0beffd0eb4e2b688a50837b",
"version" : "0.4.4"
}
},
{
"identity" : "eudi-lib-sdjwt-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/eu-digital-identity-wallet/eudi-lib-sdjwt-swift.git",
"state" : {
"revision" : "3921713715d3ff32dea391bdfd4ec8a080db3921",
"version" : "0.4.0"
"revision" : "a8f7d3a28e893ec4282747bf04617e5b0114fcdc",
"version" : "0.5.1"
}
},
{
Expand Down Expand Up @@ -186,17 +186,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-asn1.git",
"state" : {
"revision" : "7faebca1ea4f9aaf0cda1cef7c43aecd2311ddf6",
"version" : "1.3.0"
"revision" : "ae33e5941bb88d88538d0a6b19ca0b01e6c76dcf",
"version" : "1.3.1"
}
},
{
"identity" : "swift-certificates",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-certificates.git",
"state" : {
"revision" : "1fbb6ef21f1525ed5faf4c95207b9c11bea27e94",
"version" : "1.6.1"
"revision" : "274f8668d3ec5d2892904d8465635c5ea659f767",
"version" : "1.7.0"
}
},
{
Expand Down
8 changes: 4 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.3"),
.package(url: "https://github.com/crspybits/swift-log-file", from: "0.1.0"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer.git", exact: "0.5.1"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-wallet-storage.git", exact: "0.4.3"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-sdjwt-swift.git", exact: "0.4.0"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer.git", exact: "0.5.3"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-wallet-storage.git", exact: "0.4.4"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-sdjwt-swift.git", exact: "0.5.1"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-siop-openid4vp-swift.git", exact: "0.6.4"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-openid4vci-swift.git", exact: "0.10.0"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-openid4vci-swift.git", exact: "0.10.1"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ After issuing a document, the document data and corresponding private key are st
### Issue document by docType
When the document docType to be issued use the `issueDocument(docType:keyOptions:)` method.

* Currently, only mso_mdoc format is supported
* Currently, only mso_mdoc and sd_jwt formats are supported

The following example shows how to issue an EUDI Personal ID document using OpenID4VCI:

Expand All @@ -191,6 +191,14 @@ catch {
// display error
}
```
You can also issue a document by passing configuration `identifier` parameter the `identifier`. The configuration identifiers can be retrieved from the issuer's metadata, using the `getIssuerMetadata` method.

```swift
// 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")
```
### Resolving Credential offer

The library provides the `resolveOfferUrlDocTypes(uriOffer:)` method that resolves the credential offer URI.
Expand Down
34 changes: 19 additions & 15 deletions Sources/EudiWalletKit/EudiWallet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ public final class EudiWallet: ObservableObject, @unchecked Sendable {
@discardableResult public func issueDocument(docType: String?, scope: String?, identifier: String?, keyOptions: KeyOptions? = nil, promptMessage: String? = nil) async throws -> WalletStorage.Document {
let openId4VCIService = try await prepareIssuing(id: UUID().uuidString, docType: docType, displayName: nil, keyOptions: keyOptions, disablePrompt: false, promptMessage: promptMessage)
let data = try await openId4VCIService.issueDocument(docType: docType, scope: scope, identifier: identifier, promptMessage: promptMessage)
return try await finalizeIssuing(data: data.0, docType: docType, format: data.1, issueReq: openId4VCIService.issueReq, openId4VCIService: openId4VCIService)
return try await finalizeIssuing(issueOutcome: data.0, docType: docType, format: data.1, issueReq: openId4VCIService.issueReq, openId4VCIService: openId4VCIService)
}

/// Request a deferred issuance based on a stored deferred document. On success, the deferred document is replaced with the issued document.
Expand All @@ -223,7 +223,7 @@ public final class EudiWallet: ObservableObject, @unchecked Sendable {
let openId4VCIService = await OpenId4VCIService(issueRequest: issueReq, credentialIssuerURL: "", uiCulture: uiCulture, config: self.openID4VciConfig ?? Self.defaultOpenId4VCIConfig, urlSession: urlSession)
let data = try await openId4VCIService.requestDeferredIssuance(deferredDoc: deferredDoc)
guard case .issued(_, _, _) = data else { return deferredDoc }
return try await finalizeIssuing(data: data, docType: deferredDoc.docType, format: deferredDoc.docDataFormat, issueReq: issueReq, openId4VCIService: openId4VCIService)
return try await finalizeIssuing(issueOutcome: data, docType: deferredDoc.docType, format: deferredDoc.docDataFormat, issueReq: issueReq, openId4VCIService: openId4VCIService)
}

/// Resume pending issuance. Supports dynamic isuuance scenario
Expand All @@ -237,31 +237,34 @@ public final class EudiWallet: ObservableObject, @unchecked Sendable {
let openId4VCIService = try await prepareIssuing(id: pendingDoc.id, docType: pendingDoc.docType, displayName: nil, keyOptions: keyOptions, disablePrompt: true, promptMessage: nil)
let outcome = try await openId4VCIService.resumePendingIssuance(pendingDoc: pendingDoc, webUrl: webUrl)
if case .pending(_) = outcome { return pendingDoc }
let res = try await finalizeIssuing(data: outcome, docType: pendingDoc.docType, format: pendingDoc.docDataFormat, issueReq: openId4VCIService.issueReq, openId4VCIService: openId4VCIService)
let res = try await finalizeIssuing(issueOutcome: outcome, docType: pendingDoc.docType, format: pendingDoc.docDataFormat, issueReq: openId4VCIService.issueReq, openId4VCIService: openId4VCIService)
return res
}

func finalizeIssuing(data: IssuanceOutcome, docType: String?, format: DocDataFormat, issueReq: IssueRequest, openId4VCIService: OpenId4VCIService) async throws -> WalletStorage.Document {
var dataToSave: Data
var docTypeToSave: String?
var docMetadata: DocMetadata?
let pds = data.pendingOrDeferredStatus
switch data {
func finalizeIssuing(issueOutcome: IssuanceOutcome, docType: String?, format: DocDataFormat, issueReq: IssueRequest, openId4VCIService: OpenId4VCIService) async throws -> WalletStorage.Document {
var dataToSave: Data; var docTypeToSave: String?
var docMetadata: DocMetadata?; var displayName: String?
let pds = issueOutcome.pendingOrDeferredStatus
switch issueOutcome {
case .issued(let data, let str, let cc):
dataToSave = if format == .cbor, let data { data } else if let str, let data = str.data(using: .utf8) { data } else { Data() }
docMetadata = cc.convertToDocMetadata()
docTypeToSave = if format == .cbor, let data { IssuerSigned(data: [UInt8](data))?.issuerAuth.mso.docType ?? docType } else { docType }
let docTypeOrScope = docType ?? cc.docType ?? cc.scope
docTypeToSave = if format == .cbor, let data { IssuerSigned(data: [UInt8](data))?.issuerAuth.mso.docType ?? docTypeOrScope } else if format == .sdjwt, let str, let ds = str.data(using: .utf8) { StorageManager.getVctFromSdJwt(docData: ds) ?? docTypeOrScope } else { docTypeOrScope }
displayName = cc.display.getName(uiCulture)
case .deferred(let deferredIssuanceModel):
dataToSave = try JSONEncoder().encode(deferredIssuanceModel)
docMetadata = deferredIssuanceModel.configuration.convertToDocMetadata()
docTypeToSave = docType ?? "DEFERRED"
displayName = deferredIssuanceModel.configuration.display.getName(uiCulture)
case .pending(let pendingAuthModel):
dataToSave = try JSONEncoder().encode(pendingAuthModel)
docMetadata = pendingAuthModel.configuration.convertToDocMetadata()
docTypeToSave = docType ?? "PENDING"
displayName = pendingAuthModel.configuration.display.getName(uiCulture)
}
let newDocStatus: WalletStorage.DocumentStatus = data.isDeferred ? .deferred : (data.isPending ? .pending : .issued)
let newDocument = WalletStorage.Document(id: issueReq.id, docType: docTypeToSave, docDataFormat: format, data: dataToSave, secureAreaName: issueReq.secureAreaName, createdAt: Date(), metadata: docMetadata?.toData(), displayName: nil, status: newDocStatus)
let newDocStatus: WalletStorage.DocumentStatus = issueOutcome.isDeferred ? .deferred : (issueOutcome.isPending ? .pending : .issued)
let newDocument = WalletStorage.Document(id: issueReq.id, docType: docTypeToSave, docDataFormat: format, data: dataToSave, secureAreaName: issueReq.secureAreaName, createdAt: Date(), metadata: docMetadata?.toData(), displayName: displayName, status: newDocStatus)
if newDocStatus == .pending { await storage.appendDocModel(newDocument, uiCulture: uiCulture); return newDocument }
try await endIssueDocument(newDocument)
await storage.appendDocModel(newDocument, uiCulture: uiCulture)
Expand Down Expand Up @@ -300,7 +303,7 @@ public final class EudiWallet: ObservableObject, @unchecked Sendable {
if i > 0 { await openId4VCIServices[i].setBindingKey(bindingKey: await openId4VCIServices.first!.bindingKey) }
guard let offer = await OpenId4VCIService.metadataCache[offerUri] else { throw WalletError(description: "offerUri not resolved. resolveOfferDocTypes must be called first")}
guard let docData = try await openId4VCIService.issueDocumentByOfferUrl(offer: offer, authorizedOutcome: auth, configuration: credentialInfos[i], promptMessage: promptMessage, claimSet: claimSet) else { continue }
documents.append(try await finalizeIssuing(data: docData, docType: docTypes[i].docTypeOrScope, format: credentialInfos[i].format, issueReq: openId4VCIService.issueReq, openId4VCIService: openId4VCIService))
documents.append(try await finalizeIssuing(issueOutcome: docData, docType: docTypes[i].docTypeOrScope, format: credentialInfos[i].format, issueReq: openId4VCIService.issueReq, openId4VCIService: openId4VCIService))
}
await OpenId4VCIService.removeOfferFromMetadata(offerUri: offerUri)
return documents
Expand Down Expand Up @@ -395,7 +398,7 @@ public final class EudiWallet: ObservableObject, @unchecked Sendable {
let id = UUID().uuidString
_ = try await pkCose.secureArea.createKey(id: id, keyOptions: nil)
let displayName = dsd.docType == EuPidModel.euPidDocType ? "PID" : (dsd.docType == IsoMdlModel.isoDocType ? "mDL" : dsd.docType)
let docMetadata = DocMetadata(docType: dsd.docType, display: [Display(name: displayName, locale: "en")])
let docMetadata = DocMetadata(credentialIssuerIdentifier: "", configurationIdentifier: "", docType: dsd.docType, display: [DisplayMetadata(name: displayName, localeIdentifier: "en_US")], issuerDisplay: [])
let docSample = Document(id: id, docType: dsd.docType, docDataFormat: .cbor, data: dsd.issData, secureAreaName: SecureAreaRegistry.DeviceSecureArea.software.rawValue, createdAt: Date.distantPast, metadata: docMetadata.toData(), displayName: displayName, status: .issued)
try await storage.storageService.saveDocument(docSample, allowOverwrite: true)
}
Expand Down Expand Up @@ -423,7 +426,8 @@ public final class EudiWallet: ObservableObject, @unchecked Sendable {
let keyData = Dictionary(uniqueKeysWithValues: cborsWithKeys.map(\.sa))
let idsToDocTypes = Dictionary(uniqueKeysWithValues: docs.filter({$0.docType != nil}).map { ($0.id, $0.docType!) })
let docDisplayNames = Dictionary(uniqueKeysWithValues: docs.map { ($0.id, $0.getDisplayNames(uiCulture)) })
parameters = InitializeTransferData(dataFormats: Dictionary(uniqueKeysWithValues: cborsWithKeys.map(\.fmt)), documentData: docData, docDisplayNames: docDisplayNames, privateKeyData: keyData, trustedCertificates: trustedReaderCertificates ?? [], deviceAuthMethod: deviceAuthMethod.rawValue, idsToDocTypes: idsToDocTypes)
let hashingAlgs = Dictionary(uniqueKeysWithValues: docs.map { ($0.id, StorageManager.getHashingAlgorithm(doc: $0))}).compactMapValues { $0 }
parameters = InitializeTransferData(dataFormats: Dictionary(uniqueKeysWithValues: cborsWithKeys.map(\.fmt)), documentData: docData, docDisplayNames: docDisplayNames, privateKeyData: keyData, trustedCertificates: trustedReaderCertificates ?? [], deviceAuthMethod: deviceAuthMethod.rawValue, idsToDocTypes: idsToDocTypes, hashingAlgs: hashingAlgs)
return parameters
}

Expand Down
14 changes: 12 additions & 2 deletions Sources/EudiWalletKit/EudiWalletKit.docc/IssueDocuments.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ If ``EudiWallet/userAuthenticationRequired`` is true, user authentication is req
After issuing a document, the document data and corresponding private key are stored in the wallet storage.

### Issue document by docType
When the document docType to be issued use the ``EudiWallet/issueDocument(docType:keyOptions:promptMessage:)`` method.
When the document docType to be issued use the ``EudiWallet/issueDocument(docType:scope:identifier:keyOptions:promptMessage:)`` method.

__Important Notes__:

Expand All @@ -32,6 +32,16 @@ catch {
// display error
}
```

You can also issue a document by passing configuration `identifier` parameter the `identifier`. The configuration identifiers can be retrieved from the issuer's metadata, using the `getIssuerMetadata` method.

```swift
// 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")
```

### Resolving Credential offer

The library provides the ``EudiWallet/resolveOfferUrlDocTypes(uriOffer:)`` method that resolves the credential offer URI.
Expand Down Expand Up @@ -76,7 +86,7 @@ When the transaction code is provided, the issuance process can be resumed by ca
### Dynamic issuance
Wallet kit supports the Dynamic [PID based issuance](https://github.com/eu-digital-identity-wallet/eudi-wallet-product-roadmap/issues/82)

After calling ``EudiWallet/issueDocument(docType:keyOptions:promptMessage:)`` or ``EudiWallet/issueDocumentsByOfferUrl(offerUri:docTypes:docTypeKeyOptions:txCodeValue:promptMessage:claimSet:)`` 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 ``EudiWallet/resumePendingIssuance(pendingDoc:webUrl:)`` method should be called with the authorization URL provided by the server.
After calling ``EudiWallet/issueDocument(docType:scope:identifier:keyOptions:promptMessage:)`` or ``EudiWallet/issueDocumentsByOfferUrl(offerUri:docTypes:docTypeKeyOptions:txCodeValue:promptMessage:claimSet:)`` 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 ``EudiWallet/resumePendingIssuance(pendingDoc:webUrl:)`` method should be called with the authorization URL provided by the server.
```swift
if let urlString = newDocs.last?.authorizePresentationUrl {
// perform openid4vp presentation using the urlString
Expand Down
4 changes: 2 additions & 2 deletions Sources/EudiWalletKit/EudiWalletKit.docc/SecureAreas.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ For each document type, the wallet developer has the flexibility to define speci

Specifically:

- The ``EudiWallet/issueDocument(docType:format:keyOptions:promptMessage:)`` has been extended to support an
- The ``EudiWallet/issueDocument(docType:scope:identifier:keyOptions:promptMessage:)`` has been extended to support an
additional [keyOptions](https://eu-digital-identity-wallet.github.io/eudi-lib-ios-iso18013-data-model/documentation/mdocdatamodel18013/keyoptions) optional parameter to specify the secure area name and other key options for the key creation.
- The ``EudiWallet/issueDocumentsByOfferUrl(offerUri:docTypes:docTypeKeyOptions:txCodeValue:format:promptMessage:claimSet:)`` has been extended to support a `docTypeKeyOptions` to specify the secure area name and other key options for each doc type.
- The ``EudiWallet/issueDocumentsByOfferUrl(offerUri:docTypes:docTypeKeyOptions:txCodeValue:promptMessage:claimSet:)`` has been extended to support a `docTypeKeyOptions` to specify the secure area name and other key options for each doc type.

```swift
// For keychain saved keys, the iOS will automatically present a biometric or user PIN screen to authorize key usage for PID documents
Expand Down
Loading

0 comments on commit 8a2996d

Please sign in to comment.