From d7f8d5e56b5745f6b269565a2f4a129980e6b711 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sun, 14 Sep 2025 14:42:41 -0700 Subject: [PATCH] Add descriptions for unavailable keys (#708) * Describe unavailable key types * Cleanup --- .../Packages/Resources/Localizable.xcstrings | 11 +++++++ .../SecretKit/Erasers/AnySecretStore.swift | 4 +-- .../Sources/SecretKit/Types/SecretStore.swift | 29 ++++++++++++++++++- .../SecureEnclaveStore.swift | 23 +++++++++------ .../Preview Content/PreviewStore.swift | 15 +++++----- .../Views/Secrets/CreateSecretView.swift | 25 ++++++++++++---- 6 files changed, 82 insertions(+), 25 deletions(-) diff --git a/Sources/Packages/Resources/Localizable.xcstrings b/Sources/Packages/Resources/Localizable.xcstrings index 923c3d6..5e70879 100644 --- a/Sources/Packages/Resources/Localizable.xcstrings +++ b/Sources/Packages/Resources/Localizable.xcstrings @@ -5833,6 +5833,17 @@ } } }, + "create_secret_key_type_macOS_update_required_label" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unavailable on this version of macOS" + } + } + } + }, "create_secret_mldsa_warning" : { "extractionState" : "manual", "localizations" : { 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..42b4db9 100644 --- a/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift +++ b/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift @@ -62,10 +62,37 @@ 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: Reason + + public init(keyType: KeyType, reason: Reason) { + self.keyType = keyType + self.reason = reason + } + + public enum Reason: Sendable { + case macOSUpdateRequired + } + } + +} + + 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..7f2fc55 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: .macOSUpdateRequired) + } + ) + }() } } diff --git a/Sources/Secretive/Preview Content/PreviewStore.swift b/Sources/Secretive/Preview Content/PreviewStore.swift index 8c65f80..63e7507 100644 --- a/Sources/Secretive/Preview Content/PreviewStore.swift +++ b/Sources/Secretive/Preview Content/PreviewStore.swift @@ -60,16 +60,17 @@ extension Preview { let id = UUID() var name: String { "Modifiable Preview Store" } let secrets: [Secret] - var supportedKeyTypes: [KeyType] { - if #available(macOS 26, *) { - [ + var supportedKeyTypes: KeyAvailability { + return KeyAvailability( + available: [ .ecdsa256, .mldsa65, - .mldsa87, + .mldsa87 + ], + unavailable: [ + .init(keyType: .ecdsa384, reason: .macOSUpdateRequired) ] - } else { - [.ecdsa256] - } + ) } init(secrets: [Secret]) { diff --git a/Sources/Secretive/Views/Secrets/CreateSecretView.swift b/Sources/Secretive/Views/Secrets/CreateSecretView.swift index 192c3dc..5b305a2 100644 --- a/Sources/Secretive/Views/Secrets/CreateSecretView.swift +++ b/Sources/Secretive/Views/Secrets/CreateSecretView.swift @@ -75,10 +75,23 @@ 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 { + Button { + } label: { + Text(String(describing: option.keyType)) + switch option.reason { + case .macOSUpdateRequired: + Text(.createSecretKeyTypeMacOSUpdateRequiredLabel) + } + } + } + .selectionDisabled() } } if keyType?.algorithm == .mldsa { @@ -119,7 +132,7 @@ struct CreateSecretView: View { .padding() } .onAppear { - keyType = store.supportedKeyTypes.first + keyType = store.supportedKeyTypes.available.first } .formStyle(.grouped) } @@ -146,6 +159,6 @@ struct CreateSecretView: View { } -//#Preview { -// CreateSecretView(store: Preview.StoreModifiable()) { _ in } -//} +#Preview { + CreateSecretView(store: Preview.StoreModifiable()) { _ in } +}