mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-01-07 20:17:09 +00:00
Fix concurrency issues in SmartCardStore
This commit is contained in:
parent
970e407e29
commit
c2563be404
@ -7,55 +7,44 @@ import LocalAuthentication
|
|||||||
import SecretKit
|
import SecretKit
|
||||||
|
|
||||||
extension SmartCard {
|
extension SmartCard {
|
||||||
|
|
||||||
|
private struct State {
|
||||||
|
var isAvailable = false
|
||||||
|
var name = String(localized: "smart_card")
|
||||||
|
var secrets: [Secret] = []
|
||||||
|
let watcher = TKTokenWatcher()
|
||||||
|
var tokenID: String? = nil
|
||||||
|
}
|
||||||
|
|
||||||
/// An implementation of Store backed by a Smart Card.
|
/// An implementation of Store backed by a Smart Card.
|
||||||
@Observable public final class Store: SecretStore {
|
@Observable public final class Store: SecretStore {
|
||||||
|
|
||||||
|
private let state: Mutex<State> = .init(.init())
|
||||||
public var isAvailable: Bool {
|
public var isAvailable: Bool {
|
||||||
_isAvailable.withLock { $0 }
|
state.withLock { $0.isAvailable }
|
||||||
}
|
}
|
||||||
private let _isAvailable: Mutex<Bool> = .init(false)
|
|
||||||
|
|
||||||
public let id = UUID()
|
public let id = UUID()
|
||||||
public var name: String {
|
public var name: String {
|
||||||
_name.withLock { $0 }
|
state.withLock { $0.name }
|
||||||
}
|
}
|
||||||
private let _name: Mutex<String> = .init(String(localized: "smart_card"))
|
|
||||||
public var secrets: [Secret] {
|
public var secrets: [Secret] {
|
||||||
_secrets.withLock { $0 }
|
state.withLock { $0.secrets }
|
||||||
}
|
}
|
||||||
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.withLock { tokenID in
|
state.withLock { state in
|
||||||
watcher.withLock { watcher in
|
if let tokenID = state.tokenID {
|
||||||
let id = watcher.nonSecureEnclaveTokens.first
|
state.isAvailable = true
|
||||||
watcher.setInsertionHandler { string in
|
state.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: tokenID)
|
||||||
// guard self.tokenID == nil else { return }
|
}
|
||||||
// guard !string.contains("setoken") else { return }
|
state.watcher.setInsertionHandler { id in
|
||||||
//
|
// Setting insertion handler will cause it to be called immediately.
|
||||||
//// self.tokenID.withLock {
|
// Make a thread jump so we don't hit a recursive lock attempt.
|
||||||
//// $0 = string
|
Task {
|
||||||
//// }
|
self.smartcardInserted(for: id)
|
||||||
// // DispatchQueue.main.async {
|
|
||||||
// // reload()
|
|
||||||
// // }
|
|
||||||
// watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string)
|
|
||||||
}
|
}
|
||||||
tokenID = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// FIXME: THIS
|
|
||||||
if let tokenID = tokenID.withLock({ $0 }) {
|
|
||||||
_isAvailable.withLock {
|
|
||||||
$0 = true
|
|
||||||
}
|
|
||||||
watcher.withLock {
|
|
||||||
$0.addRemovalHandler(self.smartcardRemoved, forTokenID: tokenID)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadSecrets()
|
loadSecrets()
|
||||||
@ -72,7 +61,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.withLock({ $0 }) else { fatalError() }
|
guard let tokenID = state.withLock({ $0.tokenID }) 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")
|
||||||
@ -142,12 +131,11 @@ extension SmartCard {
|
|||||||
extension SmartCard.Store {
|
extension SmartCard.Store {
|
||||||
|
|
||||||
private func reloadSecretsInternal() {
|
private func reloadSecretsInternal() {
|
||||||
_isAvailable.withLock {
|
let before = state.withLock {
|
||||||
$0 = tokenID.withLock({ $0 }) != nil
|
$0.isAvailable = $0.tokenID != nil
|
||||||
}
|
let before = $0.secrets
|
||||||
let before = self.secrets
|
$0.secrets.removeAll()
|
||||||
self._secrets.withLock {
|
return before
|
||||||
$0.removeAll()
|
|
||||||
}
|
}
|
||||||
self.loadSecrets()
|
self.loadSecrets()
|
||||||
if self.secrets != before {
|
if self.secrets != before {
|
||||||
@ -155,25 +143,38 @@ extension SmartCard.Store {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resets the token ID and reloads secrets.
|
||||||
|
/// - Parameter tokenID: The ID of the token that was inserted.
|
||||||
|
private func smartcardInserted(for tokenID: String? = nil) {
|
||||||
|
state.withLock { state in
|
||||||
|
guard let string = state.watcher.nonSecureEnclaveTokens.first else { return }
|
||||||
|
guard state.tokenID == nil else { return }
|
||||||
|
guard !string.contains("setoken") else { return }
|
||||||
|
state.tokenID = string
|
||||||
|
state.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string)
|
||||||
|
state.tokenID = string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 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.withLock {
|
state.withLock {
|
||||||
$0 = nil
|
$0.tokenID = 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.withLock({ $0 }) else { return }
|
guard let tokenID = state.withLock({ $0.tokenID }) else { return }
|
||||||
|
|
||||||
let fallbackName = String(localized: "smart_card")
|
let fallbackName = String(localized: "smart_card")
|
||||||
_name.withLock {
|
state.withLock {
|
||||||
if let driverName = watcher.withLock({ $0.tokenInfo(forTokenID: tokenID)?.driverName }) {
|
if let driverName = $0.watcher.tokenInfo(forTokenID: tokenID)?.driverName {
|
||||||
$0 = driverName
|
$0.name = driverName
|
||||||
} else {
|
} else {
|
||||||
$0 = fallbackName
|
$0.name = fallbackName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,8 +199,8 @@ 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.withLock {
|
state.withLock {
|
||||||
$0.append(contentsOf: wrapped)
|
$0.secrets.append(contentsOf: wrapped)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,7 +245,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.withLock({ $0 }) else { fatalError() }
|
guard let tokenID = state.withLock({ $0.tokenID }) 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")
|
||||||
|
Loading…
Reference in New Issue
Block a user