From aec12491d812432f50bc046b798a4a749db85ef8 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 6 Nov 2021 23:12:04 -0700 Subject: [PATCH] Prompting. --- SecretAgent/Notifier.swift | 10 +++++---- SecretAgentKit/Agent.swift | 5 +++-- SecretAgentKit/SigningWitness.swift | 2 +- SecretKit/Common/Erasers/AnySecretStore.swift | 4 ++-- SecretKit/Common/Types/SecretStore.swift | 2 +- SecretKit/Common/Types/SignedData.swift | 6 +++++ .../SecureEnclave/SecureEnclaveSecret.swift | 1 - .../SecureEnclave/SecureEnclaveStore.swift | 22 ++++++++++++------- SecretKit/SmartCard/SmartCardStore.swift | 4 ++-- Secretive.xcodeproj/project.pbxproj | 4 ++++ 10 files changed, 39 insertions(+), 21 deletions(-) create mode 100644 SecretKit/Common/Types/SignedData.swift diff --git a/SecretAgent/Notifier.swift b/SecretAgent/Notifier.swift index f302d69..e32735a 100644 --- a/SecretAgent/Notifier.swift +++ b/SecretAgent/Notifier.swift @@ -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) } } diff --git a/SecretAgentKit/Agent.swift b/SecretAgentKit/Agent.swift index 6fced2d..3e69037 100644 --- a/SecretAgentKit/Agent.swift +++ b/SecretAgentKit/Agent.swift @@ -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") diff --git a/SecretAgentKit/SigningWitness.swift b/SecretAgentKit/SigningWitness.swift index c1b57df..59e2b6b 100644 --- a/SecretAgentKit/SigningWitness.swift +++ b/SecretAgentKit/SigningWitness.swift @@ -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 } diff --git a/SecretKit/Common/Erasers/AnySecretStore.swift b/SecretKit/Common/Erasers/AnySecretStore.swift index ecf6601..7b565fe 100644 --- a/SecretKit/Common/Erasers/AnySecretStore.swift +++ b/SecretKit/Common/Erasers/AnySecretStore.swift @@ -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) } diff --git a/SecretKit/Common/Types/SecretStore.swift b/SecretKit/Common/Types/SecretStore.swift index 7274b19..e57e1c1 100644 --- a/SecretKit/Common/Types/SecretStore.swift +++ b/SecretKit/Common/Types/SecretStore.swift @@ -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 diff --git a/SecretKit/Common/Types/SignedData.swift b/SecretKit/Common/Types/SignedData.swift new file mode 100644 index 0000000..14b529c --- /dev/null +++ b/SecretKit/Common/Types/SignedData.swift @@ -0,0 +1,6 @@ +import Foundation + +public struct SignedData { + public let data: Data + public let requiredAuthentication: Bool +} diff --git a/SecretKit/SecureEnclave/SecureEnclaveSecret.swift b/SecretKit/SecureEnclave/SecureEnclaveSecret.swift index 5415b48..cb6bcc1 100644 --- a/SecretKit/SecureEnclave/SecureEnclaveSecret.swift +++ b/SecretKit/SecureEnclave/SecureEnclaveSecret.swift @@ -10,7 +10,6 @@ extension SecureEnclave { public let algorithm = Algorithm.ellipticCurve public let keySize = 256 public let publicKey: Data - public let requiresAuthentication: Bool } diff --git a/SecretKit/SecureEnclave/SecureEnclaveStore.swift b/SecretKit/SecureEnclave/SecureEnclaveStore.swift index 8e6ca28..f86d479 100644 --- a/SecretKit/SecureEnclave/SecureEnclaveStore.swift +++ b/SecretKit/SecureEnclave/SecureEnclaveStore.swift @@ -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 } } diff --git a/SecretKit/SmartCard/SmartCardStore.swift b/SecretKit/SmartCard/SmartCardStore.swift index 1be657d..af28318 100644 --- a/SecretKit/SmartCard/SmartCardStore.swift +++ b/SecretKit/SmartCard/SmartCardStore.swift @@ -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 { diff --git a/Secretive.xcodeproj/project.pbxproj b/Secretive.xcodeproj/project.pbxproj index c458f90..630f5c5 100644 --- a/Secretive.xcodeproj/project.pbxproj +++ b/Secretive.xcodeproj/project.pbxproj @@ -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 = ""; }; 50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = ""; }; 5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = ""; }; + 5035FF6D2737A2F4006FE1F6 /* SignedData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignedData.swift; sourceTree = ""; }; 50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSHWriterTests.swift; sourceTree = ""; }; 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = ""; }; 50571E0424393D1500F76F6C /* LaunchAgentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAgentController.swift; sourceTree = ""; }; @@ -394,6 +396,7 @@ 507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */, 50617DCA23FCECA10099B055 /* Secret.swift */, 50617DC623FCE4EA0099B055 /* SecretStore.swift */, + 5035FF6D2737A2F4006FE1F6 /* SignedData.swift */, ); path = Types; sourceTree = ""; @@ -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; };