Skip to content

Commit

Permalink
Merge pull request #107 from niscy-eudiw/fixes
Browse files Browse the repository at this point in the history
Update EudiWalletKit extensions and services
  • Loading branch information
phisakel authored Oct 11, 2024
2 parents a8cb2c5 + 716125d commit 3f5dc84
Show file tree
Hide file tree
Showing 12 changed files with 107 additions and 107 deletions.
18 changes: 9 additions & 9 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "52a6b211db7b42edef80ca0a011ade90c5c72e8348f56a62085424df662c0bb9",
"originHash" : "b5a574ae9a5a190e024df6467d5fb5bcb9df780cacf6d2096ac36b48f0041542",
"pins" : [
{
"identity" : "blueecc",
Expand All @@ -24,26 +24,26 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-model.git",
"state" : {
"revision" : "39ba199744ad478544fbad3a73c4a47677f34ec7",
"version" : "0.3.2"
"revision" : "c1b4383d6fc3387a8ed4c79177548624c4e34e3a",
"version" : "0.3.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" : "abc00ef942e9b02e73786726661bd71eb2876b6e",
"version" : "0.3.2"
"revision" : "2f00a68370572f914997706a29d15d6fc0b89ceb",
"version" : "0.3.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" : "89cccb0dec4e675d3d83e9e78076822d98d024bb",
"version" : "0.2.5"
"revision" : "13d65a1010ee9e6219f8bccbab6eb32f67405d86",
"version" : "0.2.6"
}
},
{
Expand Down Expand Up @@ -78,8 +78,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-wallet-storage.git",
"state" : {
"revision" : "ef829a7eb0d3db82a4dd4bdabc80c930e86d152e",
"version" : "0.2.9"
"revision" : "9fe492a8648877cd3bc39baed50a1b85d4a50273",
"version" : "0.3.0"
}
},
{
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ 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.3.2"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-wallet-storage.git", exact: "0.2.9"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer.git", exact: "0.3.3"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-wallet-storage.git", exact: "0.3.0"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-siop-openid4vp-swift.git", exact: "0.4.0"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-openid4vci-swift.git", exact: "0.6.0"),
],
Expand Down
76 changes: 34 additions & 42 deletions Sources/EudiWalletKit/EudiWallet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import MdocDataTransfer18013
import WalletStorage
import LocalAuthentication
import CryptoKit
import OpenID4VCI
@preconcurrency import OpenID4VCI
import SwiftCBOR
import Logging
// ios specific imports
Expand All @@ -31,17 +31,14 @@ import UIKit
#endif

/// User wallet implementation
@MainActor
public final class EudiWallet: ObservableObject {
public final class EudiWallet: ObservableObject, @unchecked Sendable {
/// Storage manager instance
public private(set) var storage: StorageManager
var storageService: any WalletStorage.DataStorageService { storage.storageService }
/// Instance of the wallet initialized with default parameters
public static private(set) var standard: EudiWallet = try! EudiWallet()
/// The [service](https://developer.apple.com/documentation/security/ksecattrservice) used to store documents. Use a different service than the default one if you want to store documents in a different location.
public var serviceName: String { didSet { Task { try await setServiceParams() } } }
public private(set) var storage: StorageManager!
public private(set) var serviceName: String
/// The [access group](https://developer.apple.com/documentation/security/ksecattraccessgroup) that documents are stored in.
public var accessGroup: String? { didSet { Task { try await setServiceParams() } } }
public private(set) var accessGroup: String?
/// Optional model factory type to create custom stronly-typed models
public private(set) var modelFactory: (any MdocModelFactory)?
/// Whether user authentication via biometrics or passcode is required before sending user data
public var userAuthenticationRequired: Bool
/// Trusted root certificates to validate the reader authentication certificate included in the proximity request
Expand All @@ -58,8 +55,6 @@ public final class EudiWallet: ObservableObject {
public var openID4VciConfig: OpenId4VCIConfig?
/// Use iPhone Secure Enclave to protect keys and perform cryptographic operations. Defaults to true (if available)
public var useSecureEnclave: Bool { didSet { if !SecureEnclave.isAvailable { useSecureEnclave = false } } }
/// Optional model factory type to create custom stronly-typed models
public var modelFactory: (any MdocModelFactory.Type)? { didSet { storage.modelFactory = modelFactory } }
/// This variable can be used to set a custom URLSession for network requests.
public var urlSession: URLSession
/// If not-nil, logging to the specified log file name will be configured
Expand All @@ -69,13 +64,12 @@ public final class EudiWallet: ObservableObject {
public static let defaultOpenId4VCIConfig = OpenId4VCIConfig(clientId: defaultClientId, authFlowRedirectionURI: defaultOpenID4VciRedirectUri)
public static let defaultServiceName = "eudiw"
/// Initialize a wallet instance. All parameters are optional.
public init(storageType: StorageType = .keyChain, serviceName: String? = nil, accessGroup: String? = nil, trustedReaderCertificates: [Data]? = nil, userAuthenticationRequired: Bool = true, verifierApiUri: String? = nil, openID4VciIssuerUrl: String? = nil, openID4VciConfig: OpenId4VCIConfig? = nil, urlSession: URLSession? = nil, logFileName: String? = nil, modelFactory: (any MdocModelFactory.Type)? = nil) throws {
///
public init(storageType: StorageType = .keyChain, serviceName: String? = nil, accessGroup: String? = nil, trustedReaderCertificates: [Data]? = nil, userAuthenticationRequired: Bool = true, verifierApiUri: String? = nil, openID4VciIssuerUrl: String? = nil, openID4VciConfig: OpenId4VCIConfig? = nil, urlSession: URLSession? = nil, logFileName: String? = nil, modelFactory: (any MdocModelFactory)? = nil) throws {
try Self.validateServiceParams(serviceName: serviceName)
self.serviceName = serviceName ?? Self.defaultServiceName
self.accessGroup = accessGroup
let keyChainObj = KeyChainStorageService(serviceName: self.serviceName, accessGroup: accessGroup)
let storageService = switch storageType { case .keyChain:keyChainObj }
storage = StorageManager(storageService: storageService, modelFactory: modelFactory)
self.modelFactory = modelFactory
self.trustedReaderCertificates = trustedReaderCertificates
self.userAuthenticationRequired = userAuthenticationRequired
#if DEBUG
Expand All @@ -87,6 +81,14 @@ public final class EudiWallet: ObservableObject {
self.urlSession = urlSession ?? URLSession.shared
self.logFileName = logFileName
useSecureEnclave = SecureEnclave.isAvailable
storage = self.getStorage()
}

func getStorage() -> StorageManager {
guard storage == nil else { return self.storage }
let keyChainObj = KeyChainStorageService(serviceName: serviceName, accessGroup: accessGroup)
self.storage = StorageManager(storageService: keyChainObj, modelFactory: self.modelFactory)
return self.storage
}

/// Helper method to return a file URL from a file name.
Expand All @@ -106,14 +108,6 @@ public final class EudiWallet: ObservableObject {
}
}

private func setServiceParams() async throws {
if let keyChainObj = storage.storageService as? KeyChainStorageService {
try Self.validateServiceParams(serviceName: self.serviceName)
await keyChainObj.initialize(serviceName, accessGroup)
}
}


/// Get the contents of a log file stored in the caches directory
/// - Parameter fileName: A file name
/// - Returns: The file contents
Expand Down Expand Up @@ -165,7 +159,7 @@ public final class EudiWallet: ObservableObject {
return try await beginIssueDocument(id: id, privateKeyType: useSecureEnclave ? .secureEnclaveP256 : .x963EncodedP256, saveToStorage: false)
}, disabled: !userAuthenticationRequired || docType == nil, dismiss: {}, localizedReason: promptMessage ?? NSLocalizedString("issue_document", comment: "").replacingOccurrences(of: "{docType}", with: NSLocalizedString(displayName ?? docType ?? "", comment: "")))
guard let issueReq else { throw LAError(.userCancel)}
let openId4VCIService = OpenId4VCIService(issueRequest: issueReq, credentialIssuerURL: openID4VciIssuerUrl, config: openID4VciConfig ?? OpenId4VCIConfig(clientId: Self.defaultClientId, authFlowRedirectionURI: Self.defaultOpenID4VciRedirectUri), urlSession: urlSession)
let openId4VCIService = await OpenId4VCIService(issueRequest: issueReq, credentialIssuerURL: openID4VciIssuerUrl, config: openID4VciConfig ?? OpenId4VCIConfig(clientId: Self.defaultClientId, authFlowRedirectionURI: Self.defaultOpenID4VciRedirectUri), urlSession: urlSession)
return (issueReq, openId4VCIService, id)
}

Expand All @@ -192,8 +186,7 @@ public final class EudiWallet: ObservableObject {
guard deferredDoc.status == .deferred else { throw WalletError(description: "Invalid document status") }
guard let pkt = deferredDoc.privateKeyType, let pk = deferredDoc.privateKey, let format = DataFormat(deferredDoc.docDataType) else { throw WalletError(description: "Invalid document") }
let issueReq = try IssueRequest(id: deferredDoc.id, docType: deferredDoc.docType, privateKeyType: pkt, keyData: pk)
let openId4VCIService = OpenId4VCIService(issueRequest: issueReq, credentialIssuerURL: "", config: self.openID4VciConfig ?? Self.defaultOpenId4VCIConfig, urlSession: urlSession)
openId4VCIService.usedSecureEnclave = deferredDoc.privateKeyType == .secureEnclaveP256
let openId4VCIService = await OpenId4VCIService(issueRequest: issueReq, credentialIssuerURL: "", 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(id: deferredDoc.id, data: data, docType: deferredDoc.docType, format: format, issueReq: issueReq, openId4VCIService: openId4VCIService)
Expand All @@ -209,9 +202,8 @@ public final class EudiWallet: ObservableObject {
guard pendingDoc.status == .pending else { throw WalletError(description: "Invalid document status") }
guard let pkt = pendingDoc.privateKeyType, let pk = pendingDoc.privateKey, let format = DataFormat(pendingDoc.docDataType) else { throw WalletError(description: "Invalid document") }
let (_, openId4VCIService, _) = try await prepareIssuing(docType: nil, displayName: nil, promptMessage: nil)
openId4VCIService.usedSecureEnclave = pendingDoc.privateKeyType == .secureEnclaveP256
let issueReq = try IssueRequest(id: pendingDoc.id, docType: pendingDoc.docType, privateKeyType: pkt, keyData: pk)
try openId4VCIService.initSecurityKeys(openId4VCIService.usedSecureEnclave)
try await openId4VCIService.initSecurityKeys(pendingDoc.privateKeyType == .secureEnclaveP256)
let outcome = try await openId4VCIService.resumePendingIssuance(pendingDoc: pendingDoc, webUrl: webUrl)
guard case .issued(_, _) = outcome else { return pendingDoc }
let res = try await finalizeIssuing(id: pendingDoc.id, data: outcome, docType: pendingDoc.docType, format: format, issueReq: issueReq, openId4VCIService: openId4VCIService)
Expand Down Expand Up @@ -240,12 +232,12 @@ public final class EudiWallet: ObservableObject {
docTypeToSave = docType ?? "PENDING"
}
let newDocStatus: WalletStorage.DocumentStatus = data.isDeferred ? .deferred : (data.isPending ? .pending : .issued)
let newDocument = WalletStorage.Document(id: id, docType: docTypeToSave, docDataType: ddt, data: dataToSave, privateKeyType: (openId4VCIService.usedSecureEnclave ?? true) ? .secureEnclaveP256 : .x963EncodedP256, privateKey: issueReq.keyData, createdAt: Date(), displayName: displayName, status: newDocStatus)
if newDocStatus == .pending { storage.appendDocModel(newDocument); return newDocument }
let newDocument = WalletStorage.Document(id: id, docType: docTypeToSave, docDataType: ddt, data: dataToSave, privateKeyType: issueReq.privateKeyType, privateKey: issueReq.keyData, createdAt: Date(), displayName: displayName, status: newDocStatus)
if newDocStatus == .pending { await storage.appendDocModel(newDocument); return newDocument }
try await issueReq.saveTo(storageService: storage.storageService, status: newDocStatus)
try await endIssueDocument(newDocument)
storage.appendDocModel(newDocument)
storage.refreshPublishedVars()
await storage.appendDocModel(newDocument)
await storage.refreshPublishedVars()
if pds == nil { try await storage.removePendingOrDeferredDoc(id: id) }
return newDocument
}
Expand Down Expand Up @@ -278,7 +270,6 @@ public final class EudiWallet: ObservableObject {
var documents = [WalletStorage.Document]()
for (i, docData) in docsData.enumerated() {
if i > 0 { (issueReq, openId4VCIService, id) = try await prepareIssuing(docType: nil, displayName: nil) }
openId4VCIService.usedSecureEnclave = useSecureEnclave && SecureEnclave.isAvailable
documents.append(try await finalizeIssuing(id: id, data: docData, docType: docData.isDeferred ? docTypes[i].docType : nil, format: format, issueReq: issueReq, openId4VCIService: openId4VCIService))
}
return documents
Expand All @@ -298,7 +289,7 @@ public final class EudiWallet: ObservableObject {
/// End issuing by saving the issuing document (and its private key) in storage
/// - Parameter issued: The issued document
public func endIssueDocument(_ issued: WalletStorage.Document) async throws {
try await storage.storageService.saveDocument(issued, allowOverwrite: true)
try await storage.storageService.saveDocument(issued, allowOverwrite: true)
}

/// Load documents with a specific status from storage
Expand Down Expand Up @@ -366,13 +357,13 @@ public final class EudiWallet: ObservableObject {
/// The mdoc data are stored in wallet storage as documents
/// - Parameter sampleDataFiles: Names of sample files provided in the app bundle
public func loadSampleData(sampleDataFiles: [String]? = nil) async throws {
try? await storageService.deleteDocuments(status: .issued)
try? await storage.storageService.deleteDocuments(status: .issued)
let docSamples = (sampleDataFiles ?? ["EUDI_sample_data"]).compactMap { Data(name:$0) }
.compactMap(SignUpResponse.decomposeCBORSignupResponse(data:)).flatMap {$0}
.map { Document(docType: $0.docType, docDataType: .cbor, data: $0.issData, privateKeyType: .x963EncodedP256, privateKey: $0.pkData, createdAt: Date.distantPast, modifiedAt: nil, displayName: $0.docType == EuPidModel.euPidDocType ? "PID" : ($0.docType == IsoMdlModel.isoDocType ? "mDL" : $0.docType), status: .issued) }
do {
for docSample in docSamples {
try await storageService.saveDocument(docSample, allowOverwrite: true)
try await storage.storageService.saveDocument(docSample, allowOverwrite: true)
}
try await storage.loadDocuments(status: .issued)
} catch {
Expand All @@ -390,7 +381,7 @@ public final class EudiWallet: ObservableObject {
var parameters: [String: Any]
switch dataFormat {
case .cbor:
guard var docs = try await storageService.loadDocuments(status: .issued), docs.count > 0 else { throw WalletError(description: "No documents found") }
guard var docs = try await storage.storageService.loadDocuments(status: .issued), docs.count > 0 else { throw WalletError(description: "No documents found") }
if let docType { docs = docs.filter { $0.docType == docType} }
if let docType { guard docs.count > 0 else { throw WalletError(description: "No documents of type \(docType) found") } }
let cborsWithKeys = docs.compactMap { $0.getCborData() }
Expand Down Expand Up @@ -436,8 +427,8 @@ public final class EudiWallet: ObservableObject {
/// - docType: DocType of documents to present (optional)
/// - dataFormat: Exchanged data ``Format`` type
/// - Returns: A `PresentationSession` instance,
public func beginPresentation(service: any PresentationService) -> PresentationSession {
PresentationSession(presentationService: service, docIdAndTypes: storage.getDocIdsToTypes(), userAuthenticationRequired: userAuthenticationRequired)
public func beginPresentation(service: any PresentationService) async -> PresentationSession {
return PresentationSession(presentationService: service, docIdAndTypes: storage.getDocIdsToTypes(), userAuthenticationRequired: userAuthenticationRequired)
}

/// Perform an action after user authorization via TouchID/FaceID/Passcode
Expand Down Expand Up @@ -475,8 +466,9 @@ public final class EudiWallet: ObservableObject {
if context.canEvaluatePolicy(policy, error: &error) {
do {
let success = try await context.evaluatePolicy(policy, localizedReason: localizedReason)
if success {
while !UIApplication.shared.connectedScenes.allSatisfy({ $0.activationState == .foregroundActive }) {
if success, let scene = await UIApplication.shared.connectedScenes.first {
let activateState = await scene.activationState
while activateState != .foregroundActive && activateState != .foregroundInactive {
// Delay the task by 0.5 second if not foreground
try await Task.sleep(nanoseconds: 500_000_000)
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/EudiWalletKit/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,5 @@ extension WalletStorage.Document {
return urlString
}
}

extension ClaimSet: @retroactive @unchecked Sendable {}
4 changes: 2 additions & 2 deletions Sources/EudiWalletKit/Services/BlePresentationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import MdocDataTransfer18013
/// Implements proximity attestation presentation with QR to BLE data transfer

/// Implementation is based on the ISO/IEC 18013-5 specification
@MainActor
public class BlePresentationService : PresentationService {

public final class BlePresentationService: @unchecked Sendable, PresentationService {
var bleServerTransfer: MdocGattServer
public var status: TransferStatus = .initializing
var continuationQrCode: CheckedContinuation<String, Error>?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Foundation
import MdocDataTransfer18013

/// Fault presentation service. Used to communicate error state to the user
public class FaultPresentationService: PresentationService {
public final class FaultPresentationService: @unchecked Sendable, PresentationService {
public var status: TransferStatus = .error
public var flow: FlowType = .other
var error: Error
Expand Down
7 changes: 4 additions & 3 deletions Sources/EudiWalletKit/Services/OpenId4VciService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ import Security
import WalletStorage
import SwiftCBOR

extension CredentialIssuerSource: @unchecked Sendable {}
extension CredentialIssuerSource: @retroactive @unchecked Sendable {}

@MainActor
public class OpenId4VCIService: NSObject, ASWebAuthenticationPresentationContextProviding {
public class OpenId4VCIService: NSObject, @unchecked Sendable, ASWebAuthenticationPresentationContextProviding {
let issueReq: IssueRequest
let credentialIssuerURL: String
var bindingKey: BindingKey!
Expand All @@ -41,6 +40,7 @@ public class OpenId4VCIService: NSObject, ASWebAuthenticationPresentationContext
var parRequested: ParRequested?

init(issueRequest: IssueRequest, credentialIssuerURL: String, config: OpenId4VCIConfig, urlSession: URLSession) {
usedSecureEnclave = issueRequest.privateKeyType == .secureEnclaveP256 && SecureEnclave.isAvailable
self.issueReq = issueRequest
self.credentialIssuerURL = credentialIssuerURL
self.urlSession = urlSession
Expand Down Expand Up @@ -335,6 +335,7 @@ public class OpenId4VCIService: NSObject, ASWebAuthenticationPresentationContext
}

func requestDeferredIssuance(deferredDoc: WalletStorage.Document) async throws -> IssuanceOutcome {
usedSecureEnclave = deferredDoc.privateKeyType == .secureEnclaveP256
let model = try JSONDecoder().decode(DeferredIssuanceModel.self, from: deferredDoc.data)
let issuer = try getIssuerForDeferred(data: model)
let authorized: AuthorizedRequest = .noProofRequired(accessToken: model.accessToken, refreshToken: model.refreshToken, credentialIdentifiers: nil, timeStamp: model.timeStamp)
Expand Down
Loading

0 comments on commit 3f5dc84

Please sign in to comment.