From 25434aa31cc911a2887288a0fe2d8ff3c2c9199e Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sun, 14 Sep 2025 13:53:19 -0700 Subject: [PATCH] Describe unavailable key types --- .../Packages/Resources/Localizable.xcstrings | 3 +++ .../SecretKit/Erasers/AnySecretStore.swift | 4 +-- .../Sources/SecretKit/Types/SecretStore.swift | 25 ++++++++++++++++++- .../SecureEnclaveStore.swift | 23 ++++++++++------- .../Preview Content/PreviewStore.swift | 21 ++++++++++------ .../Views/Secrets/CreateSecretView.swift | 20 +++++++++++---- 6 files changed, 71 insertions(+), 25 deletions(-) diff --git a/Sources/Packages/Resources/Localizable.xcstrings b/Sources/Packages/Resources/Localizable.xcstrings index 32e79ab..85b8a3f 100644 --- a/Sources/Packages/Resources/Localizable.xcstrings +++ b/Sources/Packages/Resources/Localizable.xcstrings @@ -17031,6 +17031,9 @@ } } } + }, + "macOS Tahoe or Later Required" : { + }, "no_secure_storage_description" : { "extractionState" : "manual", diff --git a/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift b/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift index 08123a1..f163879 100644 --- a/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift +++ b/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift @@ -64,7 +64,7 @@ public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiab private let _create: @Sendable (String, Attributes) async throws -> AnySecret private let _delete: @Sendable (AnySecret) async throws -> Void private let _update: @Sendable (AnySecret, String, Attributes) async throws -> Void - private let _supportedKeyTypes: @Sendable () -> [KeyType] + private let _supportedKeyTypes: @Sendable () -> KeyAvailability public init(_ secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable { _create = { AnySecret(try await secretStore.create(name: $0, attributes: $1)) } @@ -87,7 +87,7 @@ public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiab try await _update(secret, name, attributes) } - public var supportedKeyTypes: [KeyType] { + public var supportedKeyTypes: KeyAvailability { _supportedKeyTypes() } diff --git a/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift b/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift index 14abc9f..05d9228 100644 --- a/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift +++ b/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift @@ -62,10 +62,33 @@ public protocol SecretStoreModifiable: SecretStore { /// - attributes: The new attributes for the secret. func update(secret: SecretType, name: String, attributes: Attributes) async throws - var supportedKeyTypes: [KeyType] { get } + var supportedKeyTypes: KeyAvailability { get } } +public struct KeyAvailability: Sendable { + + public let available: [KeyType] + public let unavailable: [UnavailableKeyType] + + public init(available: [KeyType], unavailable: [UnavailableKeyType]) { + self.available = available + self.unavailable = unavailable + } + + public struct UnavailableKeyType: Sendable { + public let keyType: KeyType + public let reason: LocalizedStringResource + + public init(keyType: KeyType, reason: LocalizedStringResource) { + self.keyType = keyType + self.reason = reason + } + } + +} + + extension NSNotification.Name { // Distributed notification that keys were modified out of process (ie, that the management tool added/removed secrets) diff --git a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift index 5156900..be11a25 100644 --- a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift +++ b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift @@ -186,17 +186,22 @@ extension SecureEnclave { await reloadSecrets() } - public var supportedKeyTypes: [KeyType] { - if #available(macOS 26, *) { - [ - .ecdsa256, - .mldsa65, - .mldsa87, - ] + public let supportedKeyTypes: KeyAvailability = { + let macOS26Keys: [KeyType] = [.mldsa65, .mldsa87] + let isAtLeastMacOS26 = if #available(macOS 26, *) { + true } else { - [.ecdsa256] + false } - } + return KeyAvailability( + available: [ + .ecdsa256, + ] + (isAtLeastMacOS26 ? macOS26Keys : []), + unavailable: (isAtLeastMacOS26 ? [] : macOS26Keys).map { + KeyAvailability.UnavailableKeyType(keyType: $0, reason: "macOS Tahoe or Later Required") + } + ) + }() } } diff --git a/Sources/Secretive/Preview Content/PreviewStore.swift b/Sources/Secretive/Preview Content/PreviewStore.swift index 8c65f80..56fe9ec 100644 --- a/Sources/Secretive/Preview Content/PreviewStore.swift +++ b/Sources/Secretive/Preview Content/PreviewStore.swift @@ -60,16 +60,21 @@ extension Preview { let id = UUID() var name: String { "Modifiable Preview Store" } let secrets: [Secret] - var supportedKeyTypes: [KeyType] { - if #available(macOS 26, *) { - [ - .ecdsa256, - .mldsa65, - .mldsa87, - ] + var supportedKeyTypes: KeyAvailability { + let macOS26Keys: [KeyType] = [.mldsa65, .mldsa87] + let isAtLeastMacOS26 = if #available(macOS 26, *) { + true } else { - [.ecdsa256] + false } + return KeyAvailability( + available: [ + .ecdsa256, + ] + (isAtLeastMacOS26 ? macOS26Keys : []), + unavailable: (isAtLeastMacOS26 ? [] : macOS26Keys).map { + KeyAvailability.UnavailableKeyType(keyType: $0, reason: "macOS Tahoe or Later Required") + } + ) } init(secrets: [Secret]) { diff --git a/Sources/Secretive/Views/Secrets/CreateSecretView.swift b/Sources/Secretive/Views/Secrets/CreateSecretView.swift index 192c3dc..d976742 100644 --- a/Sources/Secretive/Views/Secrets/CreateSecretView.swift +++ b/Sources/Secretive/Views/Secrets/CreateSecretView.swift @@ -75,11 +75,21 @@ struct CreateSecretView: View { Section { VStack { Picker(.createSecretKeyTypeLabel, selection: $keyType) { - ForEach(store.supportedKeyTypes, id: \.self) { option in + ForEach(store.supportedKeyTypes.available, id: \.self) { option in Text(String(describing: option)) .tag(option) .font(.caption) } + Divider() + ForEach(store.supportedKeyTypes.unavailable, id: \.keyType) { option in + VStack { + Text(String(describing: option.keyType)) + .font(.caption) + Text(option.reason) + .font(.caption) + } + .disabled(true) + } } if keyType?.algorithm == .mldsa { Text(.createSecretMldsaWarning) @@ -119,7 +129,7 @@ struct CreateSecretView: View { .padding() } .onAppear { - keyType = store.supportedKeyTypes.first + keyType = store.supportedKeyTypes.available.first } .formStyle(.grouped) } @@ -146,6 +156,6 @@ struct CreateSecretView: View { } -//#Preview { -// CreateSecretView(store: Preview.StoreModifiable()) { _ in } -//} +#Preview { + CreateSecretView(store: Preview.StoreModifiable()) { _ in } +}