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 {
+
}