mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-10-02 20:59:54 +02:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da56dd6434 | ||
|
|
00804587de | ||
|
|
b71cbf8529 | ||
|
|
b003286745 | ||
|
|
f527e136e3 | ||
|
|
ece3865d9a | ||
|
|
6b1f5bbb7c | ||
|
|
f848eb659e |
@@ -139,7 +139,7 @@ var localization: Resource {
|
||||
var swiftSettings: [PackageDescription.SwiftSetting] {
|
||||
[
|
||||
.swiftLanguageMode(.v6),
|
||||
.treatAllWarnings(as: .error),
|
||||
// .treatAllWarnings(as: .error),
|
||||
.strictMemorySafety()
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,13 +34,13 @@ import XPCWrappers
|
||||
) {
|
||||
self.osVersion = osVersion
|
||||
self.currentVersion = currentVersion
|
||||
Task {
|
||||
_ = Task {
|
||||
if checkOnLaunch {
|
||||
try? await checkForUpdates()
|
||||
}
|
||||
while !Task.isCancelled {
|
||||
try? await Task.sleep(for: .seconds(Int(checkFrequency)))
|
||||
try? await checkForUpdates()
|
||||
try await checkForUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public struct OpenSSHPublicKeyWriter: Sendable {
|
||||
("nistp" + String(describing: secret.keyType.size)).lengthAndData +
|
||||
secret.publicKey.lengthAndData
|
||||
case .mldsa:
|
||||
// https://www.ietf.org/archive/id/draft-sfluhrer-ssh-mldsa-04.txt
|
||||
// https://datatracker.ietf.org/doc/html/draft-sfluhrer-ssh-mldsa-05
|
||||
openSSHIdentifier(for: secret.keyType).lengthAndData +
|
||||
secret.publicKey.lengthAndData
|
||||
case .rsa:
|
||||
|
||||
@@ -42,6 +42,18 @@ public final class OpenSSHReader {
|
||||
return convertEndianness ? T(value.bigEndian) : T(value)
|
||||
}
|
||||
|
||||
public func readNextByteAsBool() throws(OpenSSHReaderError) -> Bool {
|
||||
let size = MemoryLayout<Bool>.size
|
||||
guard remaining.count >= size else { throw .beyondBounds }
|
||||
let lengthRange = 0..<size
|
||||
let lengthChunk = remaining[lengthRange]
|
||||
remaining.removeSubrange(lengthRange)
|
||||
if remaining.isEmpty {
|
||||
done = true
|
||||
}
|
||||
return unsafe lengthChunk.bytes.unsafeLoad(as: Bool.self)
|
||||
}
|
||||
|
||||
public func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String {
|
||||
try String(decoding: readNextChunk(convertEndianness: convertEndianness), as: UTF8.self)
|
||||
}
|
||||
@@ -53,5 +65,6 @@ public final class OpenSSHReader {
|
||||
}
|
||||
|
||||
public enum OpenSSHReaderError: Error, Codable {
|
||||
case incorrectFormat
|
||||
case beyondBounds
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public struct OpenSSHSignatureWriter: Sendable {
|
||||
// https://datatracker.ietf.org/doc/html/rfc5656#section-3.1
|
||||
ecdsaSignature(signature, keyType: secret.keyType)
|
||||
case .mldsa:
|
||||
// https://datatracker.ietf.org/doc/html/draft-sfluhrer-ssh-mldsa-00#name-public-key-algorithms
|
||||
// https://datatracker.ietf.org/doc/html/draft-sfluhrer-ssh-mldsa-05
|
||||
mldsaSignature(signature, keyType: secret.keyType)
|
||||
case .rsa:
|
||||
// https://datatracker.ietf.org/doc/html/rfc4253#section-6.6
|
||||
|
||||
@@ -3,6 +3,8 @@ import OSLog
|
||||
import SecretKit
|
||||
import CertificateKit
|
||||
|
||||
import CryptoKit
|
||||
|
||||
public protocol SSHAgentInputParserProtocol {
|
||||
|
||||
func parse(data: Data) async throws -> SSHAgent.Request
|
||||
@@ -53,8 +55,8 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
|
||||
return .unlock
|
||||
case SSHAgent.Request.addSmartcardKeyConstrained.protocolID:
|
||||
return .addSmartcardKeyConstrained
|
||||
case SSHAgent.Request.protocolExtension.protocolID:
|
||||
return .protocolExtension
|
||||
case SSHAgent.Request.protocolExtension(.empty).protocolID:
|
||||
return .protocolExtension(try protocolExtension(from: body))
|
||||
default:
|
||||
return .unknown(rawRequestInt)
|
||||
}
|
||||
@@ -64,12 +66,152 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
|
||||
|
||||
extension SSHAgentInputParser {
|
||||
|
||||
private enum Constants {
|
||||
static let userAuthMagic: UInt8 = 50 // SSH2_MSG_USERAUTH_REQUEST
|
||||
static let sshSigMagic = Data("SSHSIG".utf8)
|
||||
}
|
||||
|
||||
func signatureRequestContext(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext {
|
||||
let reader = OpenSSHReader(data: data)
|
||||
let rawKeyBlob = try reader.readNextChunk()
|
||||
let keyBlob = certificatePublicKeyBlob(from: rawKeyBlob) ?? rawKeyBlob
|
||||
let dataToSign = try reader.readNextChunk()
|
||||
return SSHAgent.Request.SignatureRequestContext(keyBlob: keyBlob, dataToSign: dataToSign)
|
||||
let rawPayload = try reader.readNextChunk()
|
||||
let payload: SSHAgent.Request.SignatureRequestContext.SignaturePayload
|
||||
do {
|
||||
if rawPayload.count > 6 && rawPayload[0..<6] == Constants.sshSigMagic {
|
||||
payload = .init(raw: rawPayload, decoded: .sshSig(try sshSigPayload(from: rawPayload[6...])))
|
||||
} else {
|
||||
payload = .init(raw: rawPayload, decoded: .sshConnection(try sshConnectionPayload(from: rawPayload)))
|
||||
}
|
||||
} catch {
|
||||
payload = .init(raw: rawPayload, decoded: nil)
|
||||
}
|
||||
return SSHAgent.Request.SignatureRequestContext(keyBlob: keyBlob, dataToSign: payload)
|
||||
}
|
||||
|
||||
func sshSigPayload(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext.SignaturePayload.DecodedPayload.SSHSigPayload {
|
||||
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig#L79
|
||||
let payloadReader = OpenSSHReader(data: data)
|
||||
let namespace = try payloadReader.readNextChunkAsString()
|
||||
_ = try payloadReader.readNextChunk() // reserved
|
||||
let hashAlgorithm = try payloadReader.readNextChunkAsString()
|
||||
let hash = try payloadReader.readNextChunk()
|
||||
return .init(
|
||||
namespace: namespace,
|
||||
hashAlgorithm: hashAlgorithm,
|
||||
hash: hash
|
||||
)
|
||||
}
|
||||
|
||||
func sshConnectionPayload(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext.SignaturePayload.DecodedPayload.SSHConnectionPayload {
|
||||
let payloadReader = OpenSSHReader(data: data)
|
||||
_ = try payloadReader.readNextChunk()
|
||||
let magic = try payloadReader.readNextBytes(as: UInt8.self, convertEndianness: false)
|
||||
guard magic == Constants.userAuthMagic else { throw .incorrectFormat }
|
||||
let username = try payloadReader.readNextChunkAsString()
|
||||
_ = try payloadReader.readNextChunkAsString() // "ssh-connection"
|
||||
_ = try payloadReader.readNextChunkAsString() // "publickey-hostbound-v00@openssh.com"
|
||||
let hasSignature = try payloadReader.readNextByteAsBool()
|
||||
let algorithm = try payloadReader.readNextChunkAsString()
|
||||
let publicKeyReader = try payloadReader.readNextChunkAsSubReader()
|
||||
_ = try publicKeyReader.readNextChunk()
|
||||
_ = try publicKeyReader.readNextChunk()
|
||||
let publicKey = try publicKeyReader.readNextChunk()
|
||||
let hostKeyReader = try payloadReader.readNextChunkAsSubReader()
|
||||
_ = try hostKeyReader.readNextChunk()
|
||||
let hostKey = try hostKeyReader.readNextChunk()
|
||||
return .init(
|
||||
username: username,
|
||||
hasSignature: hasSignature,
|
||||
publicKeyAlgorithm: algorithm,
|
||||
publicKey: publicKey,
|
||||
hostKey: hostKey,
|
||||
)
|
||||
}
|
||||
|
||||
func protocolExtension(from data: Data) throws(AgentParsingError) -> SSHAgent.ProtocolExtension {
|
||||
do {
|
||||
let reader = OpenSSHReader(data: data)
|
||||
let nameRaw = try reader.readNextChunkAsString()
|
||||
let nameSplit = nameRaw.split(separator: "@")
|
||||
guard nameSplit.count == 2 else {
|
||||
throw AgentParsingError.invalidData
|
||||
}
|
||||
let (name, domain) = (nameSplit[0], nameSplit[1])
|
||||
switch domain {
|
||||
case SSHAgent.ProtocolExtension.OpenSSHExtension.domain:
|
||||
switch name {
|
||||
case SSHAgent.ProtocolExtension.OpenSSHExtension.sessionBind(.empty).name:
|
||||
let hostkeyBlob = try reader.readNextChunkAsSubReader()
|
||||
let hostKeyType = try hostkeyBlob.readNextChunkAsString()
|
||||
let hostKeyData = try hostkeyBlob.readNextChunk()
|
||||
let sessionID = try reader.readNextChunk()
|
||||
let signatureBlob = try reader.readNextChunkAsSubReader()
|
||||
_ = try signatureBlob.readNextChunk() // key type again
|
||||
let signature = try signatureBlob.readNextChunk()
|
||||
let forwarding = try reader.readNextByteAsBool()
|
||||
switch hostKeyType {
|
||||
// FIXME: FACTOR OUT?
|
||||
case "ssh-ed25519":
|
||||
let hostKey = try CryptoKit.Curve25519.Signing.PublicKey(rawRepresentation: hostKeyData)
|
||||
guard hostKey.isValidSignature(signature, for: sessionID) else {
|
||||
throw AgentParsingError.incorrectSignature
|
||||
}
|
||||
case "ecdsa-sha2-nistp256":
|
||||
let hostKey = try CryptoKit.P256.Signing.PublicKey(rawRepresentation: hostKeyData)
|
||||
guard hostKey.isValidSignature(try .init(rawRepresentation: signature), for: sessionID) else {
|
||||
throw AgentParsingError.incorrectSignature
|
||||
}
|
||||
case "ecdsa-sha2-nistp384":
|
||||
let hostKey = try CryptoKit.P384.Signing.PublicKey(rawRepresentation: hostKeyData)
|
||||
guard hostKey.isValidSignature(try .init(rawRepresentation: signature), for: sessionID) else {
|
||||
throw AgentParsingError.incorrectSignature
|
||||
}
|
||||
case "ssh-mldsa-65":
|
||||
if #available(macOS 26.0, *) {
|
||||
let hostKey = try CryptoKit.MLDSA65.PublicKey(rawRepresentation: hostKeyData)
|
||||
guard hostKey.isValidSignature(signature, for: sessionID) else {
|
||||
throw AgentParsingError.incorrectSignature
|
||||
}
|
||||
} else {
|
||||
throw AgentParsingError.unhandledRequest
|
||||
}
|
||||
case "ssh-mldsa-87":
|
||||
if #available(macOS 26.0, *) {
|
||||
let hostKey = try CryptoKit.MLDSA65.PublicKey(rawRepresentation: hostKeyData)
|
||||
guard hostKey.isValidSignature(signature, for: sessionID) else {
|
||||
throw AgentParsingError.incorrectSignature
|
||||
}
|
||||
} else {
|
||||
throw AgentParsingError.unhandledRequest
|
||||
}
|
||||
case "ssh-rsa":
|
||||
// FIXME: HANDLE
|
||||
throw AgentParsingError.unhandledRequest
|
||||
default:
|
||||
throw AgentParsingError.unhandledRequest
|
||||
}
|
||||
let context = SSHAgent.ProtocolExtension.OpenSSHExtension.SessionBindContext(
|
||||
hostKey: hostKeyData,
|
||||
sessionID: sessionID,
|
||||
signature: signature,
|
||||
forwarding: forwarding
|
||||
)
|
||||
return .openSSH(.sessionBind(context))
|
||||
default:
|
||||
return .openSSH(.unknown(String(name)))
|
||||
}
|
||||
default:
|
||||
return .unknown(nameRaw)
|
||||
}
|
||||
|
||||
} catch let error as OpenSSHReaderError {
|
||||
throw .openSSHReader(error)
|
||||
} catch let error as AgentParsingError {
|
||||
throw error
|
||||
} catch {
|
||||
throw .unknownRequest
|
||||
}
|
||||
}
|
||||
|
||||
func certificatePublicKeyBlob(from hash: Data) -> Data? {
|
||||
@@ -99,6 +241,7 @@ extension SSHAgentInputParser {
|
||||
case unknownRequest
|
||||
case unhandledRequest
|
||||
case invalidData
|
||||
case incorrectSignature
|
||||
case openSSHReader(OpenSSHReaderError)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ extension SSHAgent {
|
||||
case lock
|
||||
case unlock
|
||||
case addSmartcardKeyConstrained
|
||||
case protocolExtension
|
||||
case protocolExtension(ProtocolExtension)
|
||||
case unknown(UInt8)
|
||||
|
||||
public var protocolID: UInt8 {
|
||||
@@ -60,18 +60,82 @@ extension SSHAgent {
|
||||
|
||||
public struct SignatureRequestContext: Sendable, Codable {
|
||||
public let keyBlob: Data
|
||||
public let dataToSign: Data
|
||||
public let dataToSign: SignaturePayload
|
||||
|
||||
public init(keyBlob: Data, dataToSign: Data) {
|
||||
public init(keyBlob: Data, dataToSign: SignaturePayload) {
|
||||
self.keyBlob = keyBlob
|
||||
self.dataToSign = dataToSign
|
||||
}
|
||||
|
||||
public static var empty: SignatureRequestContext {
|
||||
SignatureRequestContext(keyBlob: Data(), dataToSign: Data())
|
||||
SignatureRequestContext(keyBlob: Data(), dataToSign: SignaturePayload(raw: Data(), decoded: nil))
|
||||
}
|
||||
|
||||
public struct SignaturePayload: Sendable, Codable {
|
||||
|
||||
public let raw: Data
|
||||
public let decoded: DecodedPayload?
|
||||
|
||||
public init(
|
||||
raw: Data,
|
||||
decoded: DecodedPayload?
|
||||
) {
|
||||
self.raw = raw
|
||||
self.decoded = decoded
|
||||
}
|
||||
|
||||
public enum DecodedPayload: Sendable, Codable {
|
||||
case sshConnection(SSHConnectionPayload)
|
||||
case sshSig(SSHSigPayload)
|
||||
|
||||
public struct SSHConnectionPayload: Sendable, Codable {
|
||||
|
||||
public let username: String
|
||||
public let hasSignature: Bool
|
||||
public let publicKeyAlgorithm: String
|
||||
public let publicKey: Data
|
||||
public let hostKey: Data
|
||||
|
||||
public init(
|
||||
username: String,
|
||||
hasSignature: Bool,
|
||||
publicKeyAlgorithm: String,
|
||||
publicKey: Data,
|
||||
hostKey: Data
|
||||
) {
|
||||
self.username = username
|
||||
self.hasSignature = hasSignature
|
||||
self.publicKeyAlgorithm = publicKeyAlgorithm
|
||||
self.publicKey = publicKey
|
||||
self.hostKey = hostKey
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct SSHSigPayload: Sendable, Codable {
|
||||
|
||||
public let namespace: String
|
||||
public let hashAlgorithm: String
|
||||
public let hash: Data
|
||||
|
||||
public init(
|
||||
namespace: String,
|
||||
hashAlgorithm: String,
|
||||
hash: Data,
|
||||
) {
|
||||
self.namespace = namespace
|
||||
self.hashAlgorithm = hashAlgorithm
|
||||
self.hash = hash
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// The type of the SSH Agent Response, as described in https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-5.1
|
||||
@@ -88,8 +152,8 @@ extension SSHAgent {
|
||||
switch self {
|
||||
case .agentFailure: "SSH_AGENT_FAILURE"
|
||||
case .agentSuccess: "SSH_AGENT_SUCCESS"
|
||||
case .agentIdentitiesAnswer: "SSH_AGENT_IDENTITIES_ANSWER"
|
||||
case .agentSignResponse: "SSH_AGENT_SIGN_RESPONSE"
|
||||
case .agentIdentitiesAnswer: "SSH2_AGENT_IDENTITIES_ANSWER"
|
||||
case .agentSignResponse: "SSH2_AGENT_SIGN_RESPONSE"
|
||||
case .agentExtensionFailure: "SSH_AGENT_EXTENSION_FAILURE"
|
||||
case .agentExtensionResponse: "SSH_AGENT_EXTENSION_RESPONSE"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import Foundation
|
||||
|
||||
// Extensions, as defined in https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.agent
|
||||
|
||||
extension SSHAgent {
|
||||
|
||||
public enum ProtocolExtension: CustomDebugStringConvertible, Codable, Sendable {
|
||||
case openSSH(OpenSSHExtension)
|
||||
case unknown(String)
|
||||
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
case let .openSSH(protocolExtension):
|
||||
protocolExtension.debugDescription
|
||||
case .unknown(let string):
|
||||
"Unknown (\(string))"
|
||||
}
|
||||
}
|
||||
|
||||
public static var empty: ProtocolExtension {
|
||||
.unknown("empty")
|
||||
}
|
||||
|
||||
private struct ProtocolExtensionParsingError: Error {}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SSHAgent.ProtocolExtension {
|
||||
|
||||
public enum OpenSSHExtension: CustomDebugStringConvertible, Codable, Sendable {
|
||||
case sessionBind(SessionBindContext)
|
||||
case unknown(String)
|
||||
|
||||
public static var domain: String {
|
||||
"openssh.com"
|
||||
}
|
||||
|
||||
public var name: String {
|
||||
switch self {
|
||||
case .sessionBind:
|
||||
"session-bind"
|
||||
case .unknown(let name):
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
public var debugDescription: String {
|
||||
"\(name)@\(OpenSSHExtension.domain)"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SSHAgent.ProtocolExtension.OpenSSHExtension {
|
||||
|
||||
public struct SessionBindContext: Codable, Sendable {
|
||||
|
||||
public let hostKey: Data
|
||||
public let sessionID: Data
|
||||
public let signature: Data
|
||||
public let forwarding: Bool
|
||||
|
||||
public init(hostKey: Data, sessionID: Data, signature: Data, forwarding: Bool) {
|
||||
self.hostKey = hostKey
|
||||
self.sessionID = sessionID
|
||||
self.signature = signature
|
||||
self.forwarding = forwarding
|
||||
}
|
||||
|
||||
public static let empty = SessionBindContext(hostKey: Data(), sessionID: Data(), signature: Data(), forwarding: false)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,8 @@ public final class Agent: Sendable {
|
||||
private let signatureWriter = OpenSSHSignatureWriter()
|
||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "Agent")
|
||||
|
||||
@MainActor private var sessionID: SSHAgent.ProtocolExtension.OpenSSHExtension.SessionBindContext?
|
||||
|
||||
/// Initializes an agent with a store list and a witness.
|
||||
/// - Parameters:
|
||||
/// - storeList: The `SecretStoreList` to make available.
|
||||
@@ -31,7 +33,12 @@ public final class Agent: Sendable {
|
||||
|
||||
extension Agent {
|
||||
|
||||
public func handle(request: SSHAgent.Request, provenance: SigningRequestProvenance) async -> Data {
|
||||
public func handle(
|
||||
request: SSHAgent.Request,
|
||||
provenance: SigningRequestProvenance,
|
||||
hosts: [Data: String]?
|
||||
) async -> Data {
|
||||
logger.debug("Agent received request of type \(request.debugDescription)")
|
||||
// Depending on the launch context (such as after macOS update), the agent may need to reload secrets before acting
|
||||
await reloadSecretsIfNeccessary()
|
||||
var response = Data()
|
||||
@@ -42,9 +49,51 @@ extension Agent {
|
||||
response.append(await identities())
|
||||
logger.debug("Agent returned \(SSHAgent.Response.agentIdentitiesAnswer.debugDescription)")
|
||||
case .signRequest(let context):
|
||||
let target: SigningRequestTarget?
|
||||
switch context.dataToSign.decoded {
|
||||
case .sshConnection(let payload):
|
||||
target = .connection(
|
||||
.init(
|
||||
username: payload.username,
|
||||
hasSignature: payload.hasSignature,
|
||||
publicKeyAlgorithm: payload.publicKeyAlgorithm,
|
||||
publicKey: payload.publicKey,
|
||||
hostKey: payload.hostKey,
|
||||
host: hosts?[payload.hostKey]
|
||||
)
|
||||
)
|
||||
if let boundSession = await sessionID {
|
||||
guard payload.hostKey == boundSession.hostKey else {
|
||||
logger.error("Agent received bind request, but host key does not match signature request host key.")
|
||||
throw BindingFailure()
|
||||
}
|
||||
}
|
||||
case .sshSig(let payload):
|
||||
target = .signature(
|
||||
.init(
|
||||
namespace: payload.namespace,
|
||||
hashAlgorithm: payload.hashAlgorithm,
|
||||
hash: payload.hash
|
||||
)
|
||||
)
|
||||
default:
|
||||
target = nil
|
||||
}
|
||||
response.append(SSHAgent.Response.agentSignResponse.data)
|
||||
response.append(try await sign(data: context.dataToSign, keyBlob: context.keyBlob, provenance: provenance))
|
||||
response.append(try await sign(data: context.dataToSign.raw, keyBlob: context.keyBlob, provenance: provenance, target: target))
|
||||
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
|
||||
case .protocolExtension(.openSSH(.sessionBind(let bind))):
|
||||
response = try await MainActor.run {
|
||||
guard sessionID == nil else {
|
||||
logger.error("Agent received bind request, but already bound.")
|
||||
// FIXME: This will break forwarding for now.
|
||||
throw BindingFailure()
|
||||
}
|
||||
logger.debug("Agent bound")
|
||||
sessionID = bind
|
||||
return SSHAgent.Response.agentSuccess.data
|
||||
}
|
||||
logger.debug("Agent returned \(SSHAgent.Response.agentSuccess.debugDescription)")
|
||||
case .unknown(let value):
|
||||
logger.error("Agent received unknown request of type \(value).")
|
||||
throw UnhandledRequestError()
|
||||
@@ -92,19 +141,21 @@ extension Agent {
|
||||
/// - data: The data to sign.
|
||||
/// - provenance: A ``SecretKit.SigningRequestProvenance`` object describing the origin of the request.
|
||||
/// - Returns: An OpenSSH formatted Data payload containing the signed data response.
|
||||
func sign(data: Data, keyBlob: Data, provenance: SigningRequestProvenance) async throws -> Data {
|
||||
func sign(data: Data, keyBlob: Data, provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws -> Data {
|
||||
guard let (secret, store) = await secret(matching: keyBlob) else {
|
||||
let keyBlobHex = keyBlob.formatted(.hex())
|
||||
logger.debug("Agent did not have a key matching \(keyBlobHex)")
|
||||
throw NoMatchingKeyError()
|
||||
}
|
||||
|
||||
try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance)
|
||||
try evaluateRestrictions(secret.attributes.restrictions, from: provenance, for: target)
|
||||
|
||||
let rawRepresentation = try await store.sign(data: data, with: secret, for: provenance)
|
||||
try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance, target: target)
|
||||
|
||||
let rawRepresentation = try await store.sign(data: data, with: secret, for: provenance, target: target)
|
||||
let signedData = signatureWriter.data(secret: secret, signature: rawRepresentation)
|
||||
|
||||
try await witness?.witness(accessTo: secret, from: store, by: provenance)
|
||||
try await witness?.witness(accessTo: secret, from: store, by: provenance, target: target)
|
||||
|
||||
logger.debug("Agent signed request")
|
||||
|
||||
@@ -113,6 +164,22 @@ extension Agent {
|
||||
|
||||
}
|
||||
|
||||
extension Agent {
|
||||
|
||||
func evaluateRestrictions(_ restrictions: Restrictions?, from provenance: SigningRequestProvenance, for target: SigningRequestTarget?) throws(RestrictionError) {
|
||||
guard let restrictions else { return }
|
||||
print(restrictions)
|
||||
throw .connectionsNotPermitted
|
||||
}
|
||||
|
||||
enum RestrictionError: Error {
|
||||
case signingNotPermitted
|
||||
case connectionsNotPermitted
|
||||
case hostNotAllowed(String)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Agent {
|
||||
|
||||
/// Gives any store with no loaded secrets a chance to reload.
|
||||
@@ -142,6 +209,7 @@ extension Agent {
|
||||
|
||||
struct NoMatchingKeyError: Error {}
|
||||
struct UnhandledRequestError: Error {}
|
||||
struct BindingFailure: Error {}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import SecretKit
|
||||
import SSHProtocolKit
|
||||
|
||||
/// A protocol that allows conformers to be notified of access to secrets, and optionally prevent access.
|
||||
public protocol SigningWitness: Sendable {
|
||||
@@ -10,13 +11,13 @@ public protocol SigningWitness: Sendable {
|
||||
/// - store: The `Store` being asked to sign the request..
|
||||
/// - provenance: A `SigningRequestProvenance` object describing the origin of the request.
|
||||
/// - Note: This method being called does not imply that the requst has been authorized. If a secret requires authentication, authentication will still need to be performed by the user before the request will be performed. If the user declines or fails to authenticate, the request will fail.
|
||||
func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async throws
|
||||
func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws
|
||||
|
||||
/// Notifies the callee that a signing operation has been performed for a given secret.
|
||||
/// - Parameters:
|
||||
/// - secret: The `Secret` that will was used to sign the request.
|
||||
/// - store: The `Store` that signed the request..
|
||||
/// - provenance: A `SigningRequestProvenance` object describing the origin of the request.
|
||||
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async throws
|
||||
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Foundation
|
||||
import OSLog
|
||||
import SecretKit
|
||||
import launch
|
||||
|
||||
/// A controller that manages socket configuration and request dispatching.
|
||||
public struct SocketController {
|
||||
@@ -13,8 +12,7 @@ public struct SocketController {
|
||||
private let sessionsContinuation: AsyncStream<Session>.Continuation
|
||||
|
||||
/// The active SocketPort. Must be retained to be kept valid.
|
||||
/// Only applicable for legacy non-launchd sockets.
|
||||
private let port: SocketPort?
|
||||
private let port: SocketPort
|
||||
|
||||
/// The FileHandle for the main socket.
|
||||
private let fileHandle: FileHandle
|
||||
@@ -25,15 +23,10 @@ public struct SocketController {
|
||||
/// Tracer which determines who originates a socket connection.
|
||||
private let requestTracer = SigningRequestTracer()
|
||||
|
||||
public enum Socket {
|
||||
case launchd(String)
|
||||
case path(String)
|
||||
}
|
||||
|
||||
public init(_ socket: Socket) {
|
||||
/// Initializes a socket controller with a specified path.
|
||||
/// - Parameter path: The path to use as a socket.
|
||||
public init(path: String) {
|
||||
(sessions, sessionsContinuation) = AsyncStream<Session>.makeStream()
|
||||
switch socket {
|
||||
case .path(let path):
|
||||
logger.debug("Socket controller setting up at \(path)")
|
||||
if let _ = try? FileManager.default.removeItem(atPath: path) {
|
||||
logger.debug("Socket controller removed existing socket")
|
||||
@@ -41,25 +34,8 @@ public struct SocketController {
|
||||
let exists = FileManager.default.fileExists(atPath: path)
|
||||
assert(!exists)
|
||||
logger.debug("Socket controller path is clear")
|
||||
let port = SocketPort(path: path)
|
||||
port = SocketPort(path: path)
|
||||
fileHandle = FileHandle(fileDescriptor: port.socket, closeOnDealloc: true)
|
||||
self.port = port
|
||||
logger.debug("Socket listening at \(path)")
|
||||
case .launchd(let name):
|
||||
logger.debug("Socket controller setting for launchd-controlled socket \(name)")
|
||||
port = nil
|
||||
var fileDescriptors: UnsafeMutablePointer<Int32>? = nil
|
||||
var count = 0
|
||||
let result = unsafe launch_activate_socket(name, &fileDescriptors, &count)
|
||||
guard result == kOSReturnSuccess, let socket = unsafe fileDescriptors?.pointee else {
|
||||
fatalError()
|
||||
}
|
||||
fileHandle = FileHandle(fileDescriptor: socket, closeOnDealloc: true)
|
||||
}
|
||||
listen()
|
||||
}
|
||||
|
||||
func listen() {
|
||||
Task { @MainActor [fileHandle, sessionsContinuation, logger] in
|
||||
// Create the sequence before triggering the notification to
|
||||
// ensure it will not be missed.
|
||||
@@ -75,7 +51,7 @@ public struct SocketController {
|
||||
fileHandle.acceptConnectionInBackgroundAndNotify()
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Socket listening at \(path)")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -149,7 +125,14 @@ private extension SocketPort {
|
||||
|
||||
convenience init(path: String) {
|
||||
var addr = sockaddr_un()
|
||||
let length = addr.setPath(path)
|
||||
|
||||
let length = withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in
|
||||
path.withCString { cstring in
|
||||
let len = unsafe strlen(cstring)
|
||||
unsafe strncpy(pointer, cstring, len)
|
||||
return len
|
||||
}
|
||||
}
|
||||
// This doesn't seem to be _strictly_ neccessary with SocketPort.
|
||||
// but just for good form.
|
||||
addr.sun_family = sa_family_t(AF_UNIX)
|
||||
@@ -161,31 +144,3 @@ private extension SocketPort {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension sockaddr_un {
|
||||
|
||||
mutating func setPath(_ path: String) -> Int {
|
||||
#if compiler(<6.4)
|
||||
unsafe withUnsafeMutablePointer(to: &self.sun_path.0) { pointer in
|
||||
unsafe path.withCString { cstring in
|
||||
let len = unsafe strlen(cstring)
|
||||
unsafe strncpy(pointer, cstring, len)
|
||||
return len
|
||||
}
|
||||
}
|
||||
#else
|
||||
withUnsafeMutablePointer(to: &self.sun_path.0) { pointer in
|
||||
path.withCString { cstring in
|
||||
let len = unsafe strlen(cstring)
|
||||
unsafe strncpy(pointer, cstring, len)
|
||||
return len
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Changes the header from `UnsafeMutablePointer<UnsafeMutablePointer<Int32>?>?` -> `UnsafeMutablePointer<UnsafeMutablePointer<Int32>?>!`
|
||||
@_silgen_name("launch_activate_socket")
|
||||
func launch_activate_socket(_ name: UnsafePointer<CChar>, _ fds: UnsafeMutablePointer<UnsafeMutablePointer<Int32>?>!, _ cnt: UnsafeMutablePointer<Int>!) -> Int32
|
||||
|
||||
@@ -8,7 +8,7 @@ open class AnySecretStore: SecretStore, @unchecked Sendable {
|
||||
private let _id: @Sendable () -> UUID
|
||||
private let _name: @MainActor @Sendable () -> String
|
||||
private let _secrets: @MainActor @Sendable () -> [AnySecret]
|
||||
private let _sign: @Sendable (Data, AnySecret, SigningRequestProvenance) async throws -> Data
|
||||
private let _sign: @Sendable (Data, AnySecret, SigningRequestProvenance, SigningRequestTarget?) async throws -> Data
|
||||
private let _existingPersistedAuthenticationContext: @Sendable (AnySecret) async -> PersistedAuthenticationContext?
|
||||
private let _persistAuthentication: @Sendable (AnySecret, TimeInterval) async throws -> Void
|
||||
private let _reloadSecrets: @Sendable () async -> Void
|
||||
@@ -19,7 +19,7 @@ open class AnySecretStore: SecretStore, @unchecked Sendable {
|
||||
_name = { secretStore.name }
|
||||
_id = { secretStore.id }
|
||||
_secrets = { secretStore.secrets.map { AnySecret($0) } }
|
||||
_sign = { try await secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType, for: $2) }
|
||||
_sign = { try await secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType, for: $2, target: $3) }
|
||||
_existingPersistedAuthenticationContext = { await secretStore.existingPersistedAuthenticationContext(secret: $0.base as! SecretStoreType.SecretType) }
|
||||
_persistAuthentication = { try await secretStore.persistAuthentication(secret: $0.base as! SecretStoreType.SecretType, forDuration: $1) }
|
||||
_reloadSecrets = { await secretStore.reloadSecrets() }
|
||||
@@ -41,8 +41,8 @@ open class AnySecretStore: SecretStore, @unchecked Sendable {
|
||||
return _secrets()
|
||||
}
|
||||
|
||||
public func sign(data: Data, with secret: AnySecret, for provenance: SigningRequestProvenance) async throws -> Data {
|
||||
try await _sign(data, secret, provenance)
|
||||
public func sign(data: Data, with secret: AnySecret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws -> Data {
|
||||
try await _sign(data, secret, provenance, target)
|
||||
}
|
||||
|
||||
public func existingPersistedAuthenticationContext(secret: AnySecret) async -> PersistedAuthenticationContext? {
|
||||
|
||||
@@ -8,6 +8,9 @@ public struct Attributes: Sendable, Codable, Hashable {
|
||||
/// The authentication requirements for the key. This is simply a description of the option recorded at creation – modifying it doers not modify the key's authentication requirements.
|
||||
public let authentication: AuthenticationRequirement
|
||||
|
||||
/// The authentication restrictions for the key.
|
||||
public var restrictions: Restrictions?
|
||||
|
||||
/// The string appended to the end of the SSH Public Key.
|
||||
/// If nil, a default value will be used.
|
||||
public var publicKeyAttribution: String?
|
||||
@@ -15,10 +18,12 @@ public struct Attributes: Sendable, Codable, Hashable {
|
||||
public init(
|
||||
keyType: KeyType,
|
||||
authentication: AuthenticationRequirement,
|
||||
restrictions: Restrictions?,
|
||||
publicKeyAttribution: String? = nil
|
||||
) {
|
||||
self.keyType = keyType
|
||||
self.authentication = authentication
|
||||
self.restrictions = restrictions
|
||||
self.publicKeyAttribution = publicKeyAttribution
|
||||
}
|
||||
|
||||
@@ -53,3 +58,46 @@ public enum AuthenticationRequirement: String, Hashable, Sendable, Codable, Iden
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The restrictions for the key.
|
||||
public struct Restrictions: Hashable, Sendable, Codable, Identifiable {
|
||||
|
||||
public enum AllowedDomains: Hashable, Sendable, Codable {
|
||||
case all
|
||||
case specific([String])
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case .all:
|
||||
break
|
||||
case .specific(let values):
|
||||
values.hash(into: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum AllowedProvenancePaths: Hashable, Sendable, Codable, Identifiable {
|
||||
case all
|
||||
case specific([String])
|
||||
|
||||
public var id: Int {
|
||||
hashValue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public var allowForwarding: Bool
|
||||
public var allowSigning: Bool
|
||||
public var allowConnections: Bool
|
||||
public var allowedDomains: AllowedDomains
|
||||
public var allowedProvenancePaths: AllowedProvenancePaths
|
||||
|
||||
public var id: Restrictions {
|
||||
self
|
||||
}
|
||||
|
||||
// public static let `default` = Restrictions(allowForwarding: true, allowSigning: true, allowConnections: true, allowedDomains: .all, allowedProvenancePaths: .all)
|
||||
public static let `default` = Restrictions(allowForwarding: true, allowSigning: true, allowConnections: true, allowedDomains: .specific(["example.com"]), allowedProvenancePaths: .all)
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public protocol SecretStore<SecretType>: Identifiable, Sendable {
|
||||
/// - secret: The ``Secret`` to sign with.
|
||||
/// - provenance: A ``SigningRequestProvenance`` describing where the request came from.
|
||||
/// - Returns: The signed data.
|
||||
func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) async throws -> Data
|
||||
func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws -> Data
|
||||
|
||||
/// Checks to see if there is currently a valid persisted authentication for a given secret.
|
||||
/// - Parameters:
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
/// Describes the target of the signature operation.
|
||||
public enum SigningRequestTarget: Sendable {
|
||||
|
||||
case connection(ConnectionPayload)
|
||||
case signature(SignaturePayload)
|
||||
|
||||
public struct ConnectionPayload: Sendable, Codable{
|
||||
|
||||
public let username: String
|
||||
public let hasSignature: Bool
|
||||
public let publicKeyAlgorithm: String
|
||||
public let publicKey: Data
|
||||
public let hostKey: Data
|
||||
public let host: String?
|
||||
|
||||
public init(
|
||||
username: String,
|
||||
hasSignature: Bool,
|
||||
publicKeyAlgorithm: String,
|
||||
publicKey: Data,
|
||||
hostKey: Data,
|
||||
host: String?
|
||||
) {
|
||||
self.username = username
|
||||
self.hasSignature = hasSignature
|
||||
self.publicKeyAlgorithm = publicKeyAlgorithm
|
||||
self.publicKey = publicKey
|
||||
self.hostKey = hostKey
|
||||
self.host = host
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct SignaturePayload: Sendable, Codable {
|
||||
|
||||
public let namespace: String
|
||||
public let hashAlgorithm: String
|
||||
public let hash: Data
|
||||
|
||||
public init(
|
||||
namespace: String,
|
||||
hashAlgorithm: String,
|
||||
hash: Data,
|
||||
) {
|
||||
self.namespace = namespace
|
||||
self.hashAlgorithm = hashAlgorithm
|
||||
self.hash = hash
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,7 +47,7 @@ extension SecureEnclave {
|
||||
.contains("DeviceOwnerAuthentication") ? .presenceRequired : .unknown
|
||||
do {
|
||||
let parsed = try CryptoKit.SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: tokenObjectID)
|
||||
let secret = Secret(id: UUID().uuidString, name: name, publicKey: parsed.publicKey.x963Representation, attributes: Attributes(keyType: .init(algorithm: .ecdsa, size: 256), authentication: auth))
|
||||
let secret = Secret(id: UUID().uuidString, name: name, publicKey: parsed.publicKey.x963Representation, attributes: Attributes(keyType: .init(algorithm: .ecdsa, size: 256), authentication: auth, restrictions: .default))
|
||||
guard !migratedPublicKeys.contains(parsed.publicKey.x963Representation) else {
|
||||
logger.log("Skipping \(name), public key already present. Marking as migrated.")
|
||||
markMigrated(secret: secret, oldID: id)
|
||||
|
||||
@@ -37,13 +37,20 @@ extension SecureEnclave {
|
||||
|
||||
// MARK: SecretStore
|
||||
|
||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
|
||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws -> Data {
|
||||
var context: LAContext
|
||||
if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) {
|
||||
context = unsafe existing.context
|
||||
} else {
|
||||
let newContext = LAContext()
|
||||
switch target {
|
||||
case .connection(let payload) where payload.host != nil:
|
||||
newContext.localizedReason = String(localized: .authContextRequestSignatureForConnectionDescription(appName: provenance.origin.displayName, targetName: payload.host!, secretName: secret.name))
|
||||
case .signature(let payload):
|
||||
newContext.localizedReason = String(localized: .authContextRequestSignatureForSignatureDescription(namespace: payload.namespace, appName: provenance.origin.displayName, secretName: secret.name))
|
||||
default:
|
||||
newContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
|
||||
}
|
||||
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
||||
context = newContext
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ extension SmartCard {
|
||||
public let name: String
|
||||
public let publicKey: Data
|
||||
public var attributes: Attributes
|
||||
public var restrictions: Restrictions?
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ extension SmartCard {
|
||||
|
||||
// MARK: Public API
|
||||
|
||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
|
||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws -> Data {
|
||||
guard let tokenID = await state.tokenID else { fatalError() }
|
||||
var context: LAContext
|
||||
if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) {
|
||||
@@ -171,7 +171,7 @@ extension SmartCard.Store {
|
||||
let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)!
|
||||
let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any]
|
||||
let publicKey = publicKeyAttributes[kSecValueData] as! Data
|
||||
let attributes = Attributes(keyType: KeyType(secAttr: algorithmSecAttr, size: keySize)!, authentication: .presenceRequired)
|
||||
let attributes = Attributes(keyType: KeyType(secAttr: algorithmSecAttr, size: keySize)!, authentication: .presenceRequired, restrictions: .default)
|
||||
let secret = SmartCard.Secret(id: tokenID, name: name, publicKey: publicKey, attributes: attributes)
|
||||
guard signatureAlgorithm(for: secret) != nil else { return nil }
|
||||
return secret
|
||||
|
||||
@@ -49,7 +49,7 @@ extension Stub {
|
||||
print("Public Key OpenSSH: \(OpenSSHPublicKeyWriter().openSSHString(secret: secret))")
|
||||
}
|
||||
|
||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) throws -> Data {
|
||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) throws -> Data {
|
||||
guard !shouldThrow else {
|
||||
throw NSError(domain: "test", code: 0, userInfo: nil)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import SwiftUI
|
||||
|
||||
extension EnvironmentValues {
|
||||
|
||||
@MainActor fileprivate static let _certificateStore: CertificateStore = CertificateStore()
|
||||
@MainActor fileprivate static let _certificateStore = CertificateStore()
|
||||
|
||||
@MainActor var certificateStore: CertificateStore {
|
||||
EnvironmentValues._certificateStore
|
||||
@@ -41,26 +41,29 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
@MainActor private lazy var agent: Agent = {
|
||||
Agent(storeList: storeList, certificateStore: EnvironmentValues._certificateStore, witness: notifier)
|
||||
}()
|
||||
private var shutdownTask: Task<Void, Error>?
|
||||
private let socketController = SocketController(.launchd("SecureListener"))
|
||||
private lazy var socketController: SocketController = {
|
||||
let path = URL.socketPath as String
|
||||
return SocketController(path: path)
|
||||
}()
|
||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "AppDelegate")
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
logger.debug("SecretAgent finished launching")
|
||||
Task {
|
||||
_ = Task {
|
||||
for await session in socketController.sessions {
|
||||
Task {
|
||||
do {
|
||||
_ = Task {
|
||||
let inputParser = try await XPCAgentInputParser()
|
||||
let hostsReader = try await XPCHostsfileReader()
|
||||
let hosts = try? await hostsReader.read()
|
||||
do {
|
||||
for await message in session.messages {
|
||||
let request = try await inputParser.parse(data: message)
|
||||
let agentResponse = await agent.handle(request: request, provenance: session.provenance)
|
||||
let agentResponse = await agent.handle(request: request, provenance: session.provenance, hosts: hosts)
|
||||
try session.write(agentResponse)
|
||||
}
|
||||
} catch {
|
||||
try? session.close()
|
||||
try session.close()
|
||||
}
|
||||
startCountdownClock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,16 +92,5 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func startCountdownClock() {
|
||||
// FIXME: ACCOUNT FOR STORED AUTH
|
||||
logger.log("Beginning countdown clock")
|
||||
shutdownTask?.cancel()
|
||||
shutdownTask = Task { [logger] in
|
||||
try await Task.sleep(for: .seconds(30))
|
||||
logger.log("Shutting down")
|
||||
await NSApplication.shared.terminate(nil)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -60,12 +60,19 @@ final class Notifier: Sendable {
|
||||
notificationCenter.requestAuthorization(options: .alert) { _, _ in }
|
||||
}
|
||||
|
||||
func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async {
|
||||
func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?) async {
|
||||
await notificationDelegate.state.setPending(secret: secret, store: store)
|
||||
let notificationCenter = UNUserNotificationCenter.current()
|
||||
let notificationContent = UNMutableNotificationContent()
|
||||
notificationContent.title = String(localized: .signedNotificationTitle(appName: provenance.origin.displayName))
|
||||
switch target {
|
||||
case .connection(let payload) where payload.host != nil:
|
||||
notificationContent.subtitle = String(localized: .signedConnectionNotificationDescription(secretName: secret.name, username: payload.username, host: payload.host!))
|
||||
case .signature(let payload):
|
||||
notificationContent.subtitle = String(localized: .signedSignatureNotificationDescription(secretName: secret.name, namespace: payload.namespace))
|
||||
default:
|
||||
notificationContent.subtitle = String(localized: .signedNotificationDescription(secretName: secret.name))
|
||||
}
|
||||
notificationContent.userInfo[Constants.persistSecretIDKey] = secret.id.description
|
||||
notificationContent.userInfo[Constants.persistStoreIDKey] = store.id.description
|
||||
notificationContent.interruptionLevel = .timeSensitive
|
||||
@@ -100,11 +107,21 @@ final class Notifier: Sendable {
|
||||
|
||||
extension Notifier: SigningWitness {
|
||||
|
||||
func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async throws {
|
||||
func speakNowOrForeverHoldYourPeace(
|
||||
forAccessTo secret: AnySecret,
|
||||
from store: AnySecretStore,
|
||||
by provenance: SigningRequestProvenance,
|
||||
target: SigningRequestTarget?
|
||||
) async throws {
|
||||
}
|
||||
|
||||
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async throws {
|
||||
await notify(accessTo: secret, from: store, by: provenance)
|
||||
func witness(
|
||||
accessTo secret: AnySecret,
|
||||
from store: AnySecretStore,
|
||||
by provenance: SigningRequestProvenance,
|
||||
target: SigningRequestTarget?
|
||||
) async throws {
|
||||
await notify(accessTo: secret, from: store, by: provenance, target: target)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
<string>1</string>
|
||||
<key>com.apple.security.hardened-process.hardened-heap</key>
|
||||
<true/>
|
||||
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
|
||||
<string>2</string>
|
||||
<key>com.apple.security.smartcard</key>
|
||||
<true/>
|
||||
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
|
||||
<string>2</string>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import Foundation
|
||||
import OSLog
|
||||
import XPCWrappers
|
||||
import OSLog
|
||||
|
||||
public final class XPCHostsfileReader {
|
||||
|
||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "XPCHostsfileReader")
|
||||
private let session: XPCTypedSession<[Data: String], HostsfileReaderError>
|
||||
|
||||
public init() async throws {
|
||||
logger.debug("Creating XPCHostsfileReader")
|
||||
session = try await XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretAgentHostsfileReader", warmup: true)
|
||||
logger.debug("XPCHostsfileReader is warmed up.")
|
||||
}
|
||||
|
||||
public func read() async throws -> [Data: String] {
|
||||
logger.debug("Reading hosts file")
|
||||
defer { logger.debug("Read hosts file") }
|
||||
return try await session.send(Data())
|
||||
}
|
||||
|
||||
deinit {
|
||||
session.complete()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension XPCHostsfileReader {
|
||||
|
||||
public enum HostsfileReaderError: Error, Codable {
|
||||
case fileDoesNotExist
|
||||
case parseError
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>XPCService</key>
|
||||
<dict>
|
||||
<key>ServiceType</key>
|
||||
<string>Application</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.hardened-process</key>
|
||||
<true/>
|
||||
<key>com.apple.security.hardened-process.checked-allocations</key>
|
||||
<true/>
|
||||
<key>com.apple.security.hardened-process.dyld-ro</key>
|
||||
<true/>
|
||||
<key>com.apple.security.hardened-process.enhanced-security-version-string</key>
|
||||
<string>2</string>
|
||||
<key>com.apple.security.hardened-process.hardened-heap</key>
|
||||
<true/>
|
||||
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
|
||||
<string>2</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,26 @@
|
||||
import Foundation
|
||||
import OSLog
|
||||
import XPCWrappers
|
||||
import SSHProtocolKit
|
||||
|
||||
final class SecretAgentHostsfileReader: NSObject, XPCProtocol {
|
||||
|
||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.SecretAgentHostsfileReader", category: "SecretAgentHostsfileReader")
|
||||
|
||||
func process(_ data: Data) async throws -> [Data: String] {
|
||||
logger.log("Parser parsed certificate")
|
||||
var result: [Data: String] = [:]
|
||||
// FIXME: THIS
|
||||
for try await line in URL(filePath: "/Users/max/.ssh/known_hosts").lines {
|
||||
let split = line.split(separator: " ").map(String.init)
|
||||
guard split.count == 3 else { continue }
|
||||
guard let decoded = Data(base64Encoded: split[2]) else { continue }
|
||||
let reader = OpenSSHReader(data: decoded)
|
||||
_ = try reader.readNextChunk()
|
||||
let key = try reader.readNextChunk()
|
||||
result[key] = split[0]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
import XPCWrappers
|
||||
|
||||
let delegate = XPCServiceDelegate(exportedObject: SecretAgentHostsfileReader())
|
||||
let listener = NSXPCListener.service()
|
||||
listener.delegate = delegate
|
||||
listener.resume()
|
||||
@@ -21,16 +21,21 @@
|
||||
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
|
||||
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
|
||||
501421622781262300BBAA70 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501421612781262300BBAA70 /* Brief */; };
|
||||
501421652781268000BBAA70 /* SecretAgent.app in Copy SecretAgent */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
50153E20250AFCB200525160 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E1F250AFCB200525160 /* UpdateView.swift */; };
|
||||
50153E22250DECA300525160 /* SecretListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListItemView.swift */; };
|
||||
501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501578122E6C0479004A37D0 /* XPCInputParser.swift */; };
|
||||
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
|
||||
502452F92FE2026E009EE753 /* com.maxgoedjen.Secretive.SecretAgent.plist in Copy SecretAgent plist */ = {isa = PBXBuildFile; fileRef = 502452F32FE1FF89009EE753 /* com.maxgoedjen.Secretive.SecretAgent.plist */; };
|
||||
504788F22E681F3A00B4556F /* Instructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F12E681F3A00B4556F /* Instructions.swift */; };
|
||||
504788F42E681F6900B4556F /* ToolConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F32E681F6900B4556F /* ToolConfigurationView.swift */; };
|
||||
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; };
|
||||
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */; };
|
||||
505402A03034B692000C3356 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5054029D3034B692000C3356 /* main.swift */; };
|
||||
505402A13034B692000C3356 /* SecretAgentHostsfileReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5054029E3034B692000C3356 /* SecretAgentHostsfileReader.swift */; };
|
||||
505402A33034B787000C3356 /* SecretAgentHostsfileReader.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 5054028D3034B5E3000C3356 /* SecretAgentHostsfileReader.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
505402A73034B7A4000C3356 /* XPCWrappers in Frameworks */ = {isa = PBXBuildFile; productRef = 505402A63034B7A4000C3356 /* XPCWrappers */; };
|
||||
505402AA3034B80E000C3356 /* XPCHostsfileReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505402A93034B80E000C3356 /* XPCHostsfileReader.swift */; };
|
||||
505402AC303594D1000C3356 /* SSHProtocolKit in Frameworks */ = {isa = PBXBuildFile; productRef = 505402AB303594D1000C3356 /* SSHProtocolKit */; };
|
||||
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
|
||||
505F5EF22FA9635700C45824 /* CertificateKit in Frameworks */ = {isa = PBXBuildFile; productRef = 505F5EF12FA9635700C45824 /* CertificateKit */; };
|
||||
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
|
||||
@@ -111,6 +116,13 @@
|
||||
remoteGlobalIDString = 501577BC2E6BC5B4004A37D0;
|
||||
remoteInfo = ReleasesDownloader;
|
||||
};
|
||||
505402A43034B787000C3356 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 5054028C3034B5E3000C3356;
|
||||
remoteInfo = SecretAgentHostsfileReader;
|
||||
};
|
||||
50692D1B2E6FDB880043C7BB /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
||||
@@ -175,6 +187,7 @@
|
||||
dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
505402A33034B787000C3356 /* SecretAgentHostsfileReader.xpc in Embed XPC Services */,
|
||||
50692E6D2E6FFA5F0043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */,
|
||||
50E2052D2FAAB92000402380 /* SecretiveCertificateParser.xpc in Embed XPC Services */,
|
||||
50692E702E6FFA6E0043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */,
|
||||
@@ -182,17 +195,6 @@
|
||||
name = "Embed XPC Services";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
502452F82FE2024D009EE753 /* Copy SecretAgent plist */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = Contents/Library/LaunchAgents;
|
||||
dstSubfolderSpec = 1;
|
||||
files = (
|
||||
502452F92FE2026E009EE753 /* com.maxgoedjen.Secretive.SecretAgent.plist in Copy SecretAgent plist */,
|
||||
);
|
||||
name = "Copy SecretAgent plist";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
50617DBF23FCE4AB0099B055 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -213,15 +215,14 @@
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
50C385AF240E438B00AF2719 /* Copy SecretAgent */ = {
|
||||
50C385AF240E438B00AF2719 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = Contents/Library/LoginItems;
|
||||
dstSubfolderSpec = 1;
|
||||
files = (
|
||||
501421652781268000BBAA70 /* SecretAgent.app in Copy SecretAgent */,
|
||||
501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */,
|
||||
);
|
||||
name = "Copy SecretAgent";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
@@ -237,11 +238,16 @@
|
||||
50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = "<group>"; };
|
||||
501578122E6C0479004A37D0 /* XPCInputParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCInputParser.swift; sourceTree = "<group>"; };
|
||||
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
|
||||
502452F32FE1FF89009EE753 /* com.maxgoedjen.Secretive.SecretAgent.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = com.maxgoedjen.Secretive.SecretAgent.plist; sourceTree = "<group>"; };
|
||||
504788F12E681F3A00B4556F /* Instructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instructions.swift; sourceTree = "<group>"; };
|
||||
504788F32E681F6900B4556F /* ToolConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolConfigurationView.swift; sourceTree = "<group>"; };
|
||||
504788F52E68206F00B4556F /* GettingStartedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GettingStartedView.swift; sourceTree = "<group>"; };
|
||||
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxBackgroundStyle.swift; sourceTree = "<group>"; };
|
||||
5054028D3034B5E3000C3356 /* SecretAgentHostsfileReader.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretAgentHostsfileReader.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5054029C3034B692000C3356 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
5054029D3034B692000C3356 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||
5054029E3034B692000C3356 /* SecretAgentHostsfileReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretAgentHostsfileReader.swift; sourceTree = "<group>"; };
|
||||
505402A83034B7D2000C3356 /* SecretAgentHostsfileReader.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretAgentHostsfileReader.entitlements; sourceTree = "<group>"; };
|
||||
505402A93034B80E000C3356 /* XPCHostsfileReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCHostsfileReader.swift; sourceTree = "<group>"; };
|
||||
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; };
|
||||
5059933F2E7A3B5B0092CFFA /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -304,6 +310,15 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
5054028A3034B5E3000C3356 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
505402AC303594D1000C3356 /* SSHProtocolKit in Frameworks */,
|
||||
505402A73034B7A4000C3356 /* XPCWrappers in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
50617D7C23FCE48D0099B055 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -422,6 +437,17 @@
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5054029F3034B692000C3356 /* SecretAgentHostsfileReader */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
505402A83034B7D2000C3356 /* SecretAgentHostsfileReader.entitlements */,
|
||||
5054029C3034B692000C3356 /* Info.plist */,
|
||||
5054029D3034B692000C3356 /* main.swift */,
|
||||
5054029E3034B692000C3356 /* SecretAgentHostsfileReader.swift */,
|
||||
);
|
||||
path = SecretAgentHostsfileReader;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
50617D7623FCE48D0099B055 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -432,6 +458,7 @@
|
||||
50692D272E6FDB8D0043C7BB /* SecretiveUpdater */,
|
||||
50692E662E6FF9E20043C7BB /* SecretAgentInputParser */,
|
||||
50E205262FAAB82700402380 /* SecretiveCertificateParser */,
|
||||
5054029F3034B692000C3356 /* SecretAgentHostsfileReader */,
|
||||
50617D8023FCE48E0099B055 /* Products */,
|
||||
5099A08B240243730062B6F2 /* Frameworks */,
|
||||
);
|
||||
@@ -445,6 +472,7 @@
|
||||
50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */,
|
||||
50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */,
|
||||
50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */,
|
||||
5054028D3034B5E3000C3356 /* SecretAgentHostsfileReader.xpc */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -459,7 +487,6 @@
|
||||
508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */,
|
||||
50E4C4C72E777E4200C73783 /* AppIcon.icon */,
|
||||
50617D8F23FCE48E0099B055 /* Secretive.entitlements */,
|
||||
502452F32FE1FF89009EE753 /* com.maxgoedjen.Secretive.SecretAgent.plist */,
|
||||
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */,
|
||||
50617D8823FCE48E0099B055 /* Preview Content */,
|
||||
);
|
||||
@@ -544,6 +571,7 @@
|
||||
50020BAF24064869003D4025 /* AppDelegate.swift */,
|
||||
5018F54E24064786002EB505 /* Notifier.swift */,
|
||||
501578122E6C0479004A37D0 /* XPCInputParser.swift */,
|
||||
505402A93034B80E000C3356 /* XPCHostsfileReader.swift */,
|
||||
50E2057F2FAB291E00402380 /* CertificateMigrator.swift */,
|
||||
50A3B79524026B7600D209EA /* Main.storyboard */,
|
||||
50A3B79824026B7600D209EA /* Info.plist */,
|
||||
@@ -576,6 +604,27 @@
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
5054028C3034B5E3000C3356 /* SecretAgentHostsfileReader */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 5054029B3034B5E3000C3356 /* Build configuration list for PBXNativeTarget "SecretAgentHostsfileReader" */;
|
||||
buildPhases = (
|
||||
505402893034B5E3000C3356 /* Sources */,
|
||||
5054028A3034B5E3000C3356 /* Frameworks */,
|
||||
5054028B3034B5E3000C3356 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = SecretAgentHostsfileReader;
|
||||
packageProductDependencies = (
|
||||
505402A63034B7A4000C3356 /* XPCWrappers */,
|
||||
505402AB303594D1000C3356 /* SSHProtocolKit */,
|
||||
);
|
||||
productName = SecretAgentHostsfileReader;
|
||||
productReference = 5054028D3034B5E3000C3356 /* SecretAgentHostsfileReader.xpc */;
|
||||
productType = "com.apple.product-type.xpc-service";
|
||||
};
|
||||
50617D7E23FCE48D0099B055 /* Secretive */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 50617D9D23FCE48E0099B055 /* Build configuration list for PBXNativeTarget "Secretive" */;
|
||||
@@ -584,8 +633,7 @@
|
||||
50617D7C23FCE48D0099B055 /* Frameworks */,
|
||||
50617D7D23FCE48D0099B055 /* Resources */,
|
||||
50617DBF23FCE4AB0099B055 /* Embed Frameworks */,
|
||||
50C385AF240E438B00AF2719 /* Copy SecretAgent */,
|
||||
502452F82FE2024D009EE753 /* Copy SecretAgent plist */,
|
||||
50C385AF240E438B00AF2719 /* CopyFiles */,
|
||||
501577C92E6BC5B4004A37D0 /* Embed XPC Services */,
|
||||
);
|
||||
buildRules = (
|
||||
@@ -671,6 +719,7 @@
|
||||
50692E6F2E6FFA5F0043C7BB /* PBXTargetDependency */,
|
||||
50692E722E6FFA6E0043C7BB /* PBXTargetDependency */,
|
||||
50E2052F2FAAB92000402380 /* PBXTargetDependency */,
|
||||
505402A53034B787000C3356 /* PBXTargetDependency */,
|
||||
);
|
||||
name = SecretAgent;
|
||||
packageProductDependencies = (
|
||||
@@ -715,10 +764,13 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 2650;
|
||||
LastSwiftUpdateCheck = 2700;
|
||||
LastUpgradeCheck = 2640;
|
||||
ORGANIZATIONNAME = "Max Goedjen";
|
||||
TargetAttributes = {
|
||||
5054028C3034B5E3000C3356 = {
|
||||
CreatedOnToolsVersion = 27.0;
|
||||
};
|
||||
50617D7E23FCE48D0099B055 = {
|
||||
CreatedOnToolsVersion = 11.3;
|
||||
};
|
||||
@@ -760,13 +812,21 @@
|
||||
50617D7E23FCE48D0099B055 /* Secretive */,
|
||||
50A3B78924026B7500D209EA /* SecretAgent */,
|
||||
50692D112E6FDB880043C7BB /* SecretiveUpdater */,
|
||||
50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */,
|
||||
50E205132FAAB81C00402380 /* SecretiveCertificateParser */,
|
||||
50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */,
|
||||
5054028C3034B5E3000C3356 /* SecretAgentHostsfileReader */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
5054028B3034B5E3000C3356 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
50617D7D23FCE48D0099B055 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -815,6 +875,15 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
505402893034B5E3000C3356 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
505402A03034B692000C3356 /* main.swift in Sources */,
|
||||
505402A13034B692000C3356 /* SecretAgentHostsfileReader.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
50617D7B23FCE48D0099B055 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -884,6 +953,7 @@
|
||||
50020BB024064869003D4025 /* AppDelegate.swift in Sources */,
|
||||
5018F54F24064786002EB505 /* Notifier.swift in Sources */,
|
||||
501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */,
|
||||
505402AA3034B80E000C3356 /* XPCHostsfileReader.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -908,6 +978,11 @@
|
||||
isa = PBXTargetDependency;
|
||||
targetProxy = 501577D32E6BC5DD004A37D0 /* PBXContainerItemProxy */;
|
||||
};
|
||||
505402A53034B787000C3356 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 5054028C3034B5E3000C3356 /* SecretAgentHostsfileReader */;
|
||||
targetProxy = 505402A43034B787000C3356 /* PBXContainerItemProxy */;
|
||||
};
|
||||
50692D1C2E6FDB880043C7BB /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 50692D112E6FDB880043C7BB /* SecretiveUpdater */;
|
||||
@@ -952,6 +1027,104 @@
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
505402973034B5E3000C3356 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = SecretAgentHostsfileReader/SecretAgentHostsfileReader.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_ENHANCED_SECURITY = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SecretAgentHostsfileReader/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentHostsfileReader;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Max Goedjen. All rights reserved.";
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentHostsfileReader";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
505402983034B5E3000C3356 /* Test */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = SecretAgentHostsfileReader/SecretAgentHostsfileReader.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_ENHANCED_SECURITY = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SecretAgentHostsfileReader/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentHostsfileReader;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Max Goedjen. All rights reserved.";
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentHostsfileReader";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Test;
|
||||
};
|
||||
505402993034B5E3000C3356 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = SecretAgentHostsfileReader/SecretAgentHostsfileReader.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_ENHANCED_SECURITY = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SecretAgentHostsfileReader/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentHostsfileReader;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Max Goedjen. All rights reserved.";
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentHostsfileReader";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
50617D9B23FCE48E0099B055 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 508A58AB241E121B0069DC07 /* Config.xcconfig */;
|
||||
@@ -1531,26 +1704,23 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
AUTOMATION_APPLE_EVENTS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_ENHANCED_SECURITY = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
||||
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
||||
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
||||
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
|
||||
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
|
||||
ENABLE_RESOURCE_ACCESS_USB = NO;
|
||||
ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO;
|
||||
INFOPLIST_FILE = SecretAgent/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1560,6 +1730,13 @@
|
||||
MARKETING_VERSION = 1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO;
|
||||
RUNTIME_EXCEPTION_ALLOW_JIT = NO;
|
||||
RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO;
|
||||
RUNTIME_EXCEPTION_DEBUGGING_TOOL = NO;
|
||||
RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = NO;
|
||||
RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = NO;
|
||||
SWIFT_TREAT_WARNINGS_AS_ERRORS = NO;
|
||||
};
|
||||
name = Test;
|
||||
};
|
||||
@@ -1567,27 +1744,24 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
AUTOMATION_APPLE_EVENTS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_ENHANCED_SECURITY = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
||||
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
||||
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
||||
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
|
||||
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
|
||||
ENABLE_RESOURCE_ACCESS_USB = NO;
|
||||
ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO;
|
||||
INFOPLIST_FILE = SecretAgent/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1597,6 +1771,13 @@
|
||||
MARKETING_VERSION = 1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO;
|
||||
RUNTIME_EXCEPTION_ALLOW_JIT = NO;
|
||||
RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO;
|
||||
RUNTIME_EXCEPTION_DEBUGGING_TOOL = NO;
|
||||
RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = NO;
|
||||
RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = NO;
|
||||
SWIFT_TREAT_WARNINGS_AS_ERRORS = NO;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1604,6 +1785,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
AUTOMATION_APPLE_EVENTS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
@@ -1611,21 +1793,17 @@
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_ENHANCED_SECURITY = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
||||
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
||||
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
||||
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
|
||||
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
|
||||
ENABLE_RESOURCE_ACCESS_USB = NO;
|
||||
ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO;
|
||||
INFOPLIST_FILE = SecretAgent/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1636,6 +1814,13 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent";
|
||||
RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO;
|
||||
RUNTIME_EXCEPTION_ALLOW_JIT = NO;
|
||||
RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO;
|
||||
RUNTIME_EXCEPTION_DEBUGGING_TOOL = NO;
|
||||
RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = NO;
|
||||
RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = NO;
|
||||
SWIFT_TREAT_WARNINGS_AS_ERRORS = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1739,6 +1924,16 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
5054029B3034B5E3000C3356 /* Build configuration list for PBXNativeTarget "SecretAgentHostsfileReader" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
505402973034B5E3000C3356 /* Debug */,
|
||||
505402983034B5E3000C3356 /* Test */,
|
||||
505402993034B5E3000C3356 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
50617D7A23FCE48D0099B055 /* Build configuration list for PBXProject "Secretive" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
@@ -1842,6 +2037,14 @@
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Brief;
|
||||
};
|
||||
505402A63034B7A4000C3356 /* XPCWrappers */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = XPCWrappers;
|
||||
};
|
||||
505402AB303594D1000C3356 /* SSHProtocolKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = SSHProtocolKit;
|
||||
};
|
||||
505F5EF12FA9635700C45824 /* CertificateKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = CertificateKit;
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "1"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
|
||||
+13
-23
@@ -1,31 +1,14 @@
|
||||
import SwiftUI
|
||||
@unsafe @preconcurrency import ServiceManagement
|
||||
import SecretKit
|
||||
import SecureEnclaveSecretKit
|
||||
import SmartCardSecretKit
|
||||
import Brief
|
||||
import CertificateKit
|
||||
|
||||
@Observable
|
||||
final class LaunchService: Sendable {
|
||||
private let service = SMAppService.agent(plistName: "com.maxgoedjen.Secretive.SecretAgent.plist")
|
||||
var status: SMAppService.Status {
|
||||
service.status
|
||||
}
|
||||
|
||||
func configure() {
|
||||
try? service.unregister()
|
||||
try! service.register()
|
||||
}
|
||||
|
||||
func disable() {
|
||||
try? service.unregister()
|
||||
}
|
||||
}
|
||||
|
||||
@main
|
||||
struct Secretive: App {
|
||||
|
||||
@Environment(\.agentLaunchController) var agentLaunchController
|
||||
@Environment(\.justUpdatedChecker) var justUpdatedChecker
|
||||
|
||||
@SceneBuilder var body: some Scene {
|
||||
@@ -33,8 +16,18 @@ struct Secretive: App {
|
||||
ContentView()
|
||||
.environment(EnvironmentValues._secretStoreList)
|
||||
.environment(EnvironmentValues._certificateStore)
|
||||
.onAppear {
|
||||
EnvironmentValues._launchService.configure()
|
||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
||||
Task {
|
||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||
@AppStorage("explicitlyDisabled") var explicitlyDisabled = false
|
||||
guard hasRunSetup && !explicitlyDisabled else { return }
|
||||
agentLaunchController.check()
|
||||
guard !agentLaunchController.developmentBuild else { return }
|
||||
if justUpdatedChecker.justUpdatedBuild || !agentLaunchController.running {
|
||||
// Relaunch the agent, since it'll be running from earlier update still
|
||||
try? await agentLaunchController.forceLaunch()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.commands {
|
||||
@@ -122,9 +115,6 @@ extension EnvironmentValues {
|
||||
private static let _justUpdatedChecker = JustUpdatedChecker()
|
||||
@Entry var justUpdatedChecker: any JustUpdatedCheckerProtocol = _justUpdatedChecker
|
||||
|
||||
fileprivate static let _launchService = LaunchService()
|
||||
@Entry var launchService: LaunchService = _launchService
|
||||
|
||||
@MainActor var secretStoreList: SecretStoreList {
|
||||
EnvironmentValues._secretStoreList
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ extension Preview {
|
||||
Attributes(
|
||||
keyType: .init(algorithm: .ecdsa, size: 256),
|
||||
authentication: .presenceRequired,
|
||||
restrictions: .default
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -38,7 +39,7 @@ extension Preview {
|
||||
self.init(secrets: new)
|
||||
}
|
||||
|
||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance) throws -> Data {
|
||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) throws -> Data {
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -82,7 +83,7 @@ extension Preview {
|
||||
self.init(secrets: new)
|
||||
}
|
||||
|
||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance) throws -> Data {
|
||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) throws -> Data {
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
||||
@State private var name = ""
|
||||
@State private var keyAttribution = ""
|
||||
@State private var authenticationRequirement: AuthenticationRequirement = .presenceRequired
|
||||
@State private var restrictions: Restrictions = .default
|
||||
@State private var keyType: KeyType?
|
||||
@State var advanced = false
|
||||
@State var advanced = true // FIXME: Set back
|
||||
@State var errorText: String?
|
||||
|
||||
private var authenticationOptions: [AuthenticationRequirement] {
|
||||
@@ -72,6 +73,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
||||
}
|
||||
}
|
||||
if advanced {
|
||||
SecretRestrictionsView(restrictions: $restrictions)
|
||||
Section {
|
||||
VStack {
|
||||
Picker(.createSecretKeyTypeLabel, selection: $keyType) {
|
||||
@@ -107,6 +109,8 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
} header: {
|
||||
Text("Key Properties")
|
||||
}
|
||||
}
|
||||
if let errorText {
|
||||
@@ -146,6 +150,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
||||
attributes: .init(
|
||||
keyType: keyType!,
|
||||
authentication: authenticationRequirement,
|
||||
restrictions: restrictions,
|
||||
publicKeyAttribution: attribution
|
||||
)
|
||||
)
|
||||
@@ -158,7 +163,52 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
||||
}
|
||||
|
||||
}
|
||||
struct SecretRestrictionsView: View {
|
||||
|
||||
//#Preview {
|
||||
// CreateSecretView(store: Preview.StoreModifiable()) { _ in }
|
||||
//}
|
||||
@Binding var restrictions: Restrictions
|
||||
|
||||
struct IdentifiedString: Identifiable {
|
||||
let value: String
|
||||
var id: String { value }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Section {
|
||||
Toggle("Allow Forwarding", isOn: $restrictions.allowForwarding)
|
||||
Toggle("Allow Signing Operations", isOn: $restrictions.allowSigning)
|
||||
Toggle("Allow Connection Operations", isOn: $restrictions.allowConnections)
|
||||
Picker(selection: $restrictions.allowedDomains) {
|
||||
Text("All")
|
||||
.tag(Restrictions.AllowedDomains.all)
|
||||
Text("Specific")
|
||||
.tag(Restrictions.AllowedDomains.specific([]))
|
||||
} label: {
|
||||
Text("Allowed Domains")
|
||||
}
|
||||
} header: {
|
||||
Text("Restrictions")
|
||||
}
|
||||
if restrictions.allowedDomains != .all {
|
||||
Section {
|
||||
switch restrictions.allowedDomains {
|
||||
case .all:
|
||||
EmptyView()
|
||||
case .specific(let array):
|
||||
if restrictions.allowedDomains != .all {
|
||||
ForEach((array + [""]).map({IdentifiedString(value: $0)})) {
|
||||
TextField("", text: .constant($0.value), prompt: Text("example.com"))
|
||||
.labelsHidden()
|
||||
}
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Allowed Domains")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
CreateSecretView(store: Preview.StoreModifiable()) { _ in }
|
||||
.frame(height: 1000)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ import SwiftUI
|
||||
|
||||
struct AgentStatusView: View {
|
||||
|
||||
@Environment(\.launchService) private var launchService
|
||||
@Environment(\.agentLaunchController) private var agentLaunchController: any AgentLaunchControllerProtocol
|
||||
|
||||
var body: some View {
|
||||
if launchService.status == .enabled {
|
||||
if agentLaunchController.running {
|
||||
AgentRunningView()
|
||||
} else {
|
||||
AgentNotRunningView()
|
||||
@@ -14,53 +14,54 @@ struct AgentStatusView: View {
|
||||
}
|
||||
struct AgentRunningView: View {
|
||||
|
||||
@Environment(\.launchService) private var launchService
|
||||
@Environment(\.agentLaunchController) private var agentLaunchController: any AgentLaunchControllerProtocol
|
||||
@AppStorage("explicitlyDisabled") var explicitlyDisabled = false
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
// if let process = agentLaunchController.process {
|
||||
// ConfigurationItemView(
|
||||
// title: .agentDetailsLocationTitle,
|
||||
// value: process.bundleURL!.path(),
|
||||
// action: .revealInFinder(process.bundleURL!.path()),
|
||||
// )
|
||||
if let process = agentLaunchController.process {
|
||||
ConfigurationItemView(
|
||||
title: .agentDetailsLocationTitle,
|
||||
value: process.bundleURL!.path(),
|
||||
action: .revealInFinder(process.bundleURL!.path()),
|
||||
)
|
||||
ConfigurationItemView(
|
||||
title: .agentDetailsSocketPathTitle,
|
||||
value: URL.socketPath,
|
||||
action: .copy(URL.socketPath),
|
||||
)
|
||||
// ConfigurationItemView(
|
||||
// title: .agentDetailsVersionTitle,
|
||||
// value: Bundle(url: process.bundleURL!)!.infoDictionary!["CFBundleShortVersionString"] as! String
|
||||
// )
|
||||
// if let launchDate = process.launchDate {
|
||||
// ConfigurationItemView(
|
||||
// title: .agentDetailsRunningSinceTitle,
|
||||
// value: launchDate.formatted()
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
ConfigurationItemView(
|
||||
title: .agentDetailsVersionTitle,
|
||||
value: Bundle(url: process.bundleURL!)!.infoDictionary!["CFBundleShortVersionString"] as! String
|
||||
)
|
||||
if let launchDate = process.launchDate {
|
||||
ConfigurationItemView(
|
||||
title: .agentDetailsRunningSinceTitle,
|
||||
value: launchDate.formatted()
|
||||
)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text(.agentReadyNoticeDetailTitle)
|
||||
Text(.agentRunningNoticeDetailTitle)
|
||||
.font(.headline)
|
||||
.padding(.top)
|
||||
} footer: {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text(.agentReadyNoticeDetailDescription)
|
||||
Text(.agentRunningNoticeDetailDescription)
|
||||
HStack {
|
||||
Spacer()
|
||||
Menu(.agentDetailsRestartAgentButton) {
|
||||
Button(.agentDetailsDisableAgentButton) {
|
||||
Task {
|
||||
explicitlyDisabled = true
|
||||
launchService.disable()
|
||||
try? await agentLaunchController
|
||||
.uninstall()
|
||||
}
|
||||
}
|
||||
} primaryAction: {
|
||||
Task {
|
||||
launchService.configure()
|
||||
try? await agentLaunchController.forceLaunch()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +78,7 @@ struct AgentRunningView: View {
|
||||
|
||||
struct AgentNotRunningView: View {
|
||||
|
||||
@Environment(\.agentLaunchController) private var agentLaunchController
|
||||
@State var triedRestart = false
|
||||
@State var loading = false
|
||||
@AppStorage("explicitlyDisabled") var explicitlyDisabled = false
|
||||
@@ -85,12 +87,12 @@ struct AgentNotRunningView: View {
|
||||
Form {
|
||||
Section {
|
||||
} header: {
|
||||
Text(.agentNotConfiguredNoticeTitle)
|
||||
Text(.agentNotRunningNoticeTitle)
|
||||
.font(.headline)
|
||||
.padding(.top)
|
||||
} footer: {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text(.agentNotConfiguredNoticeDetailDescription)
|
||||
Text(.agentNotRunningNoticeDetailDescription)
|
||||
HStack {
|
||||
if !triedRestart {
|
||||
Spacer()
|
||||
@@ -98,6 +100,14 @@ struct AgentNotRunningView: View {
|
||||
explicitlyDisabled = false
|
||||
guard !loading else { return }
|
||||
loading = true
|
||||
Task {
|
||||
try? await agentLaunchController.forceLaunch()
|
||||
loading = false
|
||||
|
||||
if !agentLaunchController.running {
|
||||
triedRestart = true
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
if !loading {
|
||||
Text(.agentDetailsStartAgentButton)
|
||||
|
||||
@@ -19,7 +19,6 @@ struct ContentView: View {
|
||||
@Environment(\.certificateStore) private var certificateStore
|
||||
@Environment(\.updater) private var updater
|
||||
@Environment(\.agentLaunchController) private var agentLaunchController
|
||||
@Environment(\.launchService) private var launchService
|
||||
|
||||
@AppStorage("defaultsHasRunSetup") private var hasRunSetup = false
|
||||
@State private var showingCreation = false
|
||||
@@ -148,15 +147,15 @@ extension ContentView {
|
||||
showingAgentInfo = true
|
||||
}, label: {
|
||||
HStack {
|
||||
if launchService.status == .enabled {
|
||||
Text(.agentReadyNoticeTitle)
|
||||
if agentLaunchController.running {
|
||||
Text(.agentRunningNoticeTitle)
|
||||
.font(.headline)
|
||||
.foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white)
|
||||
Circle()
|
||||
.frame(width: 10, height: 10)
|
||||
.foregroundColor(Color.green)
|
||||
} else {
|
||||
Text(.agentNotConfiguredNoticeTitle)
|
||||
Text(.agentNotRunningNoticeTitle)
|
||||
.font(.headline)
|
||||
Circle()
|
||||
.frame(width: 10, height: 10)
|
||||
@@ -166,8 +165,8 @@ extension ContentView {
|
||||
})
|
||||
.buttonStyle(
|
||||
ToolbarStatusButtonStyle(
|
||||
lightColor: launchService.status == .enabled ? .black.opacity(0.05) : .red.opacity(0.75),
|
||||
darkColor: launchService.status == .enabled ? .white.opacity(0.05) : .red.opacity(0.5),
|
||||
lightColor: agentLaunchController.running ? .black.opacity(0.05) : .red.opacity(0.75),
|
||||
darkColor: agentLaunchController.running ? .white.opacity(0.05) : .red.opacity(0.5),
|
||||
)
|
||||
)
|
||||
.popover(isPresented: $showingAgentInfo, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) {
|
||||
|
||||
@@ -163,8 +163,7 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
||||
} else {
|
||||
if #available(macOS 26.0, *) {
|
||||
content
|
||||
// Very thin opacity lets user hover anywhere over the view, glassEffect doesn't allow.
|
||||
.background(.white.opacity(0.01), in: RoundedRectangle(cornerRadius: 15))
|
||||
.contentShape(RoundedRectangle(cornerRadius: 15))
|
||||
.glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), in: RoundedRectangle(cornerRadius: 15))
|
||||
.mask(RoundedRectangle(cornerRadius: 15))
|
||||
.shadow(color: .black.opacity(0.1), radius: 5)
|
||||
@@ -179,7 +178,12 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
||||
func backgroundColor(interactionState: InteractionState) -> Color {
|
||||
guard appearsActive else { return Color.clear }
|
||||
if #available(macOS 26.0, *) {
|
||||
let base = colorScheme == .dark ? Color(white: 0.2) : Color(white: 1)
|
||||
let base: Color
|
||||
if #available(macOS 27.0, *) {
|
||||
base = .clear
|
||||
} else {
|
||||
base = colorScheme == .dark ? Color(white: 0.2) : Color(white: 1)
|
||||
}
|
||||
switch interactionState {
|
||||
case .normal:
|
||||
return base
|
||||
|
||||
@@ -53,7 +53,6 @@ struct MultilineInfoView: View {
|
||||
}
|
||||
}
|
||||
.safeAreaPadding(20)
|
||||
._background(interactionState: interactionStateIndex == item.offset ? interactionState : .normal, cornerRadius: 0)
|
||||
.onHover { hovering in
|
||||
withAnimation {
|
||||
guard item.element.action != nil else { return }
|
||||
@@ -117,8 +116,7 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
if #available(macOS 26.0, *) {
|
||||
content
|
||||
// Very thin opacity lets user hover anywhere over the view, glassEffect doesn't allow.
|
||||
.background(.white.opacity(0.01), in: RoundedRectangle(cornerRadius: 15))
|
||||
.contentShape(RoundedRectangle(cornerRadius: cornerRadius))
|
||||
.glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), in: RoundedRectangle(cornerRadius: cornerRadius))
|
||||
.mask(RoundedRectangle(cornerRadius: cornerRadius))
|
||||
.shadow(color: .black.opacity(0.1), radius: 5)
|
||||
@@ -132,7 +130,12 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
||||
func backgroundColor(interactionState: InteractionState) -> Color {
|
||||
guard appearsActive else { return Color.clear }
|
||||
if #available(macOS 26.0, *) {
|
||||
let base = colorScheme == .dark ? Color(white: 0.2) : Color(white: 1)
|
||||
let base: Color
|
||||
if #available(macOS 27.0, *) {
|
||||
base = .clear
|
||||
} else {
|
||||
base = colorScheme == .dark ? Color(white: 0.2) : Color(white: 1)
|
||||
}
|
||||
switch interactionState {
|
||||
case .normal:
|
||||
return base
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.maxgoedjen.Secretive.SecretAgent</string>
|
||||
<key>BundleProgram</key>
|
||||
<string>Contents/Library/LoginItems/SecretAgent.app/Contents/MacOS/SecretAgent</string>
|
||||
<key>Sockets</key>
|
||||
<dict>
|
||||
<key>SecureListener</key>
|
||||
<dict>
|
||||
<key>SecureSocketWithKey</key>
|
||||
<string>SECRETAGENT_SOCK</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
Reference in New Issue
Block a user