RadrootsSecureStore.swift (3436B)
1 import Foundation 2 3 public enum RadrootsSecretAccessibility: Sendable, Equatable { 4 case whenUnlocked 5 case afterFirstUnlock 6 } 7 8 public struct RadrootsSecretAccessPolicy: Sendable, Equatable { 9 public let accessibility: RadrootsSecretAccessibility 10 public let deviceLocalOnly: Bool 11 public let userPresenceRequired: Bool 12 13 public init( 14 accessibility: RadrootsSecretAccessibility, 15 deviceLocalOnly: Bool, 16 userPresenceRequired: Bool 17 ) { 18 self.accessibility = accessibility 19 self.deviceLocalOnly = deviceLocalOnly 20 self.userPresenceRequired = userPresenceRequired 21 } 22 23 public static let secureLocalSecret = Self( 24 accessibility: .whenUnlocked, 25 deviceLocalOnly: true, 26 userPresenceRequired: false 27 ) 28 29 public static let userPresenceLocalSecret = Self( 30 accessibility: .whenUnlocked, 31 deviceLocalOnly: true, 32 userPresenceRequired: true 33 ) 34 } 35 36 public struct RadrootsSecureStoreKey: Hashable, Sendable { 37 public let namespace: String 38 public let name: String 39 40 public init(namespace: String, name: String) { 41 self.namespace = namespace 42 self.name = name 43 } 44 45 public func normalized() throws -> Self { 46 Self( 47 namespace: try Self.normalizedNamespace(namespace), 48 name: try Self.normalizedName(name) 49 ) 50 } 51 52 public func serviceName(servicePrefix: String) throws -> String { 53 try Self.serviceName(servicePrefix: servicePrefix, namespace: namespace) 54 } 55 56 public static func serviceName(servicePrefix: String, namespace: String) throws -> String { 57 "\(try normalizedServicePrefix(servicePrefix)).\(try normalizedNamespace(namespace))" 58 } 59 60 public static func normalizedServicePrefix(_ servicePrefix: String) throws -> String { 61 let trimmedPrefix = servicePrefix.trimmingCharacters(in: .whitespacesAndNewlines) 62 guard !trimmedPrefix.isEmpty else { 63 throw RadrootsAppleSecurityError.invalidRequest("secure store service prefix cannot be empty") 64 } 65 return trimmedPrefix 66 } 67 68 public static func normalizedNamespace(_ namespace: String) throws -> String { 69 let trimmedNamespace = namespace.trimmingCharacters(in: .whitespacesAndNewlines) 70 guard !trimmedNamespace.isEmpty else { 71 throw RadrootsAppleSecurityError.invalidRequest("secure store namespace cannot be empty") 72 } 73 return trimmedNamespace 74 } 75 76 public static func normalizedName(_ name: String) throws -> String { 77 let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines) 78 guard !trimmedName.isEmpty else { 79 throw RadrootsAppleSecurityError.invalidRequest("secure store key name cannot be empty") 80 } 81 return trimmedName 82 } 83 } 84 85 public protocol RadrootsSecureStore: AnyObject, Sendable { 86 func put( 87 _ value: Data, 88 for key: RadrootsSecureStoreKey, 89 policy: RadrootsSecretAccessPolicy 90 ) throws 91 func contains(_ key: RadrootsSecureStoreKey) throws -> Bool 92 func get(_ key: RadrootsSecureStoreKey) throws -> Data? 93 func delete(_ key: RadrootsSecureStoreKey) throws 94 func deleteNamespace(_ namespace: String) throws 95 } 96 97 extension RadrootsSecureStore { 98 public func put(_ value: Data, for key: RadrootsSecureStoreKey) throws { 99 try put(value, for: key, policy: .secureLocalSecret) 100 } 101 }