mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-04-10 17:47:19 +00:00
Updates.
This commit is contained in:
parent
e7e9f2b7f9
commit
832d82304d
@ -113,7 +113,7 @@ extension Agent {
|
|||||||
|
|
||||||
let dataToSign = reader.readNextChunk()
|
let dataToSign = reader.readNextChunk()
|
||||||
let signed = 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 derSignature = signed
|
||||||
|
|
||||||
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)!
|
||||||
|
|
||||||
@ -154,7 +154,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, requiredAuthentication: signed.requiredAuthentication)
|
try witness.witness(accessTo: secret, from: store, by: provenance)
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger().debug("Agent signed request")
|
Logger().debug("Agent signed request")
|
||||||
|
@ -17,7 +17,6 @@ public protocol SigningWitness {
|
|||||||
/// - secret: The `Secret` that will was used to sign the request.
|
/// - secret: The `Secret` that will was used to sign the request.
|
||||||
/// - store: The `Store` that signed the request..
|
/// - store: The `Store` that signed the request..
|
||||||
/// - provenance: A `SigningRequestProvenance` object describing the origin of the request.
|
/// - provenance: A `SigningRequestProvenance` object describing the origin of the request.
|
||||||
/// - requiredAuthentication: A boolean describing whether or not authentication was required for the request.
|
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
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,5 +27,8 @@ SecretKit is a collection of protocols describing secrets and stores.
|
|||||||
|
|
||||||
### Signing Process
|
### Signing Process
|
||||||
|
|
||||||
- ``SignedData``
|
|
||||||
- ``SigningRequestProvenance``
|
- ``SigningRequestProvenance``
|
||||||
|
|
||||||
|
### Authentication Persistence
|
||||||
|
|
||||||
|
- ``PersistedAuthenticationContext``
|
||||||
|
@ -9,7 +9,8 @@ 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 -> SignedData
|
private let _sign: (Data, AnySecret, SigningRequestProvenance) throws -> Data
|
||||||
|
private let _existingPersistedAuthenticationContext: (AnySecret) -> PersistedAuthenticationContext?
|
||||||
private let _persistAuthentication: (AnySecret, TimeInterval) throws -> Void
|
private let _persistAuthentication: (AnySecret, TimeInterval) throws -> Void
|
||||||
|
|
||||||
private var sink: AnyCancellable?
|
private var sink: AnyCancellable?
|
||||||
@ -21,6 +22,7 @@ public class AnySecretStore: SecretStore {
|
|||||||
_id = { secretStore.id }
|
_id = { secretStore.id }
|
||||||
_secrets = { secretStore.secrets.map { AnySecret($0) } }
|
_secrets = { secretStore.secrets.map { AnySecret($0) } }
|
||||||
_sign = { try secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType, for: $2) }
|
_sign = { try secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType, for: $2) }
|
||||||
|
_existingPersistedAuthenticationContext = { secretStore.existingPersistedAuthenticationContext(secret: $0.base as! SecretStoreType.SecretType) }
|
||||||
_persistAuthentication = { try secretStore.persistAuthentication(secret: $0.base as! SecretStoreType.SecretType, forDuration: $1) }
|
_persistAuthentication = { try secretStore.persistAuthentication(secret: $0.base as! SecretStoreType.SecretType, forDuration: $1) }
|
||||||
sink = secretStore.objectWillChange.sink { _ in
|
sink = secretStore.objectWillChange.sink { _ in
|
||||||
self.objectWillChange.send()
|
self.objectWillChange.send()
|
||||||
@ -43,10 +45,14 @@ public class AnySecretStore: SecretStore {
|
|||||||
return _secrets()
|
return _secrets()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sign(data: Data, with secret: AnySecret, for provenance: SigningRequestProvenance) throws -> SignedData {
|
public func sign(data: Data, with secret: AnySecret, for provenance: SigningRequestProvenance) throws -> Data {
|
||||||
try _sign(data, secret, provenance)
|
try _sign(data, secret, provenance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func existingPersistedAuthenticationContext(secret: AnySecret) -> PersistedAuthenticationContext? {
|
||||||
|
_existingPersistedAuthenticationContext(secret)
|
||||||
|
}
|
||||||
|
|
||||||
public func persistAuthentication(secret: AnySecret, forDuration duration: TimeInterval) throws {
|
public func persistAuthentication(secret: AnySecret, forDuration duration: TimeInterval) throws {
|
||||||
try _persistAuthentication(secret, duration)
|
try _persistAuthentication(secret, duration)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Protocol describing a persisted authentication context. This is an authorization that can be reused for multiple access to a secret that requires authentication for a specific period of time.
|
||||||
|
public protocol PersistedAuthenticationContext {
|
||||||
|
/// Whether the context remains valid.
|
||||||
|
var valid: Bool { get }
|
||||||
|
/// The date at which the authorization expires and the context becomes invalid.
|
||||||
|
var expiration: Date { get }
|
||||||
|
}
|
@ -20,8 +20,14 @@ public protocol SecretStore: ObservableObject, Identifiable {
|
|||||||
/// - data: The data to sign.
|
/// - data: The data to sign.
|
||||||
/// - secret: The ``Secret`` to sign with.
|
/// - secret: The ``Secret`` to sign with.
|
||||||
/// - provenance: A ``SigningRequestProvenance`` describing where the request came from.
|
/// - provenance: A ``SigningRequestProvenance`` describing where the request came from.
|
||||||
/// - Returns: A ``SignedData`` object, containing the signature and metadata about the signature process.
|
/// - Returns: The signed data.
|
||||||
func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> SignedData
|
func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> Data
|
||||||
|
|
||||||
|
/// Checks to see if there is currently a valid persisted authentication for a given secret.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - secret: The ``Secret`` to check if there is a persisted authentication for.
|
||||||
|
/// - Returns: A persisted authentication context, if a valid one exists.
|
||||||
|
func existingPersistedAuthenticationContext(secret: SecretType) -> PersistedAuthenticationContext?
|
||||||
|
|
||||||
/// Persists user authorization for access to a secret.
|
/// Persists user authorization for access to a secret.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// Describes the output of a sign request.
|
|
||||||
public struct SignedData {
|
|
||||||
|
|
||||||
/// The signed data.
|
|
||||||
public let data: Data
|
|
||||||
/// A boolean describing whether authentication was required during the signature process.
|
|
||||||
public let requiredAuthentication: Bool
|
|
||||||
|
|
||||||
/// Initializes a new SignedData.
|
|
||||||
/// - Parameters:
|
|
||||||
/// - data: The signed data.
|
|
||||||
/// - requiredAuthentication: A boolean describing whether authentication was required during the signature process.
|
|
||||||
public init(data: Data, requiredAuthentication: Bool) {
|
|
||||||
self.data = data
|
|
||||||
self.requiredAuthentication = requiredAuthentication
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -100,7 +100,7 @@ extension SecureEnclave {
|
|||||||
reloadSecrets()
|
reloadSecrets()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> SignedData {
|
public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> Data {
|
||||||
let context: LAContext
|
let context: LAContext
|
||||||
if let existing = persistedAuthenticationContexts[secret], existing.valid {
|
if let existing = persistedAuthenticationContexts[secret], existing.valid {
|
||||||
context = existing.context
|
context = existing.context
|
||||||
@ -131,16 +131,15 @@ 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)
|
||||||
}
|
}
|
||||||
let signatureDuration = Date().timeIntervalSince(signingStartTime)
|
return signature as Data
|
||||||
// Hack to determine if the user had to authenticate to sign.
|
}
|
||||||
// Since there's now way to inspect SecAccessControl to determine (afaict).
|
|
||||||
let requiredAuthentication = signatureDuration > Constants.unauthenticatedThreshold
|
|
||||||
|
|
||||||
return SignedData(data: signature as Data, requiredAuthentication: requiredAuthentication)
|
public func existingPersistedAuthenticationContext(secret: Secret) -> PersistedAuthenticationContext? {
|
||||||
|
guard let persisted = persistedAuthenticationContexts[secret], persisted.valid else { return nil }
|
||||||
|
return persisted
|
||||||
}
|
}
|
||||||
|
|
||||||
public func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) throws {
|
public func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) throws {
|
||||||
@ -294,7 +293,7 @@ extension SecureEnclave {
|
|||||||
extension SecureEnclave {
|
extension SecureEnclave {
|
||||||
|
|
||||||
/// A context describing a persisted authentication.
|
/// A context describing a persisted authentication.
|
||||||
private struct PersistentAuthenticationContext {
|
private struct PersistentAuthenticationContext: PersistedAuthenticationContext {
|
||||||
|
|
||||||
/// The Secret to persist authentication for.
|
/// The Secret to persist authentication for.
|
||||||
let secret: Secret
|
let secret: Secret
|
||||||
@ -302,7 +301,7 @@ extension SecureEnclave {
|
|||||||
let context: LAContext
|
let context: LAContext
|
||||||
/// An expiration date for the context.
|
/// An expiration date for the context.
|
||||||
/// - Note - Monotonic time instead of Date() to prevent people setting the clock back.
|
/// - Note - Monotonic time instead of Date() to prevent people setting the clock back.
|
||||||
let expiration: UInt64
|
let monotonicExpiration: UInt64
|
||||||
|
|
||||||
/// Initializes a context.
|
/// Initializes a context.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
@ -313,12 +312,18 @@ extension SecureEnclave {
|
|||||||
self.secret = secret
|
self.secret = secret
|
||||||
self.context = context
|
self.context = context
|
||||||
let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value
|
let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value
|
||||||
self.expiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds)
|
self.monotonicExpiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A boolean describing whether or not the context is still valid.
|
/// A boolean describing whether or not the context is still valid.
|
||||||
var valid: Bool {
|
var valid: Bool {
|
||||||
clock_gettime_nsec_np(CLOCK_MONOTONIC) < expiration
|
clock_gettime_nsec_np(CLOCK_MONOTONIC) < monotonicExpiration
|
||||||
|
}
|
||||||
|
|
||||||
|
var expiration: Date {
|
||||||
|
let remainingNanoseconds = monotonicExpiration - clock_gettime_nsec_np(CLOCK_MONOTONIC)
|
||||||
|
let remainingInSeconds = Measurement(value: Double(remainingNanoseconds), unit: UnitDuration.nanoseconds).converted(to: .seconds).value
|
||||||
|
return Date(timeIntervalSinceNow: remainingInSeconds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 -> SignedData {
|
public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> Data {
|
||||||
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,11 @@ 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 SignedData(data: signature as Data, requiredAuthentication: false)
|
return signature as Data
|
||||||
|
}
|
||||||
|
|
||||||
|
public func existingPersistedAuthenticationContext(secret: SmartCard.Secret) -> PersistedAuthenticationContext? {
|
||||||
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func persistAuthentication(secret: SmartCard.Secret, forDuration: TimeInterval) throws {
|
public func persistAuthentication(secret: SmartCard.Secret, forDuration: TimeInterval) throws {
|
||||||
|
@ -57,7 +57,7 @@ class Notifier {
|
|||||||
notificationCenter.requestAuthorization(options: .alert) { _, _ in }
|
notificationCenter.requestAuthorization(options: .alert) { _, _ in }
|
||||||
}
|
}
|
||||||
|
|
||||||
func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, requiredAuthentication: Bool) {
|
func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) {
|
||||||
notificationDelegate.pendingPersistableSecrets[secret.id.description] = secret
|
notificationDelegate.pendingPersistableSecrets[secret.id.description] = secret
|
||||||
notificationDelegate.pendingPersistableStores[store.id.description] = store
|
notificationDelegate.pendingPersistableStores[store.id.description] = store
|
||||||
let notificationCenter = UNUserNotificationCenter.current()
|
let notificationCenter = UNUserNotificationCenter.current()
|
||||||
@ -69,7 +69,7 @@ class Notifier {
|
|||||||
if #available(macOS 12.0, *) {
|
if #available(macOS 12.0, *) {
|
||||||
notificationContent.interruptionLevel = .timeSensitive
|
notificationContent.interruptionLevel = .timeSensitive
|
||||||
}
|
}
|
||||||
if requiredAuthentication {
|
if secret.requiresAuthentication && store.existingPersistedAuthenticationContext(secret: secret) == nil {
|
||||||
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) {
|
||||||
@ -106,8 +106,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, requiredAuthentication: Bool) throws {
|
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) throws {
|
||||||
notify(accessTo: secret, from: store, by: provenance, requiredAuthentication: requiredAuthentication)
|
notify(accessTo: secret, from: store, by: provenance)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,8 @@ extension Preview {
|
|||||||
self.secrets.append(contentsOf: new)
|
self.secrets.append(contentsOf: new)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance) throws -> SignedData {
|
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance) throws -> Data {
|
||||||
return SignedData(data: data, requiredAuthentication: false)
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func persistAuthentication(secret: Preview.Secret, forDuration duration: TimeInterval) throws {
|
func persistAuthentication(secret: Preview.Secret, forDuration duration: TimeInterval) throws {
|
||||||
|
Loading…
Reference in New Issue
Block a user