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 }
}
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
guard let duration = duration else { return }
try? store.persistAuthentication(secret: secret, forDuration: duration)
@ -42,7 +42,9 @@ class Notifier {
if #available(macOS 12.0, *) {
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) {
notificationContent.attachments = [attachment]
}
@ -77,8 +79,8 @@ extension Notifier: SigningWitness {
func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) throws {
}
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) throws {
notify(accessTo: secret, from: store, by: provenance)
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, requiredAuthentication: Bool) throws {
notify(accessTo: secret, from: store, by: provenance, requiredAuthentication: requiredAuthentication)
}
}

View File

@ -93,7 +93,8 @@ extension Agent {
}
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)!
@ -134,7 +135,7 @@ extension Agent {
signedData.append(writer.lengthAndData(of: sub))
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")

View File

@ -4,6 +4,6 @@ import SecretKit
public protocol SigningWitness {
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 _name: () -> String
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 var sink: AnyCancellable?
@ -42,7 +42,7 @@ public class AnySecretStore: SecretStore {
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)
}

View File

@ -9,7 +9,7 @@ public protocol SecretStore: ObservableObject, Identifiable {
var name: String { 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?
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 keySize = 256
public let publicKey: Data
public let requiresAuthentication: Bool
}

View File

@ -96,14 +96,16 @@ extension SecureEnclave {
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
// TODO: RESTORE
if let existing = persistedAuthenticationContexts[secret], existing.valid {
context = existing.context
} else {
let newContext = LAContext()
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.localizedReason = "sign a request from \"\(provenance.origin.displayName)\" using secret \"\(secret.name)\""
@ -127,10 +129,17 @@ extension SecureEnclave {
}
let key = untypedSafe as! SecKey
var signError: SecurityError?
let signingStartTime = Date()
guard let signature = SecKeyCreateSignature(key, .ecdsaSignatureMessageX962SHA256, data as CFData, &signError) else {
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 {
@ -172,8 +181,7 @@ extension SecureEnclave.Store {
let publicKeyRef = $0[kSecValueRef] as! SecKey
let publicKeyAttributes = SecKeyCopyAttributes(publicKeyRef) as! [CFString: Any]
let publicKey = publicKeyAttributes[kSecValueData] as! Data
// TODO: FIX
return SecureEnclave.Secret(id: id, name: name, publicKey: publicKey, requiresAuthentication: false)
return SecureEnclave.Secret(id: id, name: name, publicKey: publicKey)
}
secrets.append(contentsOf: wrapped)
}
@ -223,9 +231,7 @@ extension SecureEnclave {
enum Constants {
static let keyTag = "com.maxgoedjen.secretive.secureenclave.key".data(using: .utf8)! as CFData
static let keyType = kSecAttrKeyTypeECSECPrimeRandom
static let durationOneMinute: TimeInterval = 60
static let durationFiveMinutes = durationOneMinute * 5
static let durationSixtyMinutes = durationOneMinute * 60
static let unauthenticatedThreshold: TimeInterval = 0.05
}
}

View File

@ -44,7 +44,7 @@ extension SmartCard {
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() }
let context = LAContext()
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 {
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 {

View File

@ -13,6 +13,7 @@
50153E22250DECA300525160 /* SecretListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListItemView.swift */; };
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.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 */; };
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.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>"; };
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>"; };
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>"; };
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>"; };
@ -394,6 +396,7 @@
507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */,
50617DCA23FCECA10099B055 /* Secret.swift */,
50617DC623FCE4EA0099B055 /* SecretStore.swift */,
5035FF6D2737A2F4006FE1F6 /* SignedData.swift */,
);
path = Types;
sourceTree = "<group>";
@ -1059,6 +1062,7 @@
5099A02923FE35240062B6F2 /* SmartCardStore.swift in Sources */,
5099A02B23FE352C0062B6F2 /* SmartCardSecret.swift in Sources */,
50C385A3240789E600AF2719 /* OpenSSHReader.swift in Sources */,
5035FF6E2737A2F4006FE1F6 /* SignedData.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};