mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-04-19 05:52:12 +00:00
Prompting.
This commit is contained in:
parent
0a3ee77625
commit
aec12491d8
@ -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
|
||||||
}
|
}
|
||||||
|
if requiredAuthentication {
|
||||||
notificationContent.categoryIdentifier = Constants.persistAuthenticationCategoryIdentitifier
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
6
SecretKit/Common/Types/SignedData.swift
Normal file
6
SecretKit/Common/Types/SignedData.swift
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct SignedData {
|
||||||
|
public let data: Data
|
||||||
|
public let requiredAuthentication: Bool
|
||||||
|
}
|
@ -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
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user