Return created key (#638)

* Return created key.

* Fix
This commit is contained in:
Max Goedjen 2025-08-26 22:43:08 -07:00 committed by GitHub
parent 8f4d0b8eda
commit c52728a050
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 19 additions and 9 deletions

View File

@ -61,20 +61,21 @@ open class AnySecretStore: SecretStore, @unchecked Sendable {
public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable, @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 _delete: @Sendable (AnySecret) async throws -> Void
private let _update: @Sendable (AnySecret, String, Attributes) async throws -> Void private let _update: @Sendable (AnySecret, String, Attributes) async throws -> Void
private let _supportedKeyTypes: @Sendable () -> [KeyType] private let _supportedKeyTypes: @Sendable () -> [KeyType]
public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable { public init<SecretStoreType>(_ 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) } _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) } _update = { try await secretStore.update(secret: $0.base as! SecretStoreType.SecretType, name: $1, attributes: $2) }
_supportedKeyTypes = { secretStore.supportedKeyTypes } _supportedKeyTypes = { secretStore.supportedKeyTypes }
super.init(secretStore) 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) try await _create(name, attributes)
} }

View File

@ -46,8 +46,9 @@ public protocol SecretStoreModifiable<SecretType>: SecretStore {
/// Creates a new ``Secret`` in the store. /// Creates a new ``Secret`` in the store.
/// - Parameters: /// - Parameters:
/// - name: The user-facing name for the ``Secret``. /// - name: The user-facing name for the ``Secret``.
/// - attributes: A struct describing the options for creating the key. /// - attributes: A struct describing the options for creating the key.'
func create(name: String, attributes: Attributes) async throws @discardableResult
func create(name: String, attributes: Attributes) async throws -> SecretType
/// Deletes a Secret in the store. /// Deletes a Secret in the store.
/// - Parameters: /// - Parameters:

View File

@ -98,7 +98,7 @@ extension SecureEnclave {
// MARK: SecretStoreModifiable // MARK: SecretStoreModifiable
public func create(name: String, attributes: Attributes) async throws { public func create(name: String, attributes: Attributes) async throws -> Secret {
var accessError: SecurityError? var accessError: SecurityError?
let flags: SecAccessControlCreateFlags = switch attributes.authentication { let flags: SecAccessControlCreateFlags = switch attributes.authentication {
case .notRequired: case .notRequired:
@ -119,23 +119,28 @@ extension SecureEnclave {
throw error.takeRetainedValue() as Error throw error.takeRetainedValue() as Error
} }
let dataRep: Data let dataRep: Data
let publicKey: Data
switch attributes.keyType { switch attributes.keyType {
case .ecdsa256: case .ecdsa256:
let created = try CryptoKit.SecureEnclave.P256.Signing.PrivateKey(accessControl: access!) let created = try CryptoKit.SecureEnclave.P256.Signing.PrivateKey(accessControl: access!)
dataRep = created.dataRepresentation dataRep = created.dataRepresentation
publicKey = created.publicKey.x963Representation
case .mldsa65: case .mldsa65:
guard #available(macOS 26.0, *) else { throw Attributes.UnsupportedOptionError() } guard #available(macOS 26.0, *) else { throw Attributes.UnsupportedOptionError() }
let created = try CryptoKit.SecureEnclave.MLDSA65.PrivateKey(accessControl: access!) let created = try CryptoKit.SecureEnclave.MLDSA65.PrivateKey(accessControl: access!)
dataRep = created.dataRepresentation dataRep = created.dataRepresentation
publicKey = created.publicKey.rawRepresentation
case .mldsa87: case .mldsa87:
guard #available(macOS 26.0, *) else { throw Attributes.UnsupportedOptionError() } guard #available(macOS 26.0, *) else { throw Attributes.UnsupportedOptionError() }
let created = try CryptoKit.SecureEnclave.MLDSA87.PrivateKey(accessControl: access!) let created = try CryptoKit.SecureEnclave.MLDSA87.PrivateKey(accessControl: access!)
dataRep = created.dataRepresentation dataRep = created.dataRepresentation
publicKey = created.publicKey.rawRepresentation
default: default:
throw Attributes.UnsupportedOptionError() throw Attributes.UnsupportedOptionError()
} }
try saveKey(dataRep, name: name, attributes: attributes) let id = try saveKey(dataRep, name: name, attributes: attributes)
await reloadSecrets() await reloadSecrets()
return Secret(id: id, name: name, publicKey: publicKey, attributes: attributes)
} }
public func delete(secret: Secret) async throws { public func delete(secret: Secret) async throws {
@ -253,14 +258,16 @@ extension SecureEnclave.Store {
/// - name: A user-facing name for the key. /// - name: A user-facing name for the key.
/// - attributes: Attributes of 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. /// - 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 attributes = try JSONEncoder().encode(attributes)
let id = UUID().uuidString
let keychainAttributes = KeychainDictionary([ let keychainAttributes = KeychainDictionary([
kSecClass: Constants.keyClass, kSecClass: Constants.keyClass,
kSecAttrService: Constants.keyTag, kSecAttrService: Constants.keyTag,
kSecUseDataProtectionKeychain: true, kSecUseDataProtectionKeychain: true,
kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
kSecAttrAccount: UUID().uuidString, kSecAttrAccount: id,
kSecValueData: key, kSecValueData: key,
kSecAttrLabel: name, kSecAttrLabel: name,
kSecAttrGeneric: attributes kSecAttrGeneric: attributes
@ -269,6 +276,7 @@ extension SecureEnclave.Store {
if status != errSecSuccess { if status != errSecSuccess {
throw KeychainError(statusCode: status) throw KeychainError(statusCode: status)
} }
return id
} }
} }