mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-01-07 20:17:09 +00:00
WIP
This commit is contained in:
parent
2dc317d398
commit
970e407e29
@ -2,21 +2,19 @@ import Foundation
|
|||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
/// Type eraser for SecretStore.
|
/// Type eraser for SecretStore.
|
||||||
public class AnySecretStore: SecretStore {
|
public class AnySecretStore: SecretStore, @unchecked Sendable {
|
||||||
|
|
||||||
let base: Any
|
private let _isAvailable: @Sendable () -> Bool
|
||||||
private let _isAvailable: () -> Bool
|
private let _id: @Sendable () -> UUID
|
||||||
private let _id: () -> UUID
|
private let _name: @Sendable () -> String
|
||||||
private let _name: () -> String
|
private let _secrets: @Sendable () -> [AnySecret]
|
||||||
private let _secrets: () -> [AnySecret]
|
private let _sign: @Sendable (Data, AnySecret, SigningRequestProvenance) async throws -> Data
|
||||||
private let _sign: (Data, AnySecret, SigningRequestProvenance) async throws -> Data
|
private let _verify: @Sendable (Data, Data, AnySecret) async throws -> Bool
|
||||||
private let _verify: (Data, Data, AnySecret) async throws -> Bool
|
private let _existingPersistedAuthenticationContext: @Sendable (AnySecret) async -> PersistedAuthenticationContext?
|
||||||
private let _existingPersistedAuthenticationContext: (AnySecret) async -> PersistedAuthenticationContext?
|
private let _persistAuthentication: @Sendable (AnySecret, TimeInterval) async throws -> Void
|
||||||
private let _persistAuthentication: (AnySecret, TimeInterval) async throws -> Void
|
private let _reloadSecrets: @Sendable () async -> Void
|
||||||
private let _reloadSecrets: () async -> Void
|
|
||||||
|
|
||||||
public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStore {
|
public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStore {
|
||||||
base = secretStore
|
|
||||||
_isAvailable = { secretStore.isAvailable }
|
_isAvailable = { secretStore.isAvailable }
|
||||||
_name = { secretStore.name }
|
_name = { secretStore.name }
|
||||||
_id = { secretStore.id }
|
_id = { secretStore.id }
|
||||||
@ -66,11 +64,11 @@ public class AnySecretStore: SecretStore {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable {
|
public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable, @unchecked Sendable {
|
||||||
|
|
||||||
private let _create: (String, Bool) async throws -> Void
|
private let _create: @Sendable (String, Bool) async throws -> Void
|
||||||
private let _delete: (AnySecret) async throws -> Void
|
private let _delete: @Sendable (AnySecret) async throws -> Void
|
||||||
private let _update: (AnySecret, String) async throws -> Void
|
private let _update: @Sendable (AnySecret, String) async throws -> Void
|
||||||
|
|
||||||
public init<SecretStoreType>(modifiable secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
|
public init<SecretStoreType>(modifiable secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
|
||||||
_create = { try await secretStore.create(name: $0, requiresAuthentication: $1) }
|
_create = { try await secretStore.create(name: $0, requiresAuthentication: $1) }
|
||||||
|
@ -15,14 +15,14 @@ import Observation
|
|||||||
|
|
||||||
/// Adds a non-type-erased SecretStore to the list.
|
/// Adds a non-type-erased SecretStore to the list.
|
||||||
public func add<SecretStoreType: SecretStore>(store: SecretStoreType) {
|
public func add<SecretStoreType: SecretStore>(store: SecretStoreType) {
|
||||||
addInternal(store: AnySecretStore(store))
|
stores.append(AnySecretStore(store))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a non-type-erased modifiable SecretStore.
|
/// Adds a non-type-erased modifiable SecretStore.
|
||||||
public func add<SecretStoreType: SecretStoreModifiable>(store: SecretStoreType) {
|
public func add<SecretStoreType: SecretStoreModifiable>(store: SecretStoreType) {
|
||||||
let modifiable = AnySecretStoreModifiable(modifiable: store)
|
let modifiable = AnySecretStoreModifiable(modifiable: store)
|
||||||
modifiableStore = modifiable
|
modifiableStore = modifiable
|
||||||
addInternal(store: modifiable)
|
stores.append(modifiable)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A boolean describing whether there are any Stores available.
|
/// A boolean describing whether there are any Stores available.
|
||||||
@ -35,14 +35,3 @@ import Observation
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SecretStoreList {
|
|
||||||
|
|
||||||
private func addInternal(store: AnySecretStore) {
|
|
||||||
stores.append(store)
|
|
||||||
// store.objectWillChange.sink {
|
|
||||||
// self.objectWillChange.send()
|
|
||||||
// }.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// Protocol describing a persisted authentication context. This is an authorization that can be reused for multiple access to a secret that requires authentication for a specific period of time.
|
/// Protocol describing a persisted authentication context. This is an authorization that can be reused for multiple access to a secret that requires authentication for a specific period of time.
|
||||||
public protocol PersistedAuthenticationContext {
|
public protocol PersistedAuthenticationContext: Sendable {
|
||||||
/// Whether the context remains valid.
|
/// Whether the context remains valid.
|
||||||
var valid: Bool { get }
|
var valid: Bool { get }
|
||||||
/// The date at which the authorization expires and the context becomes invalid.
|
/// The date at which the authorization expires and the context becomes invalid.
|
||||||
|
@ -2,7 +2,7 @@ import Foundation
|
|||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
/// Manages access to Secrets, and performs signature operations on data using those Secrets.
|
/// Manages access to Secrets, and performs signature operations on data using those Secrets.
|
||||||
public protocol SecretStore: Identifiable {
|
public protocol SecretStore: Identifiable, Sendable {
|
||||||
|
|
||||||
associatedtype SecretType: Secret
|
associatedtype SecretType: Secret
|
||||||
|
|
||||||
|
@ -21,16 +21,15 @@ extension SecureEnclave {
|
|||||||
}
|
}
|
||||||
private let _secrets: Mutex<[Secret]> = .init([])
|
private let _secrets: Mutex<[Secret]> = .init([])
|
||||||
|
|
||||||
private var persistedAuthenticationContexts: [Secret: PersistentAuthenticationContext] = [:]
|
private let persistedAuthenticationContexts: Mutex<[Secret: PersistentAuthenticationContext]> = .init([:])
|
||||||
|
|
||||||
/// Initializes a Store.
|
/// Initializes a Store.
|
||||||
public init() {
|
public init() {
|
||||||
// FIXME: THIS
|
Task {
|
||||||
// Task {
|
for await _ in DistributedNotificationCenter.default().notifications(named: .secretStoreUpdated) {
|
||||||
// for await _ in DistributedNotificationCenter.default().notifications(named: .secretStoreUpdated) {
|
await reloadSecretsInternal(notifyAgent: false)
|
||||||
// await reloadSecretsInternal(notifyAgent: false)
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
loadSecrets()
|
loadSecrets()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,40 +105,42 @@ extension SecureEnclave {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) throws -> Data {
|
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) throws -> Data {
|
||||||
let context: LAContext
|
let context: Mutex<LAContext>
|
||||||
if let existing = persistedAuthenticationContexts[secret], existing.valid {
|
// if let existing = persistedAuthenticationContexts.withLock({ $0 })[secret], existing.valid {
|
||||||
context = existing.context
|
// context = existing.context
|
||||||
} else {
|
// } else {
|
||||||
let newContext = LAContext()
|
let newContext = LAContext()
|
||||||
newContext.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
|
newContext.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
|
||||||
context = newContext
|
context = .init(newContext)
|
||||||
}
|
// }
|
||||||
context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)")
|
return try context.withLock { context in
|
||||||
let attributes = KeychainDictionary([
|
context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)")
|
||||||
kSecClass: kSecClassKey,
|
let attributes = KeychainDictionary([
|
||||||
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
kSecClass: kSecClassKey,
|
||||||
kSecAttrApplicationLabel: secret.id as CFData,
|
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
||||||
kSecAttrKeyType: Constants.keyType,
|
kSecAttrApplicationLabel: secret.id as CFData,
|
||||||
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
|
kSecAttrKeyType: Constants.keyType,
|
||||||
kSecAttrApplicationTag: Constants.keyTag,
|
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
|
||||||
kSecUseAuthenticationContext: context,
|
kSecAttrApplicationTag: Constants.keyTag,
|
||||||
kSecReturnRef: true
|
kSecUseAuthenticationContext: context,
|
||||||
|
kSecReturnRef: true
|
||||||
])
|
])
|
||||||
var untyped: CFTypeRef?
|
var untyped: CFTypeRef?
|
||||||
let status = SecItemCopyMatching(attributes, &untyped)
|
let status = SecItemCopyMatching(attributes, &untyped)
|
||||||
if status != errSecSuccess {
|
if status != errSecSuccess {
|
||||||
throw KeychainError(statusCode: status)
|
throw KeychainError(statusCode: status)
|
||||||
|
}
|
||||||
|
guard let untypedSafe = untyped else {
|
||||||
|
throw KeychainError(statusCode: errSecSuccess)
|
||||||
|
}
|
||||||
|
let key = untypedSafe as! SecKey
|
||||||
|
var signError: SecurityError?
|
||||||
|
|
||||||
|
guard let signature = SecKeyCreateSignature(key, .ecdsaSignatureMessageX962SHA256, data as CFData, &signError) else {
|
||||||
|
throw SigningError(error: signError)
|
||||||
|
}
|
||||||
|
return signature as Data
|
||||||
}
|
}
|
||||||
guard let untypedSafe = untyped else {
|
|
||||||
throw KeychainError(statusCode: errSecSuccess)
|
|
||||||
}
|
|
||||||
let key = untypedSafe as! SecKey
|
|
||||||
var signError: SecurityError?
|
|
||||||
|
|
||||||
guard let signature = SecKeyCreateSignature(key, .ecdsaSignatureMessageX962SHA256, data as CFData, &signError) else {
|
|
||||||
throw SigningError(error: signError)
|
|
||||||
}
|
|
||||||
return signature as Data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool {
|
public func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool {
|
||||||
@ -178,7 +179,7 @@ extension SecureEnclave {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func existingPersistedAuthenticationContext(secret: Secret) -> PersistedAuthenticationContext? {
|
public func existingPersistedAuthenticationContext(secret: Secret) -> PersistedAuthenticationContext? {
|
||||||
guard let persisted = persistedAuthenticationContexts[secret], persisted.valid else { return nil }
|
guard let persisted = persistedAuthenticationContexts.withLock({ $0 })[secret], persisted.valid else { return nil }
|
||||||
return persisted
|
return persisted
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,9 +198,11 @@ extension SecureEnclave {
|
|||||||
newContext.localizedReason = String(localized: "auth_context_persist_for_duration_unknown_\(secret.name)")
|
newContext.localizedReason = String(localized: "auth_context_persist_for_duration_unknown_\(secret.name)")
|
||||||
}
|
}
|
||||||
newContext.evaluatePolicy(LAPolicy.deviceOwnerAuthentication, localizedReason: newContext.localizedReason) { [weak self] success, _ in
|
newContext.evaluatePolicy(LAPolicy.deviceOwnerAuthentication, localizedReason: newContext.localizedReason) { [weak self] success, _ in
|
||||||
guard success else { return }
|
guard success, let self else { return }
|
||||||
let context = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration)
|
let context = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration)
|
||||||
self?.persistedAuthenticationContexts[secret] = context
|
self.persistedAuthenticationContexts.withLock {
|
||||||
|
$0[secret] = context
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,12 +325,12 @@ extension SecureEnclave {
|
|||||||
extension SecureEnclave {
|
extension SecureEnclave {
|
||||||
|
|
||||||
/// A context describing a persisted authentication.
|
/// A context describing a persisted authentication.
|
||||||
private struct PersistentAuthenticationContext: PersistedAuthenticationContext {
|
private final class PersistentAuthenticationContext: PersistedAuthenticationContext {
|
||||||
|
|
||||||
/// The Secret to persist authentication for.
|
/// The Secret to persist authentication for.
|
||||||
let secret: Secret
|
let secret: Secret
|
||||||
/// The LAContext used to authorize the persistent context.
|
/// The LAContext used to authorize the persistent context.
|
||||||
let context: LAContext
|
nonisolated(unsafe) let context: LAContext
|
||||||
/// An expiration date for the context.
|
/// An expiration date for the context.
|
||||||
/// - Note - Monotonic time instead of Date() to prevent people setting the clock back.
|
/// - Note - Monotonic time instead of Date() to prevent people setting the clock back.
|
||||||
let monotonicExpiration: UInt64
|
let monotonicExpiration: UInt64
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Synchronization
|
||||||
|
import Observation
|
||||||
import Security
|
import Security
|
||||||
import CryptoTokenKit
|
import CryptoTokenKit
|
||||||
import LocalAuthentication
|
import LocalAuthentication
|
||||||
@ -8,32 +9,54 @@ import SecretKit
|
|||||||
extension SmartCard {
|
extension SmartCard {
|
||||||
|
|
||||||
/// An implementation of Store backed by a Smart Card.
|
/// An implementation of Store backed by a Smart Card.
|
||||||
public final class Store: SecretStore {
|
@Observable public final class Store: SecretStore {
|
||||||
|
|
||||||
|
public var isAvailable: Bool {
|
||||||
|
_isAvailable.withLock { $0 }
|
||||||
|
}
|
||||||
|
private let _isAvailable: Mutex<Bool> = .init(false)
|
||||||
|
|
||||||
@Published public var isAvailable: Bool = false
|
|
||||||
public let id = UUID()
|
public let id = UUID()
|
||||||
public private(set) var name = String(localized: "smart_card")
|
public var name: String {
|
||||||
@Published public private(set) var secrets: [Secret] = []
|
_name.withLock { $0 }
|
||||||
private let watcher = TKTokenWatcher()
|
}
|
||||||
private var tokenID: String?
|
private let _name: Mutex<String> = .init(String(localized: "smart_card"))
|
||||||
|
public var secrets: [Secret] {
|
||||||
|
_secrets.withLock { $0 }
|
||||||
|
}
|
||||||
|
private let _secrets: Mutex<[Secret]> = .init([])
|
||||||
|
private let watcher: Mutex<TKTokenWatcher> = .init(TKTokenWatcher())
|
||||||
|
private let tokenID: Mutex<String?> = .init(nil)
|
||||||
|
|
||||||
/// Initializes a Store.
|
/// Initializes a Store.
|
||||||
public init() {
|
public init() {
|
||||||
tokenID = watcher.nonSecureEnclaveTokens.first
|
tokenID.withLock { tokenID in
|
||||||
// FIXME: THIS
|
watcher.withLock { watcher in
|
||||||
watcher.setInsertionHandler { string in
|
let id = watcher.nonSecureEnclaveTokens.first
|
||||||
guard self.tokenID == nil else { return }
|
watcher.setInsertionHandler { string in
|
||||||
guard !string.contains("setoken") else { return }
|
// guard self.tokenID == nil else { return }
|
||||||
|
// guard !string.contains("setoken") else { return }
|
||||||
self.tokenID = string
|
//
|
||||||
// DispatchQueue.main.async {
|
//// self.tokenID.withLock {
|
||||||
// reload()
|
//// $0 = string
|
||||||
// }
|
//// }
|
||||||
self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string)
|
// // DispatchQueue.main.async {
|
||||||
|
// // reload()
|
||||||
|
// // }
|
||||||
|
// watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string)
|
||||||
|
}
|
||||||
|
tokenID = id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let tokenID = tokenID {
|
// FIXME: THIS
|
||||||
self.isAvailable = true
|
if let tokenID = tokenID.withLock({ $0 }) {
|
||||||
self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: tokenID)
|
_isAvailable.withLock {
|
||||||
|
$0 = true
|
||||||
|
}
|
||||||
|
watcher.withLock {
|
||||||
|
$0.addRemovalHandler(self.smartcardRemoved, forTokenID: tokenID)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
loadSecrets()
|
loadSecrets()
|
||||||
}
|
}
|
||||||
@ -49,7 +72,7 @@ extension SmartCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) throws -> Data {
|
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) throws -> Data {
|
||||||
guard let tokenID = tokenID else { fatalError() }
|
guard let tokenID = tokenID.withLock({ $0 }) else { fatalError() }
|
||||||
let context = LAContext()
|
let context = LAContext()
|
||||||
context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)")
|
context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)")
|
||||||
context.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
|
context.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
|
||||||
@ -119,9 +142,13 @@ extension SmartCard {
|
|||||||
extension SmartCard.Store {
|
extension SmartCard.Store {
|
||||||
|
|
||||||
private func reloadSecretsInternal() {
|
private func reloadSecretsInternal() {
|
||||||
self.isAvailable = self.tokenID != nil
|
_isAvailable.withLock {
|
||||||
|
$0 = tokenID.withLock({ $0 }) != nil
|
||||||
|
}
|
||||||
let before = self.secrets
|
let before = self.secrets
|
||||||
self.secrets.removeAll()
|
self._secrets.withLock {
|
||||||
|
$0.removeAll()
|
||||||
|
}
|
||||||
self.loadSecrets()
|
self.loadSecrets()
|
||||||
if self.secrets != before {
|
if self.secrets != before {
|
||||||
NotificationCenter.default.post(name: .secretStoreReloaded, object: self)
|
NotificationCenter.default.post(name: .secretStoreReloaded, object: self)
|
||||||
@ -131,19 +158,23 @@ extension SmartCard.Store {
|
|||||||
/// Resets the token ID and reloads secrets.
|
/// Resets the token ID and reloads secrets.
|
||||||
/// - Parameter tokenID: The ID of the token that was removed.
|
/// - Parameter tokenID: The ID of the token that was removed.
|
||||||
private func smartcardRemoved(for tokenID: String? = nil) {
|
private func smartcardRemoved(for tokenID: String? = nil) {
|
||||||
self.tokenID = nil
|
self.tokenID.withLock {
|
||||||
|
$0 = nil
|
||||||
|
}
|
||||||
reloadSecrets()
|
reloadSecrets()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads all secrets from the store.
|
/// Loads all secrets from the store.
|
||||||
private func loadSecrets() {
|
private func loadSecrets() {
|
||||||
guard let tokenID = tokenID else { return }
|
guard let tokenID = tokenID.withLock({ $0 }) else { return }
|
||||||
|
|
||||||
let fallbackName = String(localized: "smart_card")
|
let fallbackName = String(localized: "smart_card")
|
||||||
if let driverName = watcher.tokenInfo(forTokenID: tokenID)?.driverName {
|
_name.withLock {
|
||||||
name = driverName
|
if let driverName = watcher.withLock({ $0.tokenInfo(forTokenID: tokenID)?.driverName }) {
|
||||||
} else {
|
$0 = driverName
|
||||||
name = fallbackName
|
} else {
|
||||||
|
$0 = fallbackName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let attributes = KeychainDictionary([
|
let attributes = KeychainDictionary([
|
||||||
@ -167,7 +198,9 @@ extension SmartCard.Store {
|
|||||||
let publicKey = publicKeyAttributes[kSecValueData] as! Data
|
let publicKey = publicKeyAttributes[kSecValueData] as! Data
|
||||||
return SmartCard.Secret(id: tokenID, name: name, algorithm: algorithm, keySize: keySize, publicKey: publicKey)
|
return SmartCard.Secret(id: tokenID, name: name, algorithm: algorithm, keySize: keySize, publicKey: publicKey)
|
||||||
}
|
}
|
||||||
secrets.append(contentsOf: wrapped)
|
_secrets.withLock {
|
||||||
|
$0.append(contentsOf: wrapped)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -211,7 +244,7 @@ extension SmartCard.Store {
|
|||||||
/// - Returns: The decrypted data.
|
/// - Returns: The decrypted data.
|
||||||
/// - Warning: Encryption functions are deliberately only exposed on a library level, and are not exposed in Secretive itself to prevent users from data loss. Any pull requests which expose this functionality in the app will not be merged.
|
/// - Warning: Encryption functions are deliberately only exposed on a library level, and are not exposed in Secretive itself to prevent users from data loss. Any pull requests which expose this functionality in the app will not be merged.
|
||||||
public func decrypt(data: Data, with secret: SecretType) throws -> Data {
|
public func decrypt(data: Data, with secret: SecretType) throws -> Data {
|
||||||
guard let tokenID = tokenID else { fatalError() }
|
guard let tokenID = tokenID.withLock({ $0 }) else { fatalError() }
|
||||||
let context = LAContext()
|
let context = LAContext()
|
||||||
context.localizedReason = String(localized: "auth_context_request_decrypt_description_\(secret.name)")
|
context.localizedReason = String(localized: "auth_context_request_decrypt_description_\(secret.name)")
|
||||||
context.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
|
context.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
|
||||||
|
@ -69,26 +69,24 @@ struct Secretive: App {
|
|||||||
extension Secretive {
|
extension Secretive {
|
||||||
|
|
||||||
private func reinstallAgent() {
|
private func reinstallAgent() {
|
||||||
// justUpdatedChecker.check()
|
justUpdatedChecker.check()
|
||||||
// FIXME: THIS
|
Task {
|
||||||
// LaunchAgentController().install {
|
await LaunchAgentController().install()
|
||||||
// // Wait a second for launchd to kick in (next runloop isn't enough).
|
try? await Task.sleep(for: .seconds(1))
|
||||||
// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
agentStatusChecker.check()
|
||||||
// agentStatusChecker.check()
|
if !agentStatusChecker.running {
|
||||||
// if !agentStatusChecker.running {
|
forceLaunchAgent()
|
||||||
// forceLaunchAgent()
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func forceLaunchAgent() {
|
private func forceLaunchAgent() {
|
||||||
// We've run setup, we didn't just update, launchd is just not doing it's thing.
|
// We've run setup, we didn't just update, launchd is just not doing it's thing.
|
||||||
// Force a launch directly.
|
// Force a launch directly.
|
||||||
// FIXME: THIS
|
Task {
|
||||||
// LaunchAgentController().forceLaunch { _ in
|
_ = await LaunchAgentController().forceLaunch()
|
||||||
// agentStatusChecker.check()
|
agentStatusChecker.check()
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,69 +18,69 @@ extension Preview {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Preview {
|
//extension Preview {
|
||||||
|
//
|
||||||
class Store: SecretStore, ObservableObject {
|
// class Store: SecretStore, ObservableObject {
|
||||||
|
//
|
||||||
let isAvailable = true
|
// let isAvailable = true
|
||||||
let id = UUID()
|
// let id = UUID()
|
||||||
var name: String { "Preview Store" }
|
// var name: String { "Preview Store" }
|
||||||
@Published var secrets: [Secret] = []
|
// @Published var secrets: [Secret] = []
|
||||||
|
//
|
||||||
init(secrets: [Secret]) {
|
// init(secrets: [Secret]) {
|
||||||
self.secrets.append(contentsOf: secrets)
|
// self.secrets.append(contentsOf: secrets)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
init(numberOfRandomSecrets: Int = 5) {
|
// init(numberOfRandomSecrets: Int = 5) {
|
||||||
let new = (0..<numberOfRandomSecrets).map { Secret(name: String(describing: $0)) }
|
// let new = (0..<numberOfRandomSecrets).map { Secret(name: String(describing: $0)) }
|
||||||
self.secrets.append(contentsOf: new)
|
// self.secrets.append(contentsOf: new)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance) throws -> Data {
|
// func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance) throws -> Data {
|
||||||
return data
|
// return data
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func verify(signature data: Data, for signature: Data, with secret: Preview.Secret) throws -> Bool {
|
// func verify(signature data: Data, for signature: Data, with secret: Preview.Secret) throws -> Bool {
|
||||||
true
|
// true
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func existingPersistedAuthenticationContext(secret: Preview.Secret) -> PersistedAuthenticationContext? {
|
// func existingPersistedAuthenticationContext(secret: Preview.Secret) -> PersistedAuthenticationContext? {
|
||||||
nil
|
// nil
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func persistAuthentication(secret: Preview.Secret, forDuration duration: TimeInterval) throws {
|
// func persistAuthentication(secret: Preview.Secret, forDuration duration: TimeInterval) throws {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func reloadSecrets() {
|
// func reloadSecrets() {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
class StoreModifiable: Store, SecretStoreModifiable {
|
// class StoreModifiable: Store, SecretStoreModifiable {
|
||||||
override var name: String { "Modifiable Preview Store" }
|
// override var name: String { "Modifiable Preview Store" }
|
||||||
|
//
|
||||||
func create(name: String, requiresAuthentication: Bool) throws {
|
// func create(name: String, requiresAuthentication: Bool) throws {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func delete(secret: Preview.Secret) throws {
|
// func delete(secret: Preview.Secret) throws {
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func update(secret: Preview.Secret, name: String) throws {
|
// func update(secret: Preview.Secret, name: String) throws {
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
extension Preview {
|
//extension Preview {
|
||||||
|
//
|
||||||
static func storeList(stores: [Store] = [], modifiableStores: [StoreModifiable] = []) -> SecretStoreList {
|
// static func storeList(stores: [Store] = [], modifiableStores: [StoreModifiable] = []) -> SecretStoreList {
|
||||||
let list = SecretStoreList()
|
// let list = SecretStoreList()
|
||||||
for store in stores {
|
// for store in stores {
|
||||||
list.add(store: store)
|
// list.add(store: store)
|
||||||
}
|
// }
|
||||||
for storeModifiable in modifiableStores {
|
// for storeModifiable in modifiableStores {
|
||||||
list.add(store: storeModifiable)
|
// list.add(store: storeModifiable)
|
||||||
}
|
// }
|
||||||
return list
|
// return list
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
//}
|
||||||
|
@ -193,41 +193,41 @@ extension ContentView {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
//#if DEBUG
|
||||||
|
//
|
||||||
struct ContentView_Previews: PreviewProvider {
|
//struct ContentView_Previews: PreviewProvider {
|
||||||
|
//
|
||||||
private static let storeList: SecretStoreList = {
|
// private static let storeList: SecretStoreList = {
|
||||||
let list = SecretStoreList()
|
// let list = SecretStoreList()
|
||||||
list.add(store: SecureEnclave.Store())
|
// list.add(store: SecureEnclave.Store())
|
||||||
list.add(store: SmartCard.Store())
|
// list.add(store: SmartCard.Store())
|
||||||
return list
|
// return list
|
||||||
}()
|
// }()
|
||||||
private static let agentStatusChecker = AgentStatusChecker()
|
// private static let agentStatusChecker = AgentStatusChecker()
|
||||||
private static let justUpdatedChecker = JustUpdatedChecker()
|
// private static let justUpdatedChecker = JustUpdatedChecker()
|
||||||
|
//
|
||||||
@State var hasRunSetup = false
|
// @State var hasRunSetup = false
|
||||||
@State private var showingSetup = false
|
// @State private var showingSetup = false
|
||||||
@State private var showingCreation = false
|
// @State private var showingCreation = false
|
||||||
|
//
|
||||||
static var previews: some View {
|
// static var previews: some View {
|
||||||
Group {
|
// Group {
|
||||||
// Empty on modifiable and nonmodifiable
|
// // Empty on modifiable and nonmodifiable
|
||||||
ContentView<PreviewUpdater, AgentStatusChecker>(showingCreation: .constant(false), runningSetup: .constant(false), hasRunSetup: .constant(true))
|
// ContentView<PreviewUpdater, AgentStatusChecker>(showingCreation: .constant(false), runningSetup: .constant(false), hasRunSetup: .constant(true))
|
||||||
.environmentObject(Preview.storeList(stores: [Preview.Store(numberOfRandomSecrets: 0)], modifiableStores: [Preview.StoreModifiable(numberOfRandomSecrets: 0)]))
|
// .environmentObject(Preview.storeList(stores: [Preview.Store(numberOfRandomSecrets: 0)], modifiableStores: [Preview.StoreModifiable(numberOfRandomSecrets: 0)]))
|
||||||
.environmentObject(PreviewUpdater())
|
// .environmentObject(PreviewUpdater())
|
||||||
.environmentObject(agentStatusChecker)
|
// .environmentObject(agentStatusChecker)
|
||||||
|
//
|
||||||
// 5 items on modifiable and nonmodifiable
|
// // 5 items on modifiable and nonmodifiable
|
||||||
ContentView<PreviewUpdater, AgentStatusChecker>(showingCreation: .constant(false), runningSetup: .constant(false), hasRunSetup: .constant(true))
|
// ContentView<PreviewUpdater, AgentStatusChecker>(showingCreation: .constant(false), runningSetup: .constant(false), hasRunSetup: .constant(true))
|
||||||
.environmentObject(Preview.storeList(stores: [Preview.Store()], modifiableStores: [Preview.StoreModifiable()]))
|
// .environmentObject(Preview.storeList(stores: [Preview.Store()], modifiableStores: [Preview.StoreModifiable()]))
|
||||||
.environmentObject(PreviewUpdater())
|
// .environmentObject(PreviewUpdater())
|
||||||
.environmentObject(agentStatusChecker)
|
// .environmentObject(agentStatusChecker)
|
||||||
}
|
// }
|
||||||
.environmentObject(agentStatusChecker)
|
// .environmentObject(agentStatusChecker)
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
#endif
|
//#endif
|
||||||
|
|
||||||
|
@ -45,9 +45,10 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func save() {
|
func save() {
|
||||||
// FIXME: THIS
|
Task {
|
||||||
// try! store.create(name: name, requiresAuthentication: requiresAuthentication)
|
try! await store.create(name: name, requiresAuthentication: requiresAuthentication)
|
||||||
showing = false
|
showing = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -231,19 +232,19 @@ struct NotificationView: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
//#if DEBUG
|
||||||
|
//
|
||||||
struct CreateSecretView_Previews: PreviewProvider {
|
//struct CreateSecretView_Previews: PreviewProvider {
|
||||||
|
//
|
||||||
static var previews: some View {
|
// static var previews: some View {
|
||||||
Group {
|
// Group {
|
||||||
CreateSecretView(store: Preview.StoreModifiable(), showing: .constant(true))
|
// CreateSecretView(store: Preview.StoreModifiable(), showing: .constant(true))
|
||||||
AuthenticationView().environment(\.colorScheme, .dark)
|
// AuthenticationView().environment(\.colorScheme, .dark)
|
||||||
AuthenticationView().environment(\.colorScheme, .light)
|
// AuthenticationView().environment(\.colorScheme, .light)
|
||||||
NotificationView().environment(\.colorScheme, .dark)
|
// NotificationView().environment(\.colorScheme, .dark)
|
||||||
NotificationView().environment(\.colorScheme, .light)
|
// NotificationView().environment(\.colorScheme, .light)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
#endif
|
//#endif
|
||||||
|
@ -49,9 +49,10 @@ struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func delete() {
|
func delete() {
|
||||||
// FIXME: THIS
|
Task {
|
||||||
// try! store.delete(secret: secret)
|
try! await store.delete(secret: secret)
|
||||||
dismissalBlock(true)
|
dismissalBlock(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,9 @@ struct RenameSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func rename() {
|
func rename() {
|
||||||
// FIXME: THIS
|
Task {
|
||||||
// try? await store.update(secret: secret, name: newName)
|
try? await store.update(secret: secret, name: newName)
|
||||||
dismissalBlock(true)
|
dismissalBlock(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,12 +47,12 @@ struct SecretDetailView<SecretType: Secret>: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
//#if DEBUG
|
||||||
|
//
|
||||||
struct SecretDetailView_Previews: PreviewProvider {
|
//struct SecretDetailView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
// static var previews: some View {
|
||||||
SecretDetailView(secret: Preview.Store(numberOfRandomSecrets: 1).secrets[0])
|
// SecretDetailView(secret: Preview.Store(numberOfRandomSecrets: 1).secrets[0])
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
#endif
|
//#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user