diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1dc5156..ccd66df 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,8 +5,6 @@ "ghcr.io/devcontainers/features/common-utils:2": { "installZsh": "false", "username": "vscode", - "userUid": "1000", - "userGid": "1000", "upgradePackages": "false" }, "ghcr.io/devcontainers/features/git:1": { diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 9ea3e3b..c5a7066 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -2,9 +2,12 @@ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift name: Swift - -on: [push] - +on: + pull_request: + types: [opened, reopened] + push: + branches: ['main'] + tags: [ v* ] jobs: build: diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletStorage.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletStorage.xcscheme new file mode 100644 index 0000000..ab87ffa --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletStorage.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletStorageTests.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletStorageTests.xcscheme new file mode 100644 index 0000000..fe0f76f --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletStorageTests.xcscheme @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.resolved b/Package.resolved index 65bd7cb..7cb6874 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "5899d8de2075d7a83e61827a33fdb4419671b2313f3f6131cd2e3ed570f58a7d", + "originHash" : "fcb82ddcec78f864b92635b39a134e6c2ebc5f4f04274dc94f535c265aaae092", "pins" : [ { "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" : "c1b4383d6fc3387a8ed4c79177548624c4e34e3a", - "version" : "0.3.3" + "revision" : "29f30a92427733db0c7b9cea9616607a1df24284", + "version" : "0.4.0" } }, { diff --git a/Package.swift b/Package.swift index 6286c22..88705b0 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-log.git", from: "1.5.3"), - .package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-model.git", exact: "0.3.3"), + .package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-model.git", exact: "0.4.0"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. diff --git a/Sources/WalletStorage/Document.swift b/Sources/WalletStorage/Document.swift index 2515d4e..0500798 100644 --- a/Sources/WalletStorage/Document.swift +++ b/Sources/WalletStorage/Document.swift @@ -19,13 +19,12 @@ import MdocDataModel18013 /// wallet document structure public struct Document: DocumentProtocol, Sendable { - public init(id: String = UUID().uuidString, docType: String, docDataType: DocDataType, data: Data, privateKeyType: PrivateKeyType?, privateKey: Data?, createdAt: Date?, modifiedAt: Date? = nil, displayName: String?, status: DocumentStatus) { + public init(id: String = UUID().uuidString, docType: String, docDataType: DocDataType, data: Data, secureAreaName: String?, createdAt: Date?, modifiedAt: Date? = nil, displayName: String?, status: DocumentStatus) { self.id = id self.docType = docType self.docDataType = docDataType self.data = data - self.privateKeyType = privateKeyType - self.privateKey = privateKey + self.secureAreaName = secureAreaName self.createdAt = createdAt ?? Date() self.modifiedAt = modifiedAt self.displayName = displayName @@ -36,8 +35,7 @@ public struct Document: DocumentProtocol, Sendable { public let docType: String public let data: Data public let docDataType: DocDataType - public let privateKeyType: PrivateKeyType? - public let privateKey: Data? + public let secureAreaName: String? public let createdAt: Date public let modifiedAt: Date? public let displayName: String? @@ -48,12 +46,8 @@ public struct Document: DocumentProtocol, Sendable { /// get CBOR data and private key from document public func getCborData() -> (iss: (String, IssuerSigned), dpk: (String, CoseKeyPrivate))? { switch docDataType { - case .signupResponseJson: - guard let sr = data.decodeJSON(type: SignUpResponse.self), let dr = sr.deviceResponse, let iss = dr.documents?.first?.issuerSigned, let dpk = sr.devicePrivateKey else { return nil } - let randomId = UUID().uuidString - return ((randomId, iss), (randomId, dpk)) case .cbor: - guard let iss = IssuerSigned(data: [UInt8](data)), let privateKeyType, let privateKey, let dpk = try? IssueRequest(id: id, privateKeyType: privateKeyType, keyData: privateKey).toCoseKeyPrivate() else { return nil } + guard let iss = IssuerSigned(data: [UInt8](data)), case let dpk = CoseKeyPrivate(privateKeyId: id, secureArea: SecureAreaRegistry.shared.get(name: secureAreaName)) else { return nil } return ((id, iss), (id, dpk)) case .sjwt: fatalError("Format \(docDataType) not implemented") diff --git a/Sources/WalletStorage/Enumerations.swift b/Sources/WalletStorage/Enumerations.swift index 2f0ef7c..69c7b16 100644 --- a/Sources/WalletStorage/Enumerations.swift +++ b/Sources/WalletStorage/Enumerations.swift @@ -19,9 +19,10 @@ import Foundation /// type of data to save in storage /// ``doc``: Document data /// ``key``: Private-key -public enum SavedKeyChainDataType: String, Sendable { +public enum SavedKeyChainDataType: String, Sendable, CaseIterable { case doc = "sdoc" case key = "skey" + case keyInfo = "skei" } /// Format of document data @@ -32,19 +33,7 @@ public enum SavedKeyChainDataType: String, Sendable { public enum DocDataType: String, Sendable { case cbor = "cbor" case sjwt = "sjwt" - case signupResponseJson = "srjs" -} - -/// Format of private key -/// ``derEncodedP256``: DER encoded -/// ``pemStringDataP256`` PEM string encoded as utf8 -/// ``x963EncodedP256``: ANSI x9.63 representation (default) -/// ``secureEnclaveP256``: data representation for the secure enclave -public enum PrivateKeyType: String, Sendable { - case derEncodedP256 = "dep2" - case pemStringDataP256 = "pep2" - case x963EncodedP256 = "x9p2" - case secureEnclaveP256 = "sep2" + // case signupResponseJson = "srjs" } diff --git a/Sources/WalletStorage/IssueRequest.swift b/Sources/WalletStorage/IssueRequest.swift index 88b5064..80cfae0 100644 --- a/Sources/WalletStorage/IssueRequest.swift +++ b/Sources/WalletStorage/IssueRequest.swift @@ -21,87 +21,22 @@ import MdocDataModel18013 /// Issue request structure public struct IssueRequest: Sendable { public var id: String - public var docType: String? - public var keyData: Data - public var privateKeyType: PrivateKeyType - + public var keyOptions: KeyOptions? + public var secureArea: SecureArea + public var secureAreaName: String { type(of: secureArea).name } /// Initialize issue request with id /// /// - Parameters: /// - id: a key identifier (uuid) - public init(id: String = UUID().uuidString, docType: String? = nil, privateKeyType: PrivateKeyType = .secureEnclaveP256, keyData: Data? = nil) throws { - self.id = id - self.docType = docType - self.privateKeyType = privateKeyType - if let keyData { - self.keyData = keyData - // key-data already created, exit - return - } - switch privateKeyType { - case .derEncodedP256: - let p256 = P256.KeyAgreement.PrivateKey() - self.keyData = p256.derRepresentation - case .pemStringDataP256: - let p256 = P256.KeyAgreement.PrivateKey() - self.keyData = p256.pemRepresentation.data(using: .utf8)! - case .x963EncodedP256: - let p256 = P256.KeyAgreement.PrivateKey() - self.keyData = p256.x963Representation - case .secureEnclaveP256: - let secureEnclaveKey = try SecureEnclave.P256.KeyAgreement.PrivateKey() - self.keyData = secureEnclaveKey.dataRepresentation - } - logger.info("Created private key of type \(privateKeyType)") - if let docType { logger.info(" and docType: \(docType)") } - } - - public func saveTo(storageService: any DataStorageService, status: DocumentStatus) async throws { - // save key data to storage with id - logger.info("Saving Issue request with id: \(id) and document status: \(status)") - let docKey = Document(id: id, docType: docType ?? "P256", docDataType: .cbor, data: Data(), privateKeyType: privateKeyType, privateKey: keyData, createdAt: Date(), displayName: nil, status: status) - try await storageService.saveDocument(docKey, allowOverwrite: true) - } - - public mutating func loadFrom(storageService: any DataStorageService, id: String, status: DocumentStatus) async throws { - guard let doc = try await storageService.loadDocument(id: id, status: status), let pk = doc.privateKey, let pkt = doc.privateKeyType else { return } + public init(id: String = UUID().uuidString, keyOptions: KeyOptions? = nil) throws { self.id = id - keyData = pk - privateKeyType = pkt - } - - public func toCoseKeyPrivate() throws -> CoseKeyPrivate { - switch privateKeyType { - case .derEncodedP256: - let p256 = try P256.KeyAgreement.PrivateKey(derRepresentation: keyData) - return CoseKeyPrivate(privateKeyx963Data: p256.x963Representation, crv: .p256) - case .x963EncodedP256: - let p256 = try P256.KeyAgreement.PrivateKey(x963Representation: keyData) - return CoseKeyPrivate(privateKeyx963Data: p256.x963Representation, crv: .p256) - case .pemStringDataP256: - let p256 = try P256.KeyAgreement.PrivateKey(pemRepresentation: String(data: keyData, encoding: .utf8)!) - return CoseKeyPrivate(privateKeyx963Data: p256.x963Representation, crv: .p256) - case .secureEnclaveP256: - let se256 = try SecureEnclave.P256.KeyAgreement.PrivateKey(dataRepresentation: keyData) - return CoseKeyPrivate(publicKeyx963Data: se256.publicKey.x963Representation, secureEnclaveKeyID: keyData) - } + self.keyOptions = keyOptions + secureArea = SecureAreaRegistry.shared.get(name: keyOptions?.secureAreaName) } - public func getPublicKeyPEM() throws -> String { - switch privateKeyType { - case .derEncodedP256: - let p256 = try P256.KeyAgreement.PrivateKey(derRepresentation: keyData) - return p256.publicKey.pemRepresentation - case .pemStringDataP256: - let p256 = try P256.KeyAgreement.PrivateKey(pemRepresentation: String(data: keyData, encoding: .utf8)!) - return p256.publicKey.pemRepresentation - case .x963EncodedP256: - let p256 = try P256.KeyAgreement.PrivateKey(x963Representation: keyData) - return p256.publicKey.pemRepresentation - case .secureEnclaveP256: - let se256 = try SecureEnclave.P256.KeyAgreement.PrivateKey(dataRepresentation: keyData) - return se256.publicKey.pemRepresentation - } + public func createKey() async throws -> CoseKey { + let res = try await secureArea.createKey(id: id, keyOptions: keyOptions) + return res } } diff --git a/Sources/WalletStorage/KeyChainSecureKeyStorage.swift b/Sources/WalletStorage/KeyChainSecureKeyStorage.swift new file mode 100644 index 0000000..811563e --- /dev/null +++ b/Sources/WalletStorage/KeyChainSecureKeyStorage.swift @@ -0,0 +1,57 @@ +// +// ks.swift +// WalletStorage +// +// Created by ffeli on 25/10/2024. +// + +import Foundation +import MdocDataModel18013 + +public actor KeyChainSecureKeyStorage: SecureKeyStorage { + public let serviceName: String + public let accessGroup: String? + + public init(serviceName: String, accessGroup: String?) { + self.serviceName = serviceName + self.accessGroup = accessGroup + } + + static func keyChainDataValue(key: String, value: Any) -> (String, Data)? { + if let v = value as? String { (key, v.data(using: .utf8)!) } else if let v = value as? Data { (key, v) } else { nil } + } + + public func readKeyInfo(id: String) throws -> [String : Data] { + guard let dicts = try KeyChainStorageService.loadDocumentsData(serviceName: serviceName, accessGroup: accessGroup, id: id, status: .issued, dataToLoadType: .keyInfo), !dicts.isEmpty else { return [:] } + return Dictionary(uniqueKeysWithValues: dicts.first!.compactMap(Self.keyChainDataValue)) + } + + public func readKeyData(id: String) throws -> [String : Data] { + guard let dicts = try KeyChainStorageService.loadDocumentsData(serviceName: serviceName, accessGroup: accessGroup, id: id, status: .issued, dataToLoadType: .key), !dicts.isEmpty else { return [:] } + return Dictionary(uniqueKeysWithValues: dicts.first!.compactMap(Self.keyChainDataValue)) + } + + // save key public info + public func writeKeyInfo(id: String, dict: [String: Data]) throws { + func setDictValues(_ d: inout [String: Any]) { for (k, v) in dict { d[k] = if k == kSecValueData as String { v } else { String(data: v, encoding: .utf8) ?? "" } } } + try KeyChainStorageService.saveDocumentData(serviceName: serviceName, accessGroup: accessGroup, id: id, status: .issued, dataType: .keyInfo, setDictValues: setDictValues, allowOverwrite: true) + } + + // save key sensitive info + public func writeKeyData(id: String, dict: [String: Data], keyOptions: KeyOptions?) throws { + func setDictValues(_ d: inout [String: Any]) { + for (k, v) in dict { d[k] = if k == kSecValueData as String { v } else { String(data: v, encoding: .utf8) ?? "" } } + d[kSecAttrAccessControl as String] = SecAccessControlCreateWithFlags(nil, keyOptions?.accessProtection?.constant ?? kSecAttrAccessibleWhenUnlocked, keyOptions?.accessControl?.flags ?? [], nil)! as Any + } + try KeyChainStorageService.saveDocumentData(serviceName: serviceName, accessGroup: accessGroup, id: id, status: .issued, dataType: .key, setDictValues: setDictValues, allowOverwrite: true) + } + + // delete key info and data + public func deleteKey(id: String) throws { + logger.info("Delete key with id \(id)") + try? KeyChainStorageService.deleteDocumentData(serviceName: serviceName, accessGroup: accessGroup, id: id, docStatus: .issued, dataType: .keyInfo) + try KeyChainStorageService.deleteDocumentData(serviceName: serviceName, accessGroup: accessGroup, id: id, docStatus: .issued, dataType: .key) + } + +} + diff --git a/Sources/WalletStorage/KeyChainStorageService.swift b/Sources/WalletStorage/KeyChainStorageService.swift index d3c8aed..757bbe0 100644 --- a/Sources/WalletStorage/KeyChainStorageService.swift +++ b/Sources/WalletStorage/KeyChainStorageService.swift @@ -15,10 +15,11 @@ */ import Foundation +import MdocDataModel18013 /// Implements key-chain storage /// Documents are saved as a pair of generic password items (document data and private key) /// For implementation details see [Apple documentation](https://developer.apple.com/documentation/security/ksecclassgenericpassword) -public actor KeyChainStorageService: DataStorageService { +public actor KeyChainStorageService: DataStorageService { public init(serviceName: String, accessGroup: String? = nil) { self.serviceName = serviceName @@ -43,23 +44,18 @@ public actor KeyChainStorageService: DataStorageService { logger.info("Load documents with status: \(status)") return try loadDocuments(id: nil, status: status) } - // use is-negative to denote type of data - static func isDocumentDataRow(_ d: [String: Any]) -> Bool { if let b = d[kSecAttrIsNegative as String] as? Bool { !b } else { true } } - static func isPrivateKeyRow(_ d: [String: Any]) -> Bool { if let b = d[kSecAttrIsNegative as String] as? Bool { b } else { false } } /// Gets all documents /// - Parameters: /// - Returns: The documents stored in keychain under the serviceName func loadDocuments(id: String?, status: DocumentStatus) throws -> [Document]? { - guard var dicts1 = try loadDocumentsData(id: id, docStatus: status) else { return nil } - let dicts2 = dicts1.filter(Self.isPrivateKeyRow) - dicts1 = dicts1.filter(Self.isDocumentDataRow) - let documents = dicts1.compactMap { d1 in Self.makeDocument(dict1: d1, dict2: dicts2.first(where: { d2 in d1[kSecAttrAccount as String] as! String == d2[kSecAttrAccount as String] as! String}), status: status) } + guard let dicts = try Self.loadDocumentsData(serviceName: serviceName, accessGroup: accessGroup, id: id, status: status, dataToLoadType: .doc) else { return nil } + let documents = dicts.compactMap { d in Self.makeDocument(dict: d, status: status) } return documents } - func loadDocumentsData(id: String?, docStatus: DocumentStatus, dataToLoadType: SavedKeyChainDataType = .doc) throws -> [[String: Any]]? { - let query = makeQuery(id: id, bForSave: false, status: docStatus, dataType: dataToLoadType) + nonisolated static func loadDocumentsData(serviceName: String, accessGroup: String?, id: String?, status: DocumentStatus, dataToLoadType: SavedKeyChainDataType) throws -> [[String: Any]]? { + let query = Self.makeQuery(serviceName: serviceName, accessGroup: accessGroup, id: id, bForSave: false, status: status, dataType: dataToLoadType) var result: CFTypeRef? let status = SecItemCopyMatching(query as CFDictionary, &result) if status == errSecItemNotFound { return nil } @@ -68,11 +64,9 @@ public actor KeyChainStorageService: DataStorageService { logger.error("Error code: \(Int(status)), description: \(statusMessage ?? "")") throw StorageError(description: statusMessage ?? "", code: Int(status)) } - var res = result as! [[String: Any]] - if dataToLoadType == .doc { - if let dicts2 = try loadDocumentsData(id: id, docStatus: docStatus, dataToLoadType: .key) { res.append(contentsOf: dicts2) } - } - return res + if let res = result as? [[String: Any]] { return res } + else if let res1 = result as? [String: Any] { return [res1] } + else { return nil } } /// Save the secret to keychain @@ -80,10 +74,18 @@ public actor KeyChainStorageService: DataStorageService { /// - Parameters: /// - document: The document to save public func saveDocument(_ document: Document, allowOverwrite: Bool = true) throws { - try saveDocumentData(document, dataToSaveType: .doc, dataType: document.docDataType.rawValue, allowOverwrite: allowOverwrite) - if document.docDataType != .signupResponseJson { - try saveDocumentData(document, dataToSaveType: .key, dataType: document.privateKeyType!.rawValue, allowOverwrite: allowOverwrite) + func setDictValues(_ d: inout [String: Any]) { + d[kSecValueData as String] = document.data + // use this attribute to differentiate between document and key data + d[kSecAttrLabel as String] = document.docType + if let dn = document.displayName { d[kSecAttrDescription as String] = dn } + if let san = document.secureAreaName { d[kSecAttrComment as String] = san } + d[kSecAttrType as String] = document.docDataType.rawValue } + // kSecAttrAccount is used to store the secret Id (we save the document ID) + // kSecAttrService is a key whose value is a string indicating the item's service. + logger.info("Save document for status: \(document.status), id: \(document.id), docType: \(document.docType), displayName: \(document.displayName ?? "")") + try Self.saveDocumentData(serviceName: serviceName, accessGroup: accessGroup, id: document.id, status: document.status, dataType: .doc, setDictValues: setDictValues, allowOverwrite: allowOverwrite) } /// Make a query for a an item in keychain @@ -91,43 +93,26 @@ public actor KeyChainStorageService: DataStorageService { /// - id: id /// - bAll: request all matching items /// - Returns: The dictionary query - func makeQuery(id: String?, bForSave: Bool, status: DocumentStatus, dataType: SavedKeyChainDataType) -> [String: Any] { + nonisolated static func makeQuery(serviceName: String, accessGroup: String?, id: String?, bForSave: Bool, status: DocumentStatus, dataType: SavedKeyChainDataType) -> [String: Any] { let comps = [serviceName, dataType.rawValue, status.rawValue ] let queryValue = comps.joined(separator: ":") - var query: [String: Any] = [kSecClass: kSecClassGenericPassword, kSecAttrService: queryValue] as [String: Any] + var query: [String: Any] = [kSecClass: kSecClassGenericPassword, kSecAttrService: queryValue, kSecUseDataProtectionKeychain: true] as [String: Any] if !bForSave { - query[kSecReturnData as String] = true query[kSecReturnAttributes as String] = true - query[kSecMatchLimit as String] = kSecMatchLimitAll + query[kSecReturnData as String] = true } - if let id { query[kSecAttrAccount as String] = id} + if let id { query[kSecAttrAccount as String] = id } else { query[kSecMatchLimit as String] = kSecMatchLimitAll } if let accessGroup, !accessGroup.isEmpty { query[kSecAttrAccessGroup as String] = accessGroup } return query } - static func getIsNegativeValueToUse(_ dataToSaveType: SavedKeyChainDataType) -> Bool { switch dataToSaveType { case .key: true; default: false } } - - public func saveDocumentData(_ document: Document, dataToSaveType: SavedKeyChainDataType, dataType: String, allowOverwrite: Bool = true) throws { - // kSecAttrAccount is used to store the secret Id (we save the document ID) - // kSecAttrService is a key whose value is a string indicating the item's service. - logger.info("Save document for status: \(document.status), id: \(document.id), docType: \(document.docType), displayName: \(document.displayName ?? "")") - guard dataType.count == 4 else { throw StorageError(description: "Invalid type") } - if dataToSaveType == .key && document.privateKey == nil { throw StorageError(description: "Private key not available") } - var query: [String: Any] = makeQuery(id: document.id, bForSave: true, status: document.status, dataType: dataToSaveType) - #if os(macOS) - query[kSecUseDataProtectionKeychain as String] = true - #endif - query[kSecValueData as String] = switch dataToSaveType { case .key: document.privateKey!; default: document.data } - // use this attribute to differentiate between document and key data - query[kSecAttrIsNegative as String] = Self.getIsNegativeValueToUse(dataToSaveType) - query[kSecAttrLabel as String] = document.docType - if let dn = document.displayName { query[kSecAttrDescription as String] = dn } - query[kSecAttrType as String] = dataType + public nonisolated static func saveDocumentData(serviceName: String, accessGroup: String?, id: String, status: DocumentStatus, dataType: SavedKeyChainDataType, setDictValues: (inout [String: Any]) -> Void, allowOverwrite: Bool) throws { + var query: [String: Any] = Self.makeQuery(serviceName: serviceName, accessGroup: accessGroup, id: id, bForSave: true, status: status, dataType: dataType) + setDictValues(&query) var status = SecItemAdd(query as CFDictionary, nil) if allowOverwrite && status == errSecDuplicateItem { - var updated: [String: Any] = [kSecValueData: query[kSecValueData as String] as! Data, kSecAttrIsNegative: Self.getIsNegativeValueToUse(dataToSaveType), kSecAttrLabel: document.docType, kSecAttrDescription: document.displayName ?? "", kSecAttrType: dataType] as [String: Any] - if let dn = document.displayName { updated[kSecAttrDescription as String] = dn } - query = makeQuery(id: document.id, bForSave: true, status: document.status, dataType: dataToSaveType) + var updated: [String: Any] = [:] + setDictValues(&updated) status = SecItemUpdate(query as CFDictionary, updated as CFDictionary) } let statusMessage = SecCopyErrorMessageString(status, nil) as? String @@ -143,11 +128,14 @@ public actor KeyChainStorageService: DataStorageService { /// - id: The Id of the secret public func deleteDocument(id: String, status: DocumentStatus) throws { logger.info("Delete document with status: \(status), id: \(id)") - try deleteDocumentData(id: id, docStatus: status) + for dts in SavedKeyChainDataType.allCases { + try? Self.deleteDocumentData(serviceName: serviceName, accessGroup: accessGroup, id: id, docStatus: status, dataType: dts) + } } - public func deleteDocumentData(id: String?, docStatus: DocumentStatus, dataType: SavedKeyChainDataType = .doc) throws { - let query: [String: Any] = makeQuery(id: id, bForSave: true, status: docStatus, dataType: dataType) + public nonisolated static func deleteDocumentData(serviceName: String, accessGroup: String?, id: String?, docStatus: DocumentStatus, dataType: SavedKeyChainDataType) throws { + var query: [String: Any] = makeQuery(serviceName: serviceName, accessGroup: accessGroup, id: id, bForSave: false, status: docStatus, dataType: dataType) + query.removeValue(forKey: kSecMatchLimit as String) let status = SecItemDelete(query as CFDictionary) let statusMessage = SecCopyErrorMessageString(status, nil) as? String if status == errSecItemNotFound, id == nil { @@ -157,7 +145,6 @@ public actor KeyChainStorageService: DataStorageService { logger.error("Error code: \(Int(status)), description: \(statusMessage ?? "")") throw StorageError(description: statusMessage ?? "", code: Int(status)) } - if dataType == .doc { try deleteDocumentData(id: id, docStatus: docStatus, dataType: .key) } } /// Delete all documents from keychain @@ -165,20 +152,17 @@ public actor KeyChainStorageService: DataStorageService { /// - id: The Id of the secret public func deleteDocuments(status: DocumentStatus) throws { logger.info("Delete documents with status: \(status)") - try deleteDocumentData(id: nil, docStatus: status) + for dts in SavedKeyChainDataType.allCases { + try? Self.deleteDocumentData(serviceName: serviceName, accessGroup: accessGroup, id: nil, docStatus: status, dataType: dts) + } } /// Make a document from a keychain item /// - Parameter dict: keychain item returned as dictionary /// - Returns: the document - static func makeDocument(dict1: [String: Any], dict2: [String: Any]?, status: DocumentStatus) -> Document { - var data = dict1[kSecValueData as String] as! Data + static func makeDocument(dict: [String: Any], status: DocumentStatus) -> Document? { + guard var data = dict[kSecValueData as String] as? Data else { return nil } defer { let c = data.count; data.withUnsafeMutableBytes { memset_s($0.baseAddress, c, 0, c); return } } - var keyType: PrivateKeyType? = nil; var privateKeyData: Data? = nil - if let dict2 { - keyType = PrivateKeyType(rawValue: dict2[kSecAttrType as String] as? String ?? PrivateKeyType.derEncodedP256.rawValue)! - privateKeyData = (dict2[kSecValueData as String] as! Data) - } - return Document(id: dict1[kSecAttrAccount as String] as! String, docType: dict1[kSecAttrLabel as String] as? String ?? "", docDataType: DocDataType(rawValue: dict1[kSecAttrType as String] as? String ?? DocDataType.cbor.rawValue) ?? DocDataType.cbor, data: data, privateKeyType: keyType, privateKey: privateKeyData, createdAt: (dict1[kSecAttrCreationDate as String] as! Date), modifiedAt: dict1[kSecAttrModificationDate as String] as? Date, displayName: dict1[kSecAttrDescription as String] as? String, status: status) + return Document(id: dict[kSecAttrAccount as String] as! String, docType: dict[kSecAttrLabel as String] as? String ?? "", docDataType: DocDataType(rawValue: dict[kSecAttrType as String] as? String ?? DocDataType.cbor.rawValue) ?? DocDataType.cbor, data: data, secureAreaName: dict[kSecAttrComment as String] as? String, createdAt: (dict[kSecAttrCreationDate as String] as! Date), modifiedAt: dict[kSecAttrModificationDate as String] as? Date, displayName: dict[kSecAttrDescription as String] as? String, status: status) } } diff --git a/Tests/WalletStorageTests/WalletStorageTests.swift b/Tests/WalletStorageTests/WalletStorageTests.swift index 2d259c1..90e3a8a 100644 --- a/Tests/WalletStorageTests/WalletStorageTests.swift +++ b/Tests/WalletStorageTests/WalletStorageTests.swift @@ -5,12 +5,8 @@ import MdocDataModel18013 final class WalletStorageTests: XCTestCase { - - func testIssueRequestPublicKey() throws { - let ir = try IssueRequest() - let pem = try ir.getPublicKeyPEM() - XCTAssert(Bool(pem.count > 0)) - print(pem) + func testExample() throws { + }