mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-09-19 14:48:01 +02:00
Merge
This commit is contained in:
@@ -11,7 +11,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
name: Analyze (${{ matrix.language }})
|
name: Analyze (${{ matrix.language }})
|
||||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-26') || 'ubuntu-latest' }}
|
runs-on: ${{ (matrix.language == 'swift' && 'xcode-27') || 'ubuntu-latest' }}
|
||||||
permissions:
|
permissions:
|
||||||
security-events: write
|
security-events: write
|
||||||
packages: read
|
packages: read
|
||||||
@@ -24,20 +24,15 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- language: actions
|
- language: actions
|
||||||
build-mode: none
|
build-mode: none
|
||||||
# Disable this until CodeQL supports Xcode 26 builds.
|
|
||||||
# - language: swift
|
|
||||||
# build-mode: manual
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
build-mode: ${{ matrix.build-mode }}
|
build-mode: ${{ matrix.build-mode }}
|
||||||
- if: matrix.build-mode == 'manual'
|
- if: matrix.build-mode == 'manual'
|
||||||
name: "Select Xcode"
|
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
|
|
||||||
- if: matrix.build-mode == 'manual'
|
- if: matrix.build-mode == 'manual'
|
||||||
name: "Build"
|
name: "Build"
|
||||||
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: macos-26
|
runs-on: xcode-27
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
contents: write
|
contents: write
|
||||||
@@ -25,8 +25,6 @@ jobs:
|
|||||||
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
|
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
|
||||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||||
run: ./.github/scripts/signing.sh
|
run: ./.github/scripts/signing.sh
|
||||||
- name: Set Environment
|
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
|
|
||||||
- name: Update Build Number
|
- name: Update Build Number
|
||||||
env:
|
env:
|
||||||
RUN_ID: ${{ github.run_id }}
|
RUN_ID: ${{ github.run_id }}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: macos-26
|
runs-on: xcode-27
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
contents: write
|
contents: write
|
||||||
@@ -24,8 +24,6 @@ jobs:
|
|||||||
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
|
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
|
||||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||||
run: ./.github/scripts/signing.sh
|
run: ./.github/scripts/signing.sh
|
||||||
- name: Set Environment
|
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
|
|
||||||
- name: Update Build Number
|
- name: Update Build Number
|
||||||
env:
|
env:
|
||||||
RUN_ID: ${{ github.run_id }}
|
RUN_ID: ${{ github.run_id }}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
runs-on: macos-26
|
runs-on: xcode-27
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
@@ -21,8 +21,6 @@ jobs:
|
|||||||
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
|
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
|
||||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||||
run: ./.github/scripts/signing.sh
|
run: ./.github/scripts/signing.sh
|
||||||
- name: Set Environment
|
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test
|
||||||
# SPM doesn't seem to pick up on the tests currently?
|
# SPM doesn't seem to pick up on the tests currently?
|
||||||
@@ -34,7 +32,7 @@ jobs:
|
|||||||
attestations: write
|
attestations: write
|
||||||
artifact-metadata: write
|
artifact-metadata: write
|
||||||
actions: read
|
actions: read
|
||||||
runs-on: macos-26
|
runs-on: xcode-27
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
@@ -47,8 +45,6 @@ jobs:
|
|||||||
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
|
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
|
||||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||||
run: ./.github/scripts/signing.sh
|
run: ./.github/scripts/signing.sh
|
||||||
- name: Set Environment
|
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
|
|
||||||
- name: Update Build Number
|
- name: Update Build Number
|
||||||
env:
|
env:
|
||||||
TAG_NAME: ${{ github.ref }}
|
TAG_NAME: ${{ github.ref }}
|
||||||
|
|||||||
@@ -5,12 +5,10 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
runs-on: macos-26
|
runs-on: xcode-27
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- name: Set Environment
|
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
|
|
||||||
- name: Test Main Packages
|
- name: Test Main Packages
|
||||||
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test
|
||||||
# SPM doesn't seem to pick up on the tests currently?
|
# SPM doesn't seem to pick up on the tests currently?
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -34,16 +34,14 @@ import XPCWrappers
|
|||||||
) {
|
) {
|
||||||
self.osVersion = osVersion
|
self.osVersion = osVersion
|
||||||
self.currentVersion = currentVersion
|
self.currentVersion = currentVersion
|
||||||
Task {
|
_ = Task {
|
||||||
do {
|
if checkOnLaunch {
|
||||||
if checkOnLaunch {
|
try await checkForUpdates()
|
||||||
try await checkForUpdates()
|
}
|
||||||
}
|
while true {
|
||||||
while !Task.isCancelled {
|
try await Task.sleep(for: .seconds(Int(checkFrequency)))
|
||||||
try? await Task.sleep(for: .seconds(Int(checkFrequency)))
|
try await checkForUpdates()
|
||||||
try await checkForUpdates()
|
}
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,37 +22,37 @@ public struct MultilineInfoView<TitleView: View, ItemView: View>: View {
|
|||||||
self.items = items()
|
self.items = items()
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(title: LocalizedStringResource, subtitle: LocalizedStringResource, image: Image, items: [String]) where TitleView == HStack<TupleView<(Image, Text, Spacer)>> , ItemView == Text {
|
// public init(title: LocalizedStringResource, subtitle: LocalizedStringResource, image: Image, items: [String]) where ItemView == Text {
|
||||||
self.init {
|
|
||||||
HStack {
|
|
||||||
image
|
|
||||||
.renderingMode(.template)
|
|
||||||
// .imageScale(.large)
|
|
||||||
.foregroundColor(primaryTextColor)
|
|
||||||
Text(title)
|
|
||||||
.font(.headline)
|
|
||||||
.foregroundColor(primaryTextColor)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
} items: {
|
|
||||||
[Text("Hello")]
|
|
||||||
}
|
|
||||||
|
|
||||||
// self.init {
|
// self.init {
|
||||||
// } items: {
|
// HStack {
|
||||||
// ForEach(items) { item in
|
// image
|
||||||
// return HStack {
|
// .renderingMode(.template)
|
||||||
// Text(item)
|
//// .imageScale(.large)
|
||||||
// Spacer()
|
// .foregroundColor(primaryTextColor)
|
||||||
// // if let (image, _) = $0.1 {
|
// Text(title)
|
||||||
// // image
|
// .font(.headline)
|
||||||
// // .foregroundStyle(.secondary)
|
// .foregroundColor(primaryTextColor)
|
||||||
// // }
|
// Spacer()
|
||||||
// }
|
|
||||||
// }
|
// }
|
||||||
|
// } items: {
|
||||||
|
// [Text("Hello")]
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
}
|
//// self.init {
|
||||||
|
//// } items: {
|
||||||
|
//// ForEach(items) { item in
|
||||||
|
//// return HStack {
|
||||||
|
//// Text(item)
|
||||||
|
//// Spacer()
|
||||||
|
//// // if let (image, _) = $0.1 {
|
||||||
|
//// // image
|
||||||
|
//// // .foregroundStyle(.secondary)
|
||||||
|
//// // }
|
||||||
|
//// }
|
||||||
|
//// }
|
||||||
|
//// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
@State private var interactionState: InteractionState = .normal
|
@State private var interactionState: InteractionState = .normal
|
||||||
@State private var interactionStateIndex: Int?
|
@State private var interactionStateIndex: Int?
|
||||||
@@ -61,31 +61,31 @@ public struct MultilineInfoView<TitleView: View, ItemView: View>: View {
|
|||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
titleView
|
titleView
|
||||||
.safeAreaPadding(20)
|
.safeAreaPadding(20)
|
||||||
ForEach(Array(items.enumerated()), id: \.offset) { item in
|
// ForEach(Array(items.enumerated()), id: \.offset) { item in
|
||||||
Divider()
|
// Divider()
|
||||||
.ignoresSafeArea()
|
// .ignoresSafeArea()
|
||||||
.opacity(item.offset == 0 ? 1 : 0.75)
|
// .opacity(item.offset == 0 ? 1 : 0.75)
|
||||||
items.element
|
// items.element
|
||||||
.safeAreaPadding(20)
|
// .safeAreaPadding(20)
|
||||||
.onHover { hovering in
|
// .onHover { hovering in
|
||||||
withAnimation {
|
// withAnimation {
|
||||||
guard item.element.action != nil else { return }
|
// guard item.element.action != nil else { return }
|
||||||
interactionState = hovering ? .hovering : .normal
|
// interactionState = hovering ? .hovering : .normal
|
||||||
interactionStateIndex = item.offset
|
// interactionStateIndex = item.offset
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
.gesture(
|
// .gesture(
|
||||||
TapGesture()
|
// TapGesture()
|
||||||
.onEnded {
|
// .onEnded {
|
||||||
item.element.action?.1()
|
// item.element.action?.1()
|
||||||
withAnimation {
|
// withAnimation {
|
||||||
interactionState = .normal
|
// interactionState = .normal
|
||||||
interactionStateIndex = nil
|
// interactionStateIndex = nil
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
._background(interactionState: .normal)
|
._background(interactionState: .normal)
|
||||||
.frame(minWidth: 150, maxWidth: .infinity)
|
.frame(minWidth: 150, maxWidth: .infinity)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public struct OpenSSHPublicKeyWriter: Sendable {
|
|||||||
("nistp" + String(describing: secret.keyType.size)).lengthAndData +
|
("nistp" + String(describing: secret.keyType.size)).lengthAndData +
|
||||||
secret.publicKey.lengthAndData
|
secret.publicKey.lengthAndData
|
||||||
case .mldsa:
|
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 +
|
openSSHIdentifier(for: secret.keyType).lengthAndData +
|
||||||
secret.publicKey.lengthAndData
|
secret.publicKey.lengthAndData
|
||||||
case .rsa:
|
case .rsa:
|
||||||
|
|||||||
@@ -42,6 +42,18 @@ public final class OpenSSHReader {
|
|||||||
return convertEndianness ? T(value.bigEndian) : T(value)
|
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 {
|
public func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String {
|
||||||
try String(decoding: readNextChunk(convertEndianness: convertEndianness), as: UTF8.self)
|
try String(decoding: readNextChunk(convertEndianness: convertEndianness), as: UTF8.self)
|
||||||
}
|
}
|
||||||
@@ -53,5 +65,6 @@ public final class OpenSSHReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum OpenSSHReaderError: Error, Codable {
|
public enum OpenSSHReaderError: Error, Codable {
|
||||||
|
case incorrectFormat
|
||||||
case beyondBounds
|
case beyondBounds
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public struct OpenSSHSignatureWriter: Sendable {
|
|||||||
// https://datatracker.ietf.org/doc/html/rfc5656#section-3.1
|
// https://datatracker.ietf.org/doc/html/rfc5656#section-3.1
|
||||||
ecdsaSignature(signature, keyType: secret.keyType)
|
ecdsaSignature(signature, keyType: secret.keyType)
|
||||||
case .mldsa:
|
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)
|
mldsaSignature(signature, keyType: secret.keyType)
|
||||||
case .rsa:
|
case .rsa:
|
||||||
// https://datatracker.ietf.org/doc/html/rfc4253#section-6.6
|
// https://datatracker.ietf.org/doc/html/rfc4253#section-6.6
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import OSLog
|
|||||||
import SecretKit
|
import SecretKit
|
||||||
import CertificateKit
|
import CertificateKit
|
||||||
|
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
public protocol SSHAgentInputParserProtocol {
|
public protocol SSHAgentInputParserProtocol {
|
||||||
|
|
||||||
func parse(data: Data) async throws -> SSHAgent.Request
|
func parse(data: Data) async throws -> SSHAgent.Request
|
||||||
@@ -53,8 +55,8 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
|
|||||||
return .unlock
|
return .unlock
|
||||||
case SSHAgent.Request.addSmartcardKeyConstrained.protocolID:
|
case SSHAgent.Request.addSmartcardKeyConstrained.protocolID:
|
||||||
return .addSmartcardKeyConstrained
|
return .addSmartcardKeyConstrained
|
||||||
case SSHAgent.Request.protocolExtension.protocolID:
|
case SSHAgent.Request.protocolExtension(.empty).protocolID:
|
||||||
return .protocolExtension
|
return .protocolExtension(try protocolExtension(from: body))
|
||||||
default:
|
default:
|
||||||
return .unknown(rawRequestInt)
|
return .unknown(rawRequestInt)
|
||||||
}
|
}
|
||||||
@@ -64,12 +66,150 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
|
|||||||
|
|
||||||
extension SSHAgentInputParser {
|
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 {
|
func signatureRequestContext(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext {
|
||||||
let reader = OpenSSHReader(data: data)
|
let reader = OpenSSHReader(data: data)
|
||||||
let rawKeyBlob = try reader.readNextChunk()
|
let rawKeyBlob = try reader.readNextChunk()
|
||||||
let keyBlob = certificatePublicKeyBlob(from: rawKeyBlob) ?? rawKeyBlob
|
let keyBlob = certificatePublicKeyBlob(from: rawKeyBlob) ?? rawKeyBlob
|
||||||
let dataToSign = try reader.readNextChunk()
|
let rawPayload = try reader.readNextChunk()
|
||||||
return SSHAgent.Request.SignatureRequestContext(keyBlob: keyBlob, dataToSign: dataToSign)
|
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 {
|
||||||
|
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":
|
||||||
|
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? {
|
func certificatePublicKeyBlob(from hash: Data) -> Data? {
|
||||||
@@ -99,6 +239,7 @@ extension SSHAgentInputParser {
|
|||||||
case unknownRequest
|
case unknownRequest
|
||||||
case unhandledRequest
|
case unhandledRequest
|
||||||
case invalidData
|
case invalidData
|
||||||
|
case incorrectSignature
|
||||||
case openSSHReader(OpenSSHReaderError)
|
case openSSHReader(OpenSSHReaderError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ extension SSHAgent {
|
|||||||
case lock
|
case lock
|
||||||
case unlock
|
case unlock
|
||||||
case addSmartcardKeyConstrained
|
case addSmartcardKeyConstrained
|
||||||
case protocolExtension
|
case protocolExtension(ProtocolExtension)
|
||||||
case unknown(UInt8)
|
case unknown(UInt8)
|
||||||
|
|
||||||
public var protocolID: UInt8 {
|
public var protocolID: UInt8 {
|
||||||
@@ -60,18 +60,82 @@ extension SSHAgent {
|
|||||||
|
|
||||||
public struct SignatureRequestContext: Sendable, Codable {
|
public struct SignatureRequestContext: Sendable, Codable {
|
||||||
public let keyBlob: Data
|
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.keyBlob = keyBlob
|
||||||
self.dataToSign = dataToSign
|
self.dataToSign = dataToSign
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var empty: SignatureRequestContext {
|
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
|
/// 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 {
|
switch self {
|
||||||
case .agentFailure: "SSH_AGENT_FAILURE"
|
case .agentFailure: "SSH_AGENT_FAILURE"
|
||||||
case .agentSuccess: "SSH_AGENT_SUCCESS"
|
case .agentSuccess: "SSH_AGENT_SUCCESS"
|
||||||
case .agentIdentitiesAnswer: "SSH_AGENT_IDENTITIES_ANSWER"
|
case .agentIdentitiesAnswer: "SSH2_AGENT_IDENTITIES_ANSWER"
|
||||||
case .agentSignResponse: "SSH_AGENT_SIGN_RESPONSE"
|
case .agentSignResponse: "SSH2_AGENT_SIGN_RESPONSE"
|
||||||
case .agentExtensionFailure: "SSH_AGENT_EXTENSION_FAILURE"
|
case .agentExtensionFailure: "SSH_AGENT_EXTENSION_FAILURE"
|
||||||
case .agentExtensionResponse: "SSH_AGENT_EXTENSION_RESPONSE"
|
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)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,6 +17,8 @@ public final class Agent: Sendable {
|
|||||||
private let signatureWriter = OpenSSHSignatureWriter()
|
private let signatureWriter = OpenSSHSignatureWriter()
|
||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "Agent")
|
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.
|
/// Initializes an agent with a store list and a witness.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - storeList: The `SecretStoreList` to make available.
|
/// - storeList: The `SecretStoreList` to make available.
|
||||||
@@ -38,7 +40,12 @@ public final class Agent: Sendable {
|
|||||||
|
|
||||||
extension Agent {
|
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
|
// Depending on the launch context (such as after macOS update), the agent may need to reload secrets before acting
|
||||||
await reloadSecretsIfNeccessary()
|
await reloadSecretsIfNeccessary()
|
||||||
var response = Data()
|
var response = Data()
|
||||||
@@ -49,9 +56,55 @@ extension Agent {
|
|||||||
response.append(await identities())
|
response.append(await identities())
|
||||||
logger.debug("Agent returned \(SSHAgent.Response.agentIdentitiesAnswer.debugDescription)")
|
logger.debug("Agent returned \(SSHAgent.Response.agentIdentitiesAnswer.debugDescription)")
|
||||||
case .signRequest(let context):
|
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
|
||||||
|
}
|
||||||
|
_ = target
|
||||||
response.append(SSHAgent.Response.agentSignResponse.data)
|
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)")
|
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
|
||||||
|
case .protocolExtension(.openSSH(.sessionBind(let bind))):
|
||||||
|
// This is disabled until forward enforcement is handled.
|
||||||
|
_ = bind
|
||||||
|
response = try await MainActor.run {
|
||||||
|
logger.debug("Agent received bind request but not currently supported.")
|
||||||
|
throw UnhandledRequestError()
|
||||||
|
// guard sessionID == nil else {
|
||||||
|
// logger.error("Agent received bind request, but already bound.")
|
||||||
|
// 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):
|
case .unknown(let value):
|
||||||
logger.error("Agent received unknown request of type \(value).")
|
logger.error("Agent received unknown request of type \(value).")
|
||||||
throw UnhandledRequestError()
|
throw UnhandledRequestError()
|
||||||
@@ -99,16 +152,15 @@ extension Agent {
|
|||||||
/// - data: The data to sign.
|
/// - data: The data to sign.
|
||||||
/// - provenance: A ``SecretKit.SigningRequestProvenance`` object describing the origin of the request.
|
/// - provenance: A ``SecretKit.SigningRequestProvenance`` object describing the origin of the request.
|
||||||
/// - Returns: An OpenSSH formatted Data payload containing the signed data response.
|
/// - 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 {
|
guard let (secret, store) = await secret(matching: keyBlob) else {
|
||||||
let keyBlobHex = keyBlob.formatted(.hex())
|
let keyBlobHex = keyBlob.formatted(.hex())
|
||||||
logger.debug("Agent did not have a key matching \(keyBlobHex)")
|
logger.debug("Agent did not have a key matching \(keyBlobHex)")
|
||||||
throw NoMatchingKeyError()
|
throw NoMatchingKeyError()
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug("Agent offering witness chance to object")
|
logger.debug("Agent offering witness chance to object")
|
||||||
do {
|
do {
|
||||||
try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance)
|
try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance, target: target)
|
||||||
} catch {
|
} catch {
|
||||||
logger.debug("Witness objected")
|
logger.debug("Witness objected")
|
||||||
throw error
|
throw error
|
||||||
@@ -117,26 +169,26 @@ extension Agent {
|
|||||||
|
|
||||||
if secret.authenticationRequirement.required {
|
if secret.authenticationRequirement.required {
|
||||||
// Slow path, may block or suggest batching.
|
// Slow path, may block or suggest batching.
|
||||||
return try await signWithRequiredAuthentication(data: data, store: store, secret: secret, provenance: provenance)
|
return try await signWithRequiredAuthentication(data: data, store: store, secret: secret, provenance: provenance, target: target)
|
||||||
} else {
|
} else {
|
||||||
// Fast path, no blocking/enqueing required
|
// Fast path, no blocking/enqueing required
|
||||||
return try await signWithoutRequiredAuthentication(data: data, store: store, secret: secret, provenance: provenance)
|
return try await signWithoutRequiredAuthentication(data: data, store: store, secret: secret, provenance: provenance, target: target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func signWithoutRequiredAuthentication(data: Data, store: AnySecretStore, secret: AnySecret, provenance: SigningRequestProvenance) async throws -> Data {
|
func signWithoutRequiredAuthentication(data: Data, store: AnySecretStore, secret: AnySecret, provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws -> Data {
|
||||||
let rawRepresentation = try await store.sign(data: data, with: secret, for: provenance, context: nil)
|
let rawRepresentation = try await store.sign(data: data, with: secret, for: provenance, target: target, context: nil)
|
||||||
let signedData = signatureWriter.data(secret: secret, signature: rawRepresentation)
|
let signedData = signatureWriter.data(secret: secret, signature: rawRepresentation)
|
||||||
try await witness?.witness(accessTo: secret, from: store, by: provenance, offerPersistence: false)
|
try await witness?.witness(accessTo: secret, from: store, by: provenance, target: target, offerPersistence: false)
|
||||||
logger.debug("Agent signed request")
|
logger.debug("Agent signed request")
|
||||||
return signedData
|
return signedData
|
||||||
}
|
}
|
||||||
|
|
||||||
func signWithRequiredAuthentication(data: Data, store: AnySecretStore, secret: AnySecret, provenance: SigningRequestProvenance) async throws -> Data {
|
func signWithRequiredAuthentication(data: Data, store: AnySecretStore, secret: AnySecret, provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws -> Data {
|
||||||
let context = try await authenticationHandler.waitForAuthentication(for: SignatureRequest(secret: secret, provenance: provenance))
|
let context = try await authenticationHandler.waitForAuthentication(for: SignatureRequest(secret: secret, provenance: provenance))
|
||||||
let result = try await store.sign(data: data, with: secret, for: provenance, context: context.laContext)
|
let result = try await store.sign(data: data, with: secret, for: provenance, target: target, context: context.laContext)
|
||||||
let signedData = signatureWriter.data(secret: secret, signature: result)
|
let signedData = signatureWriter.data(secret: secret, signature: result)
|
||||||
try await witness?.witness(accessTo: secret, from: store, by: provenance, offerPersistence: false) // FIXME: THIS
|
try await witness?.witness(accessTo: secret, from: store, by: provenance, target: target, offerPersistence: false) // FIXME: THIS
|
||||||
logger.debug("Agent signed request")
|
logger.debug("Agent signed request")
|
||||||
return signedData
|
return signedData
|
||||||
}
|
}
|
||||||
@@ -172,6 +224,7 @@ extension Agent {
|
|||||||
|
|
||||||
struct NoMatchingKeyError: Error {}
|
struct NoMatchingKeyError: Error {}
|
||||||
struct UnhandledRequestError: Error {}
|
struct UnhandledRequestError: Error {}
|
||||||
|
struct BindingFailure: Error {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SecretKit
|
import SecretKit
|
||||||
|
import SSHProtocolKit
|
||||||
|
|
||||||
/// A protocol that allows conformers to be notified of access to secrets, and optionally prevent access.
|
/// A protocol that allows conformers to be notified of access to secrets, and optionally prevent access.
|
||||||
public protocol SigningWitness: Sendable {
|
public protocol SigningWitness: Sendable {
|
||||||
@@ -10,13 +11,13 @@ public protocol SigningWitness: Sendable {
|
|||||||
/// - store: The `Store` being asked to sign the request..
|
/// - store: The `Store` being asked to sign the request..
|
||||||
/// - provenance: A `SigningRequestProvenance` object describing the origin of 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.
|
/// - 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.
|
/// Notifies the callee that a signing operation has been performed for a given secret.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - secret: The `Secret` that will was used to sign the request.
|
/// - secret: The `Secret` that will was used to sign the request.
|
||||||
/// - store: The `Store` that signed the request..
|
/// - store: The `Store` that signed the request..
|
||||||
/// - provenance: A `SigningRequestProvenance` object describing the origin of the request.
|
/// - provenance: A `SigningRequestProvenance` object describing the origin of the request.
|
||||||
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, offerPersistence: Bool) async throws
|
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?, offerPersistence: Bool) async throws
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ open class AnySecretStore: SecretStore, @unchecked Sendable {
|
|||||||
private let _id: @Sendable () -> UUID
|
private let _id: @Sendable () -> UUID
|
||||||
private let _name: @MainActor @Sendable () -> String
|
private let _name: @MainActor @Sendable () -> String
|
||||||
private let _secrets: @MainActor @Sendable () -> [AnySecret]
|
private let _secrets: @MainActor @Sendable () -> [AnySecret]
|
||||||
private let _sign: @Sendable (Data, AnySecret, SigningRequestProvenance, LAContext?) async throws -> Data
|
private let _sign: @Sendable (Data, AnySecret, SigningRequestProvenance, SigningRequestTarget?, LAContext?) async throws -> Data
|
||||||
private let _reloadSecrets: @Sendable () async -> Void
|
private let _reloadSecrets: @Sendable () async -> Void
|
||||||
|
|
||||||
public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStore {
|
public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStore {
|
||||||
@@ -18,7 +18,7 @@ open class AnySecretStore: SecretStore, @unchecked Sendable {
|
|||||||
_name = { secretStore.name }
|
_name = { secretStore.name }
|
||||||
_id = { secretStore.id }
|
_id = { secretStore.id }
|
||||||
_secrets = { secretStore.secrets.map { AnySecret($0) } }
|
_secrets = { secretStore.secrets.map { AnySecret($0) } }
|
||||||
_sign = { try await secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType, for: $2, context: $3) }
|
_sign = { try await secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType, for: $2, target: $3, context: $4) }
|
||||||
_reloadSecrets = { await secretStore.reloadSecrets() }
|
_reloadSecrets = { await secretStore.reloadSecrets() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,8 +38,8 @@ open class AnySecretStore: SecretStore, @unchecked Sendable {
|
|||||||
return _secrets()
|
return _secrets()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sign(data: Data, with secret: AnySecret, for provenance: SigningRequestProvenance, context: LAContext?) async throws -> Data {
|
public func sign(data: Data, with secret: AnySecret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?, context: LAContext?) async throws -> Data {
|
||||||
try await _sign(data, secret, provenance, context)
|
try await _sign(data, secret, provenance, target, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func reloadSecrets() async {
|
public func reloadSecrets() async {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public protocol SecretStore<SecretType>: Identifiable, Sendable {
|
|||||||
/// - secret: The ``Secret`` to sign with.
|
/// - secret: The ``Secret`` to sign with.
|
||||||
/// - provenance: A ``SigningRequestProvenance`` describing where the request came from.
|
/// - provenance: A ``SigningRequestProvenance`` describing where the request came from.
|
||||||
/// - Returns: The signed data.
|
/// - Returns: The signed data.
|
||||||
func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance, context: LAContext?) async throws -> Data
|
func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance, target: SigningRequestTarget?, context: LAContext?) async throws -> Data
|
||||||
|
|
||||||
/// Requests that the store reload secrets from any backing store, if neccessary.
|
/// Requests that the store reload secrets from any backing store, if neccessary.
|
||||||
func reloadSecrets() async
|
func reloadSecrets() async
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -36,8 +36,7 @@ extension SecureEnclave {
|
|||||||
|
|
||||||
// MARK: SecretStore
|
// MARK: SecretStore
|
||||||
|
|
||||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, context: LAContext?) async throws -> Data {
|
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?, context: LAContext?) async throws -> Data {
|
||||||
|
|
||||||
let queryAttributes = KeychainDictionary([
|
let queryAttributes = KeychainDictionary([
|
||||||
kSecClass: Constants.keyClass,
|
kSecClass: Constants.keyClass,
|
||||||
kSecAttrService: Constants.keyTag,
|
kSecAttrService: Constants.keyTag,
|
||||||
|
|||||||
@@ -56,8 +56,7 @@ extension SmartCard {
|
|||||||
|
|
||||||
// MARK: Public API
|
// MARK: Public API
|
||||||
|
|
||||||
|
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?, context: LAContext?) async throws -> Data {
|
||||||
public func sign(data: Data, with secret: SmartCard.Secret, for provenance: SigningRequestProvenance, context: LAContext?) async throws -> Data {
|
|
||||||
guard let tokenID = await state.tokenID else { fatalError() }
|
guard let tokenID = await state.tokenID else { fatalError() }
|
||||||
let attributes = KeychainDictionary([
|
let attributes = KeychainDictionary([
|
||||||
kSecClass: kSecClassKey,
|
kSecClass: kSecClassKey,
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ public final class XPCServiceDelegate: NSObject, NSXPCListenerDelegate {
|
|||||||
newConnection.exportedInterface = NSXPCInterface(with: (any _XPCProtocol).self)
|
newConnection.exportedInterface = NSXPCInterface(with: (any _XPCProtocol).self)
|
||||||
let exportedObject = exportedObject
|
let exportedObject = exportedObject
|
||||||
newConnection.exportedObject = exportedObject
|
newConnection.exportedObject = exportedObject
|
||||||
newConnection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"\(ProcessInfo.processInfo.teamID)\"")
|
#if !DEBUG
|
||||||
|
newConnection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"Z72PRUAWF6\"")
|
||||||
|
#endif
|
||||||
newConnection.resume()
|
newConnection.resume()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ public struct XPCTypedSession<ResponseType: Codable & Sendable, ErrorType: Error
|
|||||||
public init(serviceName: String, warmup: Bool = false) async throws {
|
public init(serviceName: String, warmup: Bool = false) async throws {
|
||||||
let connection = NSXPCConnection(serviceName: serviceName)
|
let connection = NSXPCConnection(serviceName: serviceName)
|
||||||
connection.remoteObjectInterface = NSXPCInterface(with: (any _XPCProtocol).self)
|
connection.remoteObjectInterface = NSXPCInterface(with: (any _XPCProtocol).self)
|
||||||
connection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"\(ProcessInfo.processInfo.teamID)\"")
|
#if !DEBUG
|
||||||
|
connection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"Z72PRUAWF6\"")
|
||||||
|
#endif
|
||||||
connection.resume()
|
connection.resume()
|
||||||
guard let proxy = connection.remoteObjectProxy as? _XPCProtocol else { fatalError() }
|
guard let proxy = connection.remoteObjectProxy as? _XPCProtocol else { fatalError() }
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import CertificateKit
|
|||||||
@Test func emptyStores() async throws {
|
@Test func emptyStores() async throws {
|
||||||
let agent = Agent(storeList: SecretStoreList(), certificateStore: CertificateStore())
|
let agent = Agent(storeList: SecretStoreList(), certificateStore: CertificateStore())
|
||||||
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities)
|
||||||
let response = await agent.handle(request: request, provenance: .test)
|
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
#expect(response == Constants.Responses.requestIdentitiesEmpty)
|
#expect(response == Constants.Responses.requestIdentitiesEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ import CertificateKit
|
|||||||
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore())
|
let agent = Agent(storeList: list, certificateStore: CertificateStore())
|
||||||
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities)
|
||||||
let response = await agent.handle(request: request, provenance: .test)
|
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
|
|
||||||
let actual = OpenSSHReader(data: response)
|
let actual = OpenSSHReader(data: response)
|
||||||
let expected = OpenSSHReader(data: Constants.Responses.requestIdentitiesMultiple)
|
let expected = OpenSSHReader(data: Constants.Responses.requestIdentitiesMultiple)
|
||||||
@@ -35,7 +35,7 @@ import CertificateKit
|
|||||||
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore())
|
let agent = Agent(storeList: list, certificateStore: CertificateStore())
|
||||||
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignatureWithNoneMatching)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignatureWithNoneMatching)
|
||||||
let response = await agent.handle(request: request, provenance: .test)
|
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
#expect(response == Constants.Responses.requestFailure)
|
#expect(response == Constants.Responses.requestFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ import CertificateKit
|
|||||||
guard case SSHAgent.Request.signRequest(let context) = request else { return }
|
guard case SSHAgent.Request.signRequest(let context) = request else { return }
|
||||||
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore())
|
let agent = Agent(storeList: list, certificateStore: CertificateStore())
|
||||||
let response = await agent.handle(request: request, provenance: .test)
|
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
let responseReader = OpenSSHReader(data: response)
|
let responseReader = OpenSSHReader(data: response)
|
||||||
let length = try responseReader.readNextBytes(as: UInt32.self)
|
let length = try responseReader.readNextBytes(as: UInt32.self)
|
||||||
let type = try responseReader.readNextBytes(as: UInt8.self)
|
let type = try responseReader.readNextBytes(as: UInt8.self)
|
||||||
@@ -68,7 +68,7 @@ import CertificateKit
|
|||||||
let signature = try P256.Signing.ECDSASignature(rawRepresentation: rs)
|
let signature = try P256.Signing.ECDSASignature(rawRepresentation: rs)
|
||||||
// Correct signature
|
// Correct signature
|
||||||
#expect(try P256.Signing.PublicKey(x963Representation: Constants.Secrets.ecdsa256Secret.publicKey)
|
#expect(try P256.Signing.PublicKey(x963Representation: Constants.Secrets.ecdsa256Secret.publicKey)
|
||||||
.isValidSignature(signature, for: context.dataToSign))
|
.isValidSignature(signature, for: context.dataToSign.raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Witness protocol
|
// MARK: Witness protocol
|
||||||
@@ -79,7 +79,7 @@ import CertificateKit
|
|||||||
return true
|
return true
|
||||||
}, witness: { _, _ in })
|
}, witness: { _, _ in })
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness)
|
let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness)
|
||||||
let response = await agent.handle(request: .signRequest(.empty), provenance: .test)
|
let response = await agent.handle(request: .signRequest(.empty), provenance: .test, hosts: nil)
|
||||||
#expect(response == Constants.Responses.requestFailure)
|
#expect(response == Constants.Responses.requestFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ import CertificateKit
|
|||||||
})
|
})
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness)
|
let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness)
|
||||||
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
||||||
_ = await agent.handle(request: request, provenance: .test)
|
_ = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
#expect(witnessed)
|
#expect(witnessed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ import CertificateKit
|
|||||||
})
|
})
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness)
|
let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness)
|
||||||
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
||||||
_ = await agent.handle(request: request, provenance: .test)
|
_ = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
#expect(witnessTrace == speakNowTrace)
|
#expect(witnessTrace == speakNowTrace)
|
||||||
#expect(witnessTrace == .test)
|
#expect(witnessTrace == .test)
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ import CertificateKit
|
|||||||
store.shouldThrow = true
|
store.shouldThrow = true
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore())
|
let agent = Agent(storeList: list, certificateStore: CertificateStore())
|
||||||
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
||||||
let response = await agent.handle(request: request, provenance: .test)
|
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
#expect(response == Constants.Responses.requestFailure)
|
#expect(response == Constants.Responses.requestFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ import CertificateKit
|
|||||||
|
|
||||||
@Test func unhandledAdd() async throws {
|
@Test func unhandledAdd() async throws {
|
||||||
let agent = Agent(storeList: SecretStoreList(), certificateStore: CertificateStore())
|
let agent = Agent(storeList: SecretStoreList(), certificateStore: CertificateStore())
|
||||||
let response = await agent.handle(request: .addIdentity, provenance: .test)
|
let response = await agent.handle(request: .addIdentity, provenance: .test, hosts: nil)
|
||||||
#expect(response == Constants.Responses.requestFailure)
|
#expect(response == Constants.Responses.requestFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Foundation
|
|||||||
import SecretKit
|
import SecretKit
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
import SSHProtocolKit
|
import SSHProtocolKit
|
||||||
|
import LocalAuthentication
|
||||||
|
|
||||||
struct Stub {}
|
struct Stub {}
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ extension Stub {
|
|||||||
print("Public Key OpenSSH: \(OpenSSHPublicKeyWriter().openSSHString(secret: secret))")
|
print("Public Key OpenSSH: \(OpenSSHPublicKeyWriter().openSSHString(secret: secret))")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, context: AuthenticationContextProtocol?) throws -> Data {
|
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?, context: LAContext?) throws -> Data {
|
||||||
guard !shouldThrow else {
|
guard !shouldThrow else {
|
||||||
throw NSError(domain: "test", code: 0, userInfo: nil)
|
throw NSError(domain: "test", code: 0, userInfo: nil)
|
||||||
}
|
}
|
||||||
@@ -57,13 +58,6 @@ extension Stub {
|
|||||||
return try privateKey.signature(for: data).rawRepresentation
|
return try privateKey.signature(for: data).rawRepresentation
|
||||||
}
|
}
|
||||||
|
|
||||||
public func existingAuthenticationContextProtocol(secret: Stub.Secret) -> AuthenticationContextProtocol? {
|
|
||||||
nil
|
|
||||||
}
|
|
||||||
|
|
||||||
public func persistAuthentication(secret: Stub.Secret, forDuration duration: TimeInterval) throws {
|
|
||||||
}
|
|
||||||
|
|
||||||
public func reloadSecrets() {
|
public func reloadSecrets() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ struct StubWitness {
|
|||||||
|
|
||||||
extension StubWitness: SigningWitness {
|
extension StubWitness: SigningWitness {
|
||||||
|
|
||||||
func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) throws {
|
func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?) throws {
|
||||||
let objection = speakNow(secret, provenance)
|
let objection = speakNow(secret, provenance)
|
||||||
if objection {
|
if objection {
|
||||||
throw TheresMyChance()
|
throw TheresMyChance()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) throws {
|
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?) throws {
|
||||||
witness(secret, provenance)
|
witness(secret, provenance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,9 +54,10 @@ struct SecretAgent: App {
|
|||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let inputParser = try await XPCAgentInputParser()
|
let inputParser = try await XPCAgentInputParser()
|
||||||
|
let hosts = try? await XPCHostsfileReader().read()
|
||||||
for await message in session.messages {
|
for await message in session.messages {
|
||||||
let request = try await inputParser.parse(data: message)
|
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)
|
try session.write(agentResponse)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import Cocoa
|
||||||
|
import OSLog
|
||||||
|
import SecretKit
|
||||||
|
import SecureEnclaveSecretKit
|
||||||
|
import SmartCardSecretKit
|
||||||
|
import SecretAgentKit
|
||||||
|
import Brief
|
||||||
|
import Observation
|
||||||
|
import SSHProtocolKit
|
||||||
|
import CertificateKit
|
||||||
|
import Common
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension EnvironmentValues {
|
||||||
|
|
||||||
|
@MainActor fileprivate static let _certificateStore: CertificateStore = CertificateStore()
|
||||||
|
|
||||||
|
@MainActor var certificateStore: CertificateStore {
|
||||||
|
EnvironmentValues._certificateStore
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
@main
|
||||||
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
|
||||||
|
@MainActor private let storeList: SecretStoreList = {
|
||||||
|
let list = SecretStoreList()
|
||||||
|
let cryptoKit = SecureEnclave.Store()
|
||||||
|
let migrator = SecureEnclave.CryptoKitMigrator()
|
||||||
|
try? migrator.migrate(to: cryptoKit)
|
||||||
|
list.add(store: cryptoKit)
|
||||||
|
list.add(store: SmartCard.Store())
|
||||||
|
let certsMigrator = CertificateMigrator(homeDirectory: URL.homeDirectory, certificateStore: EnvironmentValues._certificateStore)
|
||||||
|
try? certsMigrator.migrate()
|
||||||
|
return list
|
||||||
|
}()
|
||||||
|
private let updater = Updater(checkOnLaunch: true)
|
||||||
|
private let notifier = Notifier()
|
||||||
|
private let publicKeyFileStoreController = PublicKeyFileStoreController(publicKeysURL: URL.publicKeyDirectory, certificatesURL: URL.certificatesDirectory)
|
||||||
|
@MainActor private lazy var agent: Agent = {
|
||||||
|
Agent(storeList: storeList, certificateStore: EnvironmentValues._certificateStore, witness: notifier)
|
||||||
|
}()
|
||||||
|
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 {
|
||||||
|
for await session in socketController.sessions {
|
||||||
|
Task {
|
||||||
|
do {
|
||||||
|
let inputParser = try await XPCAgentInputParser()
|
||||||
|
let hostsReader = try? await XPCHostsfileReader()
|
||||||
|
let hosts = try? await hostsReader?.read()
|
||||||
|
for await message in session.messages {
|
||||||
|
let request = try await inputParser.parse(data: message)
|
||||||
|
let agentResponse = await agent.handle(request: request, provenance: session.provenance, hosts: hosts)
|
||||||
|
try session.write(agentResponse)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
try? session.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task {
|
||||||
|
for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) {
|
||||||
|
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task {
|
||||||
|
for await _ in NotificationCenter.default.notifications(named: .certificateStoreReloaded) {
|
||||||
|
try? publicKeyFileStoreController.generateCertificates(for: EnvironmentValues._certificateStore.certificates, clear: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
||||||
|
try? publicKeyFileStoreController.generateCertificates(for: EnvironmentValues._certificateStore.certificates, clear: true)
|
||||||
|
notifier.prompt()
|
||||||
|
_ = withObservationTracking {
|
||||||
|
updater.update
|
||||||
|
} onChange: { [updater, notifier] in
|
||||||
|
Task {
|
||||||
|
guard !updater.currentVersion.isTestBuild else { return }
|
||||||
|
await notifier.notify(update: updater.update!) { release in
|
||||||
|
await updater.ignore(release: release)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -61,12 +61,19 @@ final class Notifier: Sendable {
|
|||||||
notificationCenter.requestAuthorization(options: .alert) { _, _ in }
|
notificationCenter.requestAuthorization(options: .alert) { _, _ in }
|
||||||
}
|
}
|
||||||
|
|
||||||
func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, offerPersistence: Bool) async {
|
func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?, offerPersistence: Bool) async {
|
||||||
await notificationDelegate.state.setPending(secret: secret, store: store)
|
await notificationDelegate.state.setPending(secret: secret, store: store)
|
||||||
let notificationCenter = UNUserNotificationCenter.current()
|
let notificationCenter = UNUserNotificationCenter.current()
|
||||||
let notificationContent = UNMutableNotificationContent()
|
let notificationContent = UNMutableNotificationContent()
|
||||||
notificationContent.title = String(localized: .signedNotificationTitle(appName: provenance.origin.displayName))
|
notificationContent.title = String(localized: .signedNotificationTitle(appName: provenance.origin.displayName))
|
||||||
notificationContent.subtitle = String(localized: .signedNotificationDescription(secretName: secret.name))
|
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.persistSecretIDKey] = secret.id.description
|
||||||
notificationContent.userInfo[Constants.persistStoreIDKey] = store.id.description
|
notificationContent.userInfo[Constants.persistStoreIDKey] = store.id.description
|
||||||
notificationContent.interruptionLevel = .timeSensitive
|
notificationContent.interruptionLevel = .timeSensitive
|
||||||
@@ -101,11 +108,16 @@ final class Notifier: Sendable {
|
|||||||
|
|
||||||
extension Notifier: SigningWitness {
|
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, offerPersistence: Bool) async throws {
|
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?, offerPersistence: Bool) async throws {
|
||||||
await notify(accessTo: secret, from: store, by: provenance, offerPersistence: offerPersistence)
|
await notify(accessTo: secret, from: store, by: provenance, target: target, offerPersistence: offerPersistence)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,25 @@
|
|||||||
|
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("Parsing hostsfile")
|
||||||
|
var result: [Data: String] = [:]
|
||||||
|
for try await line in URL(filePath: NSHomeDirectory().appending("/.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()
|
||||||
@@ -31,6 +31,12 @@
|
|||||||
504788F42E681F6900B4556F /* ToolConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F32E681F6900B4556F /* ToolConfigurationView.swift */; };
|
504788F42E681F6900B4556F /* ToolConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F32E681F6900B4556F /* ToolConfigurationView.swift */; };
|
||||||
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; };
|
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; };
|
||||||
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504789222E697DD300B4556F /* BoxBackgroundStyle.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 */; };
|
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
|
||||||
505F5EF22FA9635700C45824 /* CertificateKit in Frameworks */ = {isa = PBXBuildFile; productRef = 505F5EF12FA9635700C45824 /* CertificateKit */; };
|
505F5EF22FA9635700C45824 /* CertificateKit in Frameworks */ = {isa = PBXBuildFile; productRef = 505F5EF12FA9635700C45824 /* CertificateKit */; };
|
||||||
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
|
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
|
||||||
@@ -110,6 +116,13 @@
|
|||||||
remoteGlobalIDString = 501577BC2E6BC5B4004A37D0;
|
remoteGlobalIDString = 501577BC2E6BC5B4004A37D0;
|
||||||
remoteInfo = ReleasesDownloader;
|
remoteInfo = ReleasesDownloader;
|
||||||
};
|
};
|
||||||
|
505402A43034B787000C3356 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 5054028C3034B5E3000C3356;
|
||||||
|
remoteInfo = SecretAgentHostsfileReader;
|
||||||
|
};
|
||||||
50692D1B2E6FDB880043C7BB /* PBXContainerItemProxy */ = {
|
50692D1B2E6FDB880043C7BB /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
||||||
@@ -174,6 +187,7 @@
|
|||||||
dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
|
dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
|
||||||
dstSubfolderSpec = 16;
|
dstSubfolderSpec = 16;
|
||||||
files = (
|
files = (
|
||||||
|
505402A33034B787000C3356 /* SecretAgentHostsfileReader.xpc in Embed XPC Services */,
|
||||||
50692E6D2E6FFA5F0043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */,
|
50692E6D2E6FFA5F0043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */,
|
||||||
50E2052D2FAAB92000402380 /* SecretiveCertificateParser.xpc in Embed XPC Services */,
|
50E2052D2FAAB92000402380 /* SecretiveCertificateParser.xpc in Embed XPC Services */,
|
||||||
50692E702E6FFA6E0043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */,
|
50692E702E6FFA6E0043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */,
|
||||||
@@ -229,6 +243,12 @@
|
|||||||
504788F32E681F6900B4556F /* ToolConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolConfigurationView.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -290,6 +310,15 @@
|
|||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
5054028A3034B5E3000C3356 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
505402AC303594D1000C3356 /* SSHProtocolKit in Frameworks */,
|
||||||
|
505402A73034B7A4000C3356 /* XPCWrappers in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
50617D7C23FCE48D0099B055 /* Frameworks */ = {
|
50617D7C23FCE48D0099B055 /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -407,6 +436,17 @@
|
|||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
5054029F3034B692000C3356 /* SecretAgentHostsfileReader */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
505402A83034B7D2000C3356 /* SecretAgentHostsfileReader.entitlements */,
|
||||||
|
5054029C3034B692000C3356 /* Info.plist */,
|
||||||
|
5054029D3034B692000C3356 /* main.swift */,
|
||||||
|
5054029E3034B692000C3356 /* SecretAgentHostsfileReader.swift */,
|
||||||
|
);
|
||||||
|
path = SecretAgentHostsfileReader;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
50617D7623FCE48D0099B055 = {
|
50617D7623FCE48D0099B055 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -417,6 +457,7 @@
|
|||||||
50692D272E6FDB8D0043C7BB /* SecretiveUpdater */,
|
50692D272E6FDB8D0043C7BB /* SecretiveUpdater */,
|
||||||
50692E662E6FF9E20043C7BB /* SecretAgentInputParser */,
|
50692E662E6FF9E20043C7BB /* SecretAgentInputParser */,
|
||||||
50E205262FAAB82700402380 /* SecretiveCertificateParser */,
|
50E205262FAAB82700402380 /* SecretiveCertificateParser */,
|
||||||
|
5054029F3034B692000C3356 /* SecretAgentHostsfileReader */,
|
||||||
50617D8023FCE48E0099B055 /* Products */,
|
50617D8023FCE48E0099B055 /* Products */,
|
||||||
5099A08B240243730062B6F2 /* Frameworks */,
|
5099A08B240243730062B6F2 /* Frameworks */,
|
||||||
);
|
);
|
||||||
@@ -430,6 +471,7 @@
|
|||||||
50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */,
|
50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */,
|
||||||
50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */,
|
50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */,
|
||||||
50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */,
|
50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */,
|
||||||
|
5054028D3034B5E3000C3356 /* SecretAgentHostsfileReader.xpc */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -529,6 +571,7 @@
|
|||||||
5018F54E24064786002EB505 /* Notifier.swift */,
|
5018F54E24064786002EB505 /* Notifier.swift */,
|
||||||
501578122E6C0479004A37D0 /* XPCInputParser.swift */,
|
501578122E6C0479004A37D0 /* XPCInputParser.swift */,
|
||||||
503647472F870B7800977A23 /* BatchedRequestsView.swift */,
|
503647472F870B7800977A23 /* BatchedRequestsView.swift */,
|
||||||
|
505402A93034B80E000C3356 /* XPCHostsfileReader.swift */,
|
||||||
50E2057F2FAB291E00402380 /* CertificateMigrator.swift */,
|
50E2057F2FAB291E00402380 /* CertificateMigrator.swift */,
|
||||||
50A3B79524026B7600D209EA /* Main.storyboard */,
|
50A3B79524026B7600D209EA /* Main.storyboard */,
|
||||||
50A3B79824026B7600D209EA /* Info.plist */,
|
50A3B79824026B7600D209EA /* Info.plist */,
|
||||||
@@ -561,6 +604,27 @@
|
|||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget 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 */ = {
|
50617D7E23FCE48D0099B055 /* Secretive */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 50617D9D23FCE48E0099B055 /* Build configuration list for PBXNativeTarget "Secretive" */;
|
buildConfigurationList = 50617D9D23FCE48E0099B055 /* Build configuration list for PBXNativeTarget "Secretive" */;
|
||||||
@@ -655,6 +719,7 @@
|
|||||||
50692E6F2E6FFA5F0043C7BB /* PBXTargetDependency */,
|
50692E6F2E6FFA5F0043C7BB /* PBXTargetDependency */,
|
||||||
50692E722E6FFA6E0043C7BB /* PBXTargetDependency */,
|
50692E722E6FFA6E0043C7BB /* PBXTargetDependency */,
|
||||||
50E2052F2FAAB92000402380 /* PBXTargetDependency */,
|
50E2052F2FAAB92000402380 /* PBXTargetDependency */,
|
||||||
|
505402A53034B787000C3356 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = SecretAgent;
|
name = SecretAgent;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
@@ -699,10 +764,13 @@
|
|||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastSwiftUpdateCheck = 2650;
|
LastSwiftUpdateCheck = 2700;
|
||||||
LastUpgradeCheck = 2640;
|
LastUpgradeCheck = 2640;
|
||||||
ORGANIZATIONNAME = "Max Goedjen";
|
ORGANIZATIONNAME = "Max Goedjen";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
|
5054028C3034B5E3000C3356 = {
|
||||||
|
CreatedOnToolsVersion = 27.0;
|
||||||
|
};
|
||||||
50617D7E23FCE48D0099B055 = {
|
50617D7E23FCE48D0099B055 = {
|
||||||
CreatedOnToolsVersion = 11.3;
|
CreatedOnToolsVersion = 11.3;
|
||||||
};
|
};
|
||||||
@@ -744,13 +812,21 @@
|
|||||||
50617D7E23FCE48D0099B055 /* Secretive */,
|
50617D7E23FCE48D0099B055 /* Secretive */,
|
||||||
50A3B78924026B7500D209EA /* SecretAgent */,
|
50A3B78924026B7500D209EA /* SecretAgent */,
|
||||||
50692D112E6FDB880043C7BB /* SecretiveUpdater */,
|
50692D112E6FDB880043C7BB /* SecretiveUpdater */,
|
||||||
50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */,
|
|
||||||
50E205132FAAB81C00402380 /* SecretiveCertificateParser */,
|
50E205132FAAB81C00402380 /* SecretiveCertificateParser */,
|
||||||
|
50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */,
|
||||||
|
5054028C3034B5E3000C3356 /* SecretAgentHostsfileReader */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
5054028B3034B5E3000C3356 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
50617D7D23FCE48D0099B055 /* Resources */ = {
|
50617D7D23FCE48D0099B055 /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -799,6 +875,15 @@
|
|||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase 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 */ = {
|
50617D7B23FCE48D0099B055 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -868,6 +953,7 @@
|
|||||||
5018F54F24064786002EB505 /* Notifier.swift in Sources */,
|
5018F54F24064786002EB505 /* Notifier.swift in Sources */,
|
||||||
503647482F870B7800977A23 /* BatchedRequestsView.swift in Sources */,
|
503647482F870B7800977A23 /* BatchedRequestsView.swift in Sources */,
|
||||||
501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */,
|
501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */,
|
||||||
|
505402AA3034B80E000C3356 /* XPCHostsfileReader.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -892,6 +978,11 @@
|
|||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
targetProxy = 501577D32E6BC5DD004A37D0 /* PBXContainerItemProxy */;
|
targetProxy = 501577D32E6BC5DD004A37D0 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
505402A53034B787000C3356 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 5054028C3034B5E3000C3356 /* SecretAgentHostsfileReader */;
|
||||||
|
targetProxy = 505402A43034B787000C3356 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
50692D1C2E6FDB880043C7BB /* PBXTargetDependency */ = {
|
50692D1C2E6FDB880043C7BB /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = 50692D112E6FDB880043C7BB /* SecretiveUpdater */;
|
target = 50692D112E6FDB880043C7BB /* SecretiveUpdater */;
|
||||||
@@ -936,6 +1027,104 @@
|
|||||||
/* End PBXVariantGroup section */
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration 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 */ = {
|
50617D9B23FCE48E0099B055 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 508A58AB241E121B0069DC07 /* Config.xcconfig */;
|
baseConfigurationReference = 508A58AB241E121B0069DC07 /* Config.xcconfig */;
|
||||||
@@ -1515,6 +1704,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
AUTOMATION_APPLE_EVENTS = NO;
|
||||||
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
@@ -1534,6 +1724,7 @@
|
|||||||
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
|
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
|
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_USB = NO;
|
ENABLE_RESOURCE_ACCESS_USB = NO;
|
||||||
INFOPLIST_FILE = SecretAgent/Info.plist;
|
INFOPLIST_FILE = SecretAgent/Info.plist;
|
||||||
@@ -1545,6 +1736,12 @@
|
|||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
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;
|
||||||
};
|
};
|
||||||
name = Test;
|
name = Test;
|
||||||
};
|
};
|
||||||
@@ -1552,6 +1749,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
AUTOMATION_APPLE_EVENTS = NO;
|
||||||
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
@@ -1572,6 +1770,7 @@
|
|||||||
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
|
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
|
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_USB = NO;
|
ENABLE_RESOURCE_ACCESS_USB = NO;
|
||||||
INFOPLIST_FILE = SecretAgent/Info.plist;
|
INFOPLIST_FILE = SecretAgent/Info.plist;
|
||||||
@@ -1583,6 +1782,12 @@
|
|||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
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;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -1590,6 +1795,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
AUTOMATION_APPLE_EVENTS = NO;
|
||||||
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
@@ -1611,6 +1817,7 @@
|
|||||||
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
|
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
|
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_USB = NO;
|
ENABLE_RESOURCE_ACCESS_USB = NO;
|
||||||
INFOPLIST_FILE = SecretAgent/Info.plist;
|
INFOPLIST_FILE = SecretAgent/Info.plist;
|
||||||
@@ -1623,6 +1830,12 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent";
|
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;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -1637,6 +1850,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1668,6 +1882,7 @@
|
|||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1701,6 +1916,7 @@
|
|||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6;
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1726,6 +1942,16 @@
|
|||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList 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" */ = {
|
50617D7A23FCE48D0099B055 /* Build configuration list for PBXProject "Secretive" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
@@ -1829,6 +2055,14 @@
|
|||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = Brief;
|
productName = Brief;
|
||||||
};
|
};
|
||||||
|
505402A63034B7A4000C3356 /* XPCWrappers */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = XPCWrappers;
|
||||||
|
};
|
||||||
|
505402AB303594D1000C3356 /* SSHProtocolKit */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = SSHProtocolKit;
|
||||||
|
};
|
||||||
505F5EF12FA9635700C45824 /* CertificateKit */ = {
|
505F5EF12FA9635700C45824 /* CertificateKit */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = CertificateKit;
|
productName = CertificateKit;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ extension Preview {
|
|||||||
self.init(secrets: new)
|
self.init(secrets: new)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, context: LAContext?) async throws -> Data {
|
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?, context: LAContext?) async throws -> Data {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ extension Preview {
|
|||||||
self.init(secrets: new)
|
self.init(secrets: new)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, context: LAContext?) async throws -> Data {
|
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?, context: LAContext?) async throws -> Data {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,42 +46,42 @@ struct CertificateDetailView: View {
|
|||||||
text: URL.certificatePath(for: certificate.id, in: URL.certificatesDirectory),
|
text: URL.certificatePath(for: certificate.id, in: URL.certificatesDirectory),
|
||||||
showRevealInFinder: true
|
showRevealInFinder: true
|
||||||
)
|
)
|
||||||
if let validityRange = certificate.validityRange {
|
// if let validityRange = certificate.validityRange {
|
||||||
let epoch = Date(timeIntervalSince1970: 0)
|
// let epoch = Date(timeIntervalSince1970: 0)
|
||||||
let end = Date(timeIntervalSince1970: TimeInterval(UInt64.max))
|
// let end = Date(timeIntervalSince1970: TimeInterval(UInt64.max))
|
||||||
switch (validityRange.lowerBound, validityRange.upperBound) {
|
// switch (validityRange.lowerBound, validityRange.upperBound) {
|
||||||
case (epoch, end):
|
// case (epoch, end):
|
||||||
EmptyView()
|
// EmptyView()
|
||||||
case (epoch, let otherEnd):
|
// case (epoch, let otherEnd):
|
||||||
Spacer()
|
// Spacer()
|
||||||
.frame(height: 20)
|
// .frame(height: 20)
|
||||||
MultilineInfoView(title: .certificateDetailValidUntilLabel, image: Image(systemName: "calendar.badge.clock"), items: [otherEnd.formatted()])
|
// MultilineInfoView(title: .certificateDetailValidUntilLabel, image: Image(systemName: "calendar.badge.clock"), items: [otherEnd.formatted()])
|
||||||
case (let otherStart, end):
|
// case (let otherStart, end):
|
||||||
Spacer()
|
// Spacer()
|
||||||
.frame(height: 20)
|
// .frame(height: 20)
|
||||||
MultilineInfoView(title: .certificateDetailValidAfterLabel, image: Image(systemName: "calendar.badge.clock"), items: [otherStart.formatted()])
|
// MultilineInfoView(title: .certificateDetailValidAfterLabel, image: Image(systemName: "calendar.badge.clock"), items: [otherStart.formatted()])
|
||||||
default:
|
// default:
|
||||||
Spacer()
|
// Spacer()
|
||||||
.frame(height: 20)
|
// .frame(height: 20)
|
||||||
MultilineInfoView(title: .certificateDetailValidityRangeLabel, image: Image(systemName: "calendar.badge.clock"), items: [validityRange.formatted()])
|
// MultilineInfoView(title: .certificateDetailValidityRangeLabel, image: Image(systemName: "calendar.badge.clock"), items: [validityRange.formatted()])
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if !certificate.principals.isEmpty {
|
// if !certificate.principals.isEmpty {
|
||||||
Spacer()
|
// Spacer()
|
||||||
.frame(height: 20)
|
// .frame(height: 20)
|
||||||
MultilineInfoView(title: .certificateDetailPrincipalsLabel, image: Image(systemName: "person.2"), items: certificate.principals)
|
// MultilineInfoView(title: .certificateDetailPrincipalsLabel, image: Image(systemName: "person.2"), items: certificate.principals)
|
||||||
}
|
// }
|
||||||
if !certificate.criticalOptions.isEmpty {
|
// if !certificate.criticalOptions.isEmpty {
|
||||||
Spacer()
|
// Spacer()
|
||||||
.frame(height: 20)
|
// .frame(height: 20)
|
||||||
MultilineInfoView(title: .certificateDetailCriticalOptionsLabel, image: Image(systemName: "person.2"), items: certificate.criticalOptions)
|
// MultilineInfoView(title: .certificateDetailCriticalOptionsLabel, image: Image(systemName: "person.2"), items: certificate.criticalOptions)
|
||||||
}
|
// }
|
||||||
if !certificate.extensions.isEmpty {
|
// if !certificate.extensions.isEmpty {
|
||||||
Spacer()
|
// Spacer()
|
||||||
.frame(height: 20)
|
// .frame(height: 20)
|
||||||
MultilineInfoView(title: .certificateDetailExtensionsLabel, image: Image(systemName: "person.2"), items: certificate.extensions)
|
// MultilineInfoView(title: .certificateDetailExtensionsLabel, image: Image(systemName: "person.2"), items: certificate.extensions)
|
||||||
}
|
// }
|
||||||
Spacer()
|
// Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
|||||||
@@ -43,22 +43,22 @@ struct SecretDetailView<SecretType: Secret>: View {
|
|||||||
text: URL.publicKeyPath(for: secret, in: URL.publicKeyDirectory),
|
text: URL.publicKeyPath(for: secret, in: URL.publicKeyDirectory),
|
||||||
showRevealInFinder: true
|
showRevealInFinder: true
|
||||||
)
|
)
|
||||||
if !certificates.isEmpty {
|
// if !certificates.isEmpty {
|
||||||
Spacer()
|
// Spacer()
|
||||||
.frame(height: 20)
|
// .frame(height: 20)
|
||||||
MultilineInfoView(
|
// MultilineInfoView(
|
||||||
title: .secretDetailCertificatePathLabel,
|
// title: .secretDetailCertificatePathLabel,
|
||||||
image: Image(
|
// image: Image(
|
||||||
systemName: "checkmark.seal.text.page"
|
// systemName: "checkmark.seal.text.page"
|
||||||
),
|
// ),
|
||||||
items: certificates.map({ certificate in
|
// items: certificates.map({ certificate in
|
||||||
MultilineInfoView.Item(
|
// MultilineInfoView.Item(
|
||||||
text: certificate.name,
|
// text: certificate.name,
|
||||||
action: (Image(systemName: "chevron.forward"), { navigateToCertificate?(certificate) })
|
// action: (Image(systemName: "chevron.forward"), { navigateToCertificate?(certificate) })
|
||||||
)
|
// )
|
||||||
})
|
// })
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,11 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.hardened-process.dyld-ro</key>
|
<key>com.apple.security.hardened-process.dyld-ro</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.hardened-process.enhanced-security-version</key>
|
<key>com.apple.security.hardened-process.enhanced-security-version-string</key>
|
||||||
<integer>1</integer>
|
<string>2</string>
|
||||||
<key>com.apple.security.hardened-process.hardened-heap</key>
|
<key>com.apple.security.hardened-process.hardened-heap</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.hardened-process.platform-restrictions</key>
|
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
|
||||||
<integer>2</integer>
|
<string>2</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Reference in New Issue
Block a user