This commit is contained in:
Max Goedjen 2022-01-01 17:54:40 -08:00
parent fc294d9f3c
commit 56cc559a97
No known key found for this signature in database
GPG Key ID: E58C21DD77B9B8E8
5 changed files with 66 additions and 1 deletions

View File

@ -1,13 +1,18 @@
import Foundation
/// Reads OpenSSH protocol data.
public class OpenSSHReader {
var remaining: Data
/// Initialize the reader with an OpenSSH data payload.
/// - Parameter data: the data to read.
public init(data: Data) {
remaining = Data(data)
}
/// Reads the next chunk of data from the playload.
/// - Returns: The next chunk of data.
public func readNextChunk() -> Data {
let lengthRange = 0..<(UInt32.bitWidth/8)
let lengthChunk = remaining[lengthRange]

View File

@ -1,25 +1,52 @@
import Foundation
import Combine
/// Manages access to Secrets, and performs signature operations on data using those Secrets.
public protocol SecretStore: ObservableObject, Identifiable {
associatedtype SecretType: Secret
/// A boolean indicating whether or not the store is available.
var isAvailable: Bool { get }
/// A unique identifier for the store.
var id: UUID { get }
/// A user-facing name for the store.
var name: String { get }
/// The secrets the store manages.
var secrets: [SecretType] { get }
/// Signs a data payload with a specified Secret.
/// - Parameters:
/// - data: The data to sign.
/// - secret: The ``Secret`` to sign with.
/// - provenance: A ``SigningRequestProvenance`` describing where the request came from.
/// - Returns: A ``SignedData`` object, containing the signature and metadata about the signature process.
func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> SignedData
/// Persists user authorization for access to a secret.
/// - Parameters:
/// - secret: The ``Secret`` to persist the authorization for.
/// - duration: The duration that the authorization should persist for.
/// - Note: This is used for temporarily unlocking access to a secret which would otherwise require authentication every single use. This is useful for situations where the user anticipates several rapid accesses to a authorization-guarded secret.
func persistAuthentication(secret: SecretType, forDuration duration: TimeInterval) throws
}
/// A SecretStore that the Secretive admin app can modify.
public protocol SecretStoreModifiable: SecretStore {
/// Creates a new ``Secret`` in the store.
/// - Parameters:
/// - name: The user-facing name for the ``Secret``.
/// - requiresAuthentication: A boolean indicating whether or not the user will be required to authenticate before performing signature operations with the secret.
func create(name: String, requiresAuthentication: Bool) throws
/// Deletes a Secret in the store.
/// - Parameters:
/// - secret: The ``Secret`` to delete.
func delete(secret: SecretType) throws
func update(secret: SecretType, name: String) throws
}

View File

@ -1,10 +1,17 @@
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

View File

@ -1,8 +1,11 @@
import Foundation
import AppKit
/// Describes the chain of applications that requested a signature operation.
public struct SigningRequestProvenance: Equatable {
/// A list of processes involved in the request.
/// - Note: A chain will typically consist of many elements even for a simple request. For example, running `git fetch` in Terminal.app would generate a request chain of `ssh` -> `git` -> `zsh` -> `login` -> `Terminal.app`
public var chain: [Process]
public init(root: Process) {
self.chain = [root]
@ -12,10 +15,12 @@ public struct SigningRequestProvenance: Equatable {
extension SigningRequestProvenance {
/// The `Process` which initiated the signing request.
public var origin: Process {
chain.last!
}
/// A boolean describing whether all processes in the request chain had a valid code signature.
public var intact: Bool {
chain.allSatisfy { $0.validSignature }
}
@ -24,16 +29,33 @@ extension SigningRequestProvenance {
extension SigningRequestProvenance {
/// Describes a process in a `SigningRequestProvenance` chain.
public struct Process: Equatable {
/// The pid of the process.
public let pid: Int32
/// A user-facing name for the process.
public let processName: String
/// A user-facing name for the application, if one exists.
public let appName: String?
/// An icon representation of the application, if one exists.
public let iconURL: URL?
/// The path the process exists at.
public let path: String
/// A boolean describing whether or not the process has a valid code signature.
public let validSignature: Bool
/// The pid of the process's parent.
public let parentPID: Int32?
/// Initializes a Process.
/// - Parameters:
/// - pid: The pid of the process.
/// - processName: A user-facing name for the process.
/// - appName: A user-facing name for the application, if one exists.
/// - iconURL: An icon representation of the application, if one exists.
/// - path: The path the process exists at.
/// - validSignature: A boolean describing whether or not the process has a valid code signature.
/// - parentPID: The pid of the process's parent.
public init(pid: Int32, processName: String, appName: String?, iconURL: URL?, path: String, validSignature: Bool, parentPID: Int32?) {
self.pid = pid
self.processName = processName
@ -44,6 +66,7 @@ extension SigningRequestProvenance {
self.parentPID = parentPID
}
/// The best user-facing name to display for the process.
public var displayName: String {
appName ?? processName
}

View File

@ -97,7 +97,6 @@ extension SecureEnclave {
}
reloadSecrets()
}
public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> SignedData {
let context: LAContext
if let existing = persistedAuthenticationContexts[secret], existing.valid {
@ -141,6 +140,10 @@ extension SecureEnclave {
return SignedData(data: signature as Data, requiredAuthentication: requiredAuthentication)
}
/// <#Description#>
/// - Parameters:
/// - secret: <#secret description#>
/// - duration: <#duration description#>
public func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) throws {
let newContext = LAContext()
newContext.touchIDAuthenticationAllowableReuseDuration = duration