From c52728a0506e4a5938627e7fc5f5bc923bbb7da6 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Tue, 26 Aug 2025 22:43:08 -0700 Subject: [PATCH] Return created key (#638) * Return created key. * Fix --- .../SecretKit/Erasers/AnySecretStore.swift | 7 ++++--- .../Sources/SecretKit/Types/SecretStore.swift | 5 +++-- .../SecureEnclaveStore.swift | 16 ++++++++++++---- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift b/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift index cb47e95..9e94618 100644 --- a/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift +++ b/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift @@ -61,20 +61,21 @@ open class AnySecretStore: SecretStore, @unchecked Sendable { public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable, @unchecked Sendable { - private let _create: @Sendable (String, Attributes) async throws -> Void + private let _create: @Sendable (String, Attributes) async throws -> SecretType private let _delete: @Sendable (AnySecret) async throws -> Void private let _update: @Sendable (AnySecret, String, Attributes) async throws -> Void private let _supportedKeyTypes: @Sendable () -> [KeyType] public init(_ secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable { - _create = { try await secretStore.create(name: $0, attributes: $1) } + _create = { try await secretStore.create(name: $0, attributes: $1) as! SecretType } _delete = { try await secretStore.delete(secret: $0.base as! SecretStoreType.SecretType) } _update = { try await secretStore.update(secret: $0.base as! SecretStoreType.SecretType, name: $1, attributes: $2) } _supportedKeyTypes = { secretStore.supportedKeyTypes } super.init(secretStore) } - public func create(name: String, attributes: Attributes) async throws { + @discardableResult + public func create(name: String, attributes: Attributes) async throws -> SecretType { try await _create(name, attributes) } diff --git a/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift b/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift index c1dcdec..14abc9f 100644 --- a/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift +++ b/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift @@ -46,8 +46,9 @@ public protocol SecretStoreModifiable: SecretStore { /// Creates a new ``Secret`` in the store. /// - Parameters: /// - name: The user-facing name for the ``Secret``. - /// - attributes: A struct describing the options for creating the key. - func create(name: String, attributes: Attributes) async throws + /// - attributes: A struct describing the options for creating the key.' + @discardableResult + func create(name: String, attributes: Attributes) async throws -> SecretType /// Deletes a Secret in the store. /// - Parameters: diff --git a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift index 1296189..b94dc5e 100644 --- a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift +++ b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift @@ -98,7 +98,7 @@ extension SecureEnclave { // MARK: SecretStoreModifiable - public func create(name: String, attributes: Attributes) async throws { + public func create(name: String, attributes: Attributes) async throws -> Secret { var accessError: SecurityError? let flags: SecAccessControlCreateFlags = switch attributes.authentication { case .notRequired: @@ -119,23 +119,28 @@ extension SecureEnclave { throw error.takeRetainedValue() as Error } let dataRep: Data + let publicKey: Data switch attributes.keyType { case .ecdsa256: let created = try CryptoKit.SecureEnclave.P256.Signing.PrivateKey(accessControl: access!) dataRep = created.dataRepresentation + publicKey = created.publicKey.x963Representation case .mldsa65: guard #available(macOS 26.0, *) else { throw Attributes.UnsupportedOptionError() } let created = try CryptoKit.SecureEnclave.MLDSA65.PrivateKey(accessControl: access!) dataRep = created.dataRepresentation + publicKey = created.publicKey.rawRepresentation case .mldsa87: guard #available(macOS 26.0, *) else { throw Attributes.UnsupportedOptionError() } let created = try CryptoKit.SecureEnclave.MLDSA87.PrivateKey(accessControl: access!) dataRep = created.dataRepresentation + publicKey = created.publicKey.rawRepresentation default: throw Attributes.UnsupportedOptionError() } - try saveKey(dataRep, name: name, attributes: attributes) + let id = try saveKey(dataRep, name: name, attributes: attributes) await reloadSecrets() + return Secret(id: id, name: name, publicKey: publicKey, attributes: attributes) } public func delete(secret: Secret) async throws { @@ -253,14 +258,16 @@ extension SecureEnclave.Store { /// - name: A user-facing name for the key. /// - attributes: Attributes of the key. /// - Note: Despite the name, the "Data" of the key is _not_ actual key material. This is an opaque data representation that the SEP can manipulate. - func saveKey(_ key: Data, name: String, attributes: Attributes) throws { + @discardableResult + func saveKey(_ key: Data, name: String, attributes: Attributes) throws -> String { let attributes = try JSONEncoder().encode(attributes) + let id = UUID().uuidString let keychainAttributes = KeychainDictionary([ kSecClass: Constants.keyClass, kSecAttrService: Constants.keyTag, kSecUseDataProtectionKeychain: true, kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, - kSecAttrAccount: UUID().uuidString, + kSecAttrAccount: id, kSecValueData: key, kSecAttrLabel: name, kSecAttrGeneric: attributes @@ -269,6 +276,7 @@ extension SecureEnclave.Store { if status != errSecSuccess { throw KeychainError(statusCode: status) } + return id } }