apple_kit

Apple-native services for Radroots iOS and macOS apps
git clone https://radroots.dev/git/apple_kit.git
Log | Files | Refs | README

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 }