Prompting.

This commit is contained in:
Max Goedjen 2021-11-06 23:12:04 -07:00
parent 0a3ee77625
commit aec12491d8
No known key found for this signature in database
GPG Key ID: E58C21DD77B9B8E8
10 changed files with 39 additions and 21 deletions

View File

@ -30,7 +30,7 @@ class Notifier {
notificationCenter.requestAuthorization(options: .alert) { _, _ in } notificationCenter.requestAuthorization(options: .alert) { _, _ in }
} }
func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) { func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, requiredAuthentication: Bool) {
notificationDelegate.persistAuthentication = { duration in notificationDelegate.persistAuthentication = { duration in
guard let duration = duration else { return } guard let duration = duration else { return }
try? store.persistAuthentication(secret: secret, forDuration: duration) try? store.persistAuthentication(secret: secret, forDuration: duration)
@ -42,7 +42,9 @@ class Notifier {
if #available(macOS 12.0, *) { if #available(macOS 12.0, *) {
notificationContent.interruptionLevel = .timeSensitive notificationContent.interruptionLevel = .timeSensitive
} }
notificationContent.categoryIdentifier = Constants.persistAuthenticationCategoryIdentitifier if requiredAuthentication {
notificationContent.categoryIdentifier = Constants.persistAuthenticationCategoryIdentitifier
}
if let iconURL = provenance.origin.iconURL, let attachment = try? UNNotificationAttachment(identifier: "icon", url: iconURL, options: nil) { if let iconURL = provenance.origin.iconURL, let attachment = try? UNNotificationAttachment(identifier: "icon", url: iconURL, options: nil) {
notificationContent.attachments = [attachment] notificationContent.attachments = [attachment]
} }
@ -77,8 +79,8 @@ extension Notifier: SigningWitness {
func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) throws { func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) throws {
} }
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) throws { func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, requiredAuthentication: Bool) throws {
notify(accessTo: secret, from: store, by: provenance) notify(accessTo: secret, from: store, by: provenance, requiredAuthentication: requiredAuthentication)
} }
} }

View File

@ -93,7 +93,8 @@ extension Agent {
} }
let dataToSign = reader.readNextChunk() let dataToSign = reader.readNextChunk()
let derSignature = try store.sign(data: dataToSign, with: secret, for: provenance) let signed = try store.sign(data: dataToSign, with: secret, for: provenance)
let derSignature = signed.data
let curveData = writer.curveType(for: secret.algorithm, length: secret.keySize).data(using: .utf8)! let curveData = writer.curveType(for: secret.algorithm, length: secret.keySize).data(using: .utf8)!
@ -134,7 +135,7 @@ extension Agent {
signedData.append(writer.lengthAndData(of: sub)) signedData.append(writer.lengthAndData(of: sub))
if let witness = witness { if let witness = witness {
try witness.witness(accessTo: secret, from: store, by: provenance) try witness.witness(accessTo: secret, from: store, by: provenance, requiredAuthentication: signed.requiredAuthentication)
} }
Logger().debug("Agent signed request") Logger().debug("Agent signed request")

View File

@ -4,6 +4,6 @@ import SecretKit
public protocol SigningWitness { public protocol SigningWitness {
func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) throws func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) throws
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) throws func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, requiredAuthentication: Bool) throws
} }

View File

@ -8,7 +8,7 @@ public class AnySecretStore: SecretStore {
private let _id: () -> UUID private let _id: () -> UUID
private let _name: () -> String private let _name: () -> String
private let _secrets: () -> [AnySecret] private let _secrets: () -> [AnySecret]
private let _sign: (Data, AnySecret, SigningRequestProvenance) throws -> Data private let _sign: (Data, AnySecret, SigningRequestProvenance) throws -> SignedData
private let _persistAuthentication: (AnySecret, TimeInterval) throws -> Void private let _persistAuthentication: (AnySecret, TimeInterval) throws -> Void
private var sink: AnyCancellable? private var sink: AnyCancellable?
@ -42,7 +42,7 @@ public class AnySecretStore: SecretStore {
return _secrets() return _secrets()
} }
public func sign(data: Data, with secret: AnySecret, for provenance: SigningRequestProvenance) throws -> Data { public func sign(data: Data, with secret: AnySecret, for provenance: SigningRequestProvenance) throws -> SignedData {
try _sign(data, secret, provenance) try _sign(data, secret, provenance)
} }

View File

@ -9,7 +9,7 @@ public protocol SecretStore: ObservableObject, Identifiable {
var name: String { get } var name: String { get }
var secrets: [SecretType] { get } var secrets: [SecretType] { get }
func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> Data func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> SignedData
// TODO: MOVE TO SEPARATE PROTOCOL? // TODO: MOVE TO SEPARATE PROTOCOL?
func persistAuthentication(secret: SecretType, forDuration: TimeInterval) throws func persistAuthentication(secret: SecretType, forDuration: TimeInterval) throws

View File

@ -0,0 +1,6 @@
import Foundation
public struct SignedData {
public let data: Data
public let requiredAuthentication: Bool
}

View File

@ -10,7 +10,6 @@ extension SecureEnclave {
public let algorithm = Algorithm.ellipticCurve public let algorithm = Algorithm.ellipticCurve
public let keySize = 256 public let keySize = 256
public let publicKey: Data public let publicKey: Data
public let requiresAuthentication: Bool
} }

View File

@ -96,14 +96,16 @@ extension SecureEnclave {
reloadSecrets() reloadSecrets()
} }
public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> Data { public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> SignedData {
let context: LAContext let context: LAContext
// TODO: RESTORE
if let existing = persistedAuthenticationContexts[secret], existing.valid { if let existing = persistedAuthenticationContexts[secret], existing.valid {
context = existing.context context = existing.context
} else { } else {
let newContext = LAContext() let newContext = LAContext()
newContext.localizedCancelTitle = "Deny" newContext.localizedCancelTitle = "Deny"
pendingAuthenticationContext = PersistentAuthenticationContext(secret: secret, context: newContext, expiration: Date(timeIntervalSinceNow: Constants.durationOneMinute)) // TODO: FIX
pendingAuthenticationContext = PersistentAuthenticationContext(secret: secret, context: newContext, expiration: Date(timeIntervalSinceNow: 100))
context = newContext context = newContext
} }
context.localizedReason = "sign a request from \"\(provenance.origin.displayName)\" using secret \"\(secret.name)\"" context.localizedReason = "sign a request from \"\(provenance.origin.displayName)\" using secret \"\(secret.name)\""
@ -127,10 +129,17 @@ extension SecureEnclave {
} }
let key = untypedSafe as! SecKey let key = untypedSafe as! SecKey
var signError: SecurityError? var signError: SecurityError?
let signingStartTime = Date()
guard let signature = SecKeyCreateSignature(key, .ecdsaSignatureMessageX962SHA256, data as CFData, &signError) else { guard let signature = SecKeyCreateSignature(key, .ecdsaSignatureMessageX962SHA256, data as CFData, &signError) else {
throw SigningError(error: signError) throw SigningError(error: signError)
} }
return signature as Data let signatureDuration = Date().timeIntervalSince(signingStartTime)
// Hack to determine if the user had to authenticate to sign.
// Since there's now way to inspect SecAccessControl to determine.
let requiredAuthentication = signatureDuration > Constants.unauthenticatedThreshold
return SignedData(data: signature as Data, requiredAuthentication: requiredAuthentication)
} }
public func persistAuthentication(secret: Secret, forDuration: TimeInterval) throws { public func persistAuthentication(secret: Secret, forDuration: TimeInterval) throws {
@ -172,8 +181,7 @@ extension SecureEnclave.Store {
let publicKeyRef = $0[kSecValueRef] as! SecKey let publicKeyRef = $0[kSecValueRef] as! SecKey
let publicKeyAttributes = SecKeyCopyAttributes(publicKeyRef) as! [CFString: Any] let publicKeyAttributes = SecKeyCopyAttributes(publicKeyRef) as! [CFString: Any]
let publicKey = publicKeyAttributes[kSecValueData] as! Data let publicKey = publicKeyAttributes[kSecValueData] as! Data
// TODO: FIX return SecureEnclave.Secret(id: id, name: name, publicKey: publicKey)
return SecureEnclave.Secret(id: id, name: name, publicKey: publicKey, requiresAuthentication: false)
} }
secrets.append(contentsOf: wrapped) secrets.append(contentsOf: wrapped)
} }
@ -223,9 +231,7 @@ extension SecureEnclave {
enum Constants { enum Constants {
static let keyTag = "com.maxgoedjen.secretive.secureenclave.key".data(using: .utf8)! as CFData static let keyTag = "com.maxgoedjen.secretive.secureenclave.key".data(using: .utf8)! as CFData
static let keyType = kSecAttrKeyTypeECSECPrimeRandom static let keyType = kSecAttrKeyTypeECSECPrimeRandom
static let durationOneMinute: TimeInterval = 60 static let unauthenticatedThreshold: TimeInterval = 0.05
static let durationFiveMinutes = durationOneMinute * 5
static let durationSixtyMinutes = durationOneMinute * 60
} }
} }

View File

@ -44,7 +44,7 @@ extension SmartCard {
fatalError("Keys must be deleted on the smart card.") fatalError("Keys must be deleted on the smart card.")
} }
public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> Data { public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> SignedData {
guard let tokenID = tokenID else { fatalError() } guard let tokenID = tokenID else { fatalError() }
let context = LAContext() let context = LAContext()
context.localizedReason = "sign a request from \"\(provenance.origin.displayName)\" using secret \"\(secret.name)\"" context.localizedReason = "sign a request from \"\(provenance.origin.displayName)\" using secret \"\(secret.name)\""
@ -79,7 +79,7 @@ extension SmartCard {
guard let signature = SecKeyCreateSignature(key, signatureAlgorithm, data as CFData, &signError) else { guard let signature = SecKeyCreateSignature(key, signatureAlgorithm, data as CFData, &signError) else {
throw SigningError(error: signError) throw SigningError(error: signError)
} }
return signature as Data return SignedData(data: signature as Data, requiredAuthentication: false)
} }
public func persistAuthentication(secret: SmartCard.Secret, forDuration: TimeInterval) throws { public func persistAuthentication(secret: SmartCard.Secret, forDuration: TimeInterval) throws {

View File

@ -13,6 +13,7 @@
50153E22250DECA300525160 /* SecretListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListItemView.swift */; }; 50153E22250DECA300525160 /* SecretListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListItemView.swift */; };
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; }; 5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
501B7AE1251C56F700776EC7 /* SigningRequestProvenance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */; }; 501B7AE1251C56F700776EC7 /* SigningRequestProvenance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */; };
5035FF6E2737A2F4006FE1F6 /* SignedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5035FF6D2737A2F4006FE1F6 /* SignedData.swift */; };
50524B442420969E008DBD97 /* OpenSSHWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */; }; 50524B442420969E008DBD97 /* OpenSSHWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */; };
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; }; 50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; }; 50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; };
@ -228,6 +229,7 @@
50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; }; 50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; };
50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = "<group>"; }; 50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = "<group>"; };
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; }; 5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
5035FF6D2737A2F4006FE1F6 /* SignedData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignedData.swift; sourceTree = "<group>"; };
50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSHWriterTests.swift; sourceTree = "<group>"; }; 50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSHWriterTests.swift; sourceTree = "<group>"; };
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; }; 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; };
50571E0424393D1500F76F6C /* LaunchAgentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAgentController.swift; sourceTree = "<group>"; }; 50571E0424393D1500F76F6C /* LaunchAgentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAgentController.swift; sourceTree = "<group>"; };
@ -394,6 +396,7 @@
507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */, 507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */,
50617DCA23FCECA10099B055 /* Secret.swift */, 50617DCA23FCECA10099B055 /* Secret.swift */,
50617DC623FCE4EA0099B055 /* SecretStore.swift */, 50617DC623FCE4EA0099B055 /* SecretStore.swift */,
5035FF6D2737A2F4006FE1F6 /* SignedData.swift */,
); );
path = Types; path = Types;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1059,6 +1062,7 @@
5099A02923FE35240062B6F2 /* SmartCardStore.swift in Sources */, 5099A02923FE35240062B6F2 /* SmartCardStore.swift in Sources */,
5099A02B23FE352C0062B6F2 /* SmartCardSecret.swift in Sources */, 5099A02B23FE352C0062B6F2 /* SmartCardSecret.swift in Sources */,
50C385A3240789E600AF2719 /* OpenSSHReader.swift in Sources */, 50C385A3240789E600AF2719 /* OpenSSHReader.swift in Sources */,
5035FF6E2737A2F4006FE1F6 /* SignedData.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };