mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-04-10 03:07:22 +02:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d16a6ffb1 | ||
|
|
95658072e7 | ||
|
|
7eeb09b1ec | ||
|
|
e6f21c13b0 | ||
|
|
96ef91df0c | ||
|
|
aa46d8fa48 | ||
|
|
cf7c6e9fbe | ||
|
|
7c7db56c1e | ||
|
|
cd12e4c828 | ||
|
|
a5b43ea046 | ||
|
|
8c516e128a | ||
|
|
6854c05763 | ||
|
|
5467474d88 | ||
|
|
5c2d039682 | ||
|
|
20e64604d6 | ||
|
|
c5a610d786 | ||
|
|
7d21e3983c | ||
|
|
2c38aaed6f | ||
|
|
63b42bd9df | ||
|
|
cf5ae49ebc | ||
|
|
61705af42f | ||
|
|
558ae15b2d | ||
|
|
902d5c4a1e | ||
|
|
e0c24917f2 | ||
|
|
3d5f0b45bd | ||
|
|
74f4f1c0b1 |
47
.github/workflows/codeql.yml
vendored
Normal file
47
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: "CodeQL Advanced"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
schedule:
|
||||||
|
- cron: '26 15 * * 3'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze (${{ matrix.language }})
|
||||||
|
runs-on: ${{ (matrix.language == 'swift' && 'macos-26') || 'ubuntu-latest' }}
|
||||||
|
permissions:
|
||||||
|
security-events: write
|
||||||
|
packages: read
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- language: actions
|
||||||
|
build-mode: none
|
||||||
|
# Disable this until CodeQL supports Xcode 26 builds.
|
||||||
|
# - language: swift
|
||||||
|
# build-mode: manual
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v3
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
build-mode: ${{ matrix.build-mode }}
|
||||||
|
- if: matrix.build-mode == 'manual'
|
||||||
|
name: "Select Xcode"
|
||||||
|
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
|
||||||
|
- if: matrix.build-mode == 'manual'
|
||||||
|
name: "Build"
|
||||||
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v3
|
||||||
|
with:
|
||||||
|
category: "/language:${{matrix.language}}"
|
||||||
6
.github/workflows/nightly.yml
vendored
6
.github/workflows/nightly.yml
vendored
@@ -7,8 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
# runs-on: macOS-latest
|
runs-on: macos-26
|
||||||
runs-on: macos-15
|
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
contents: write
|
contents: write
|
||||||
@@ -31,7 +30,8 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
RUN_ID: ${{ github.run_id }}
|
RUN_ID: ${{ github.run_id }}
|
||||||
run: |
|
run: |
|
||||||
sed -i '' -e "s/GITHUB_CI_VERSION/0.0.0/g" Sources/Config/Config.xcconfig
|
DATE=$(date "+%Y-%m-%d")
|
||||||
|
sed -i '' -e "s/GITHUB_CI_VERSION/0.0.0_nightly-$DATE/g" Sources/Config/Config.xcconfig
|
||||||
sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig
|
sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig
|
||||||
sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Secretive/Credits.rtf
|
sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Secretive/Credits.rtf
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|||||||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -6,8 +6,9 @@ on:
|
|||||||
- '*'
|
- '*'
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
# runs-on: macOS-latest
|
permissions:
|
||||||
runs-on: macos-15
|
contents: read
|
||||||
|
runs-on: macos-26
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
@@ -23,14 +24,15 @@ jobs:
|
|||||||
- name: Set Environment
|
- name: Set Environment
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
|
||||||
- name: Test
|
- name: Test
|
||||||
run: swift test --build-system swiftbuild --package-path Sources/Packages
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test
|
||||||
|
# SPM doesn't seem to pick up on the tests currently?
|
||||||
|
# run: swift test --build-system swiftbuild --package-path Sources/Packages
|
||||||
build:
|
build:
|
||||||
# runs-on: macOS-latest
|
|
||||||
runs-on: macos-15
|
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
contents: write
|
contents: write
|
||||||
attestations: write
|
attestations: write
|
||||||
|
runs-on: macos-26
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
|
|||||||
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
@@ -3,14 +3,17 @@ name: Test
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
# runs-on: macOS-latest
|
permissions:
|
||||||
runs-on: macos-15
|
contents: read
|
||||||
|
runs-on: macos-26
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- name: Set Environment
|
- name: Set Environment
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
|
||||||
- name: Test Main Packages
|
- name: Test Main Packages
|
||||||
run: swift test --build-system swiftbuild --package-path Sources/Packages
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test
|
||||||
|
# SPM doesn't seem to pick up on the tests currently?
|
||||||
|
# run: swift test --build-system swiftbuild --package-path Sources/Packages
|
||||||
- name: Test SecretKit Packages
|
- name: Test SecretKit Packages
|
||||||
run: swift test --build-system swiftbuild
|
run: swift test --build-system swiftbuild
|
||||||
|
|||||||
@@ -2,36 +2,35 @@
|
|||||||
|
|
||||||
If you speak another language, and would like to help translate Secretive to support that language, we'd love your help!
|
If you speak another language, and would like to help translate Secretive to support that language, we'd love your help!
|
||||||
|
|
||||||
## Getting Started
|
## Crowdin
|
||||||
|
|
||||||
### Download Xcode
|
[Secretive uses Crowdin for localization](https://crowdin.com/project/secretive/). Open the link and select your language to translate!
|
||||||
|
|
||||||
Download the latest version of Xcode (at minimum, Xcode 15) from [Apple](http://developer.apple.com/download/applications/).
|
### Manual Translation
|
||||||
|
|
||||||
### Clone Secretive
|
Crowdin is the easiest way to translate Secretive, but I'm happy to accept Pull Requests directly as well.
|
||||||
|
|
||||||
Clone Secretive using [these instructions from GitHub](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository).
|
|
||||||
|
|
||||||
### Open Secretive
|
|
||||||
|
|
||||||
Open [Sources/Secretive.xcodeproj](Sources/Secretive.xcodeproj) in Xcode.
|
|
||||||
|
|
||||||
### Translate
|
|
||||||
|
|
||||||
Navigate to [Secretive/Localizable](Sources/Secretive/Localizable.xcstrings).
|
|
||||||
|
|
||||||
<img src="/.github/readme/localize_sidebar.png" alt="Screenshot of Xcode navigating to the Localizable file" width="300">
|
|
||||||
|
|
||||||
If your language already has an in-progress localization, select it from the list. If it isn't there, hit the "+" button and choose your language from the list.
|
|
||||||
|
|
||||||
<img src="/.github/readme/localize_add.png" alt="Screenshot of Xcode adding a new language" width="600">
|
|
||||||
|
|
||||||
Start translating! You'll see a list of english phrases, and a space to add a translation of your language.
|
|
||||||
|
|
||||||
### Create a Pull Request
|
|
||||||
|
|
||||||
Push your changes and open a pull request.
|
|
||||||
|
|
||||||
### Questions
|
### Questions
|
||||||
|
|
||||||
Please open an issue if you have a question about translating the app. I'm more than happy to clarify any terms that are ambiguous or confusing. Thanks for contributing!
|
Please open an issue if you have a question about translating the app. I'm more than happy to clarify any terms that are ambiguous or confusing. Thanks for contributing!
|
||||||
|
|
||||||
|
### Thank You
|
||||||
|
|
||||||
|
Thanks to all the folks who have contributed localizations so far!
|
||||||
|
|
||||||
|
- @mtardy for the French localization
|
||||||
|
- @GravityRyu for the Chinese localization
|
||||||
|
- @Saeger for the Portuguese (Brazil) localization
|
||||||
|
- @moritzsternemann for the German localization
|
||||||
|
- @RoboRich00A16 for the Italian localization
|
||||||
|
- @akx for the Finnish localization
|
||||||
|
- @mog422 for the Korean localization
|
||||||
|
- @niw for the Japanese localization
|
||||||
|
- @truita for the Catalan localization
|
||||||
|
- @Adimac93 for the Polish localization
|
||||||
|
- @alongotv for the Russian localization
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
A special thanks to [Crowdin](https://crowdin.com) for their [generous support of open source projects](https://crowdin.com/page/open-source-project-setup-request).
|
||||||
|
|||||||
@@ -13,12 +13,24 @@
|
|||||||
},
|
},
|
||||||
"testTargets" : [
|
"testTargets" : [
|
||||||
{
|
{
|
||||||
"enabled" : false,
|
|
||||||
"parallelizable" : true,
|
|
||||||
"target" : {
|
"target" : {
|
||||||
"containerPath" : "container:Secretive.xcodeproj",
|
"containerPath" : "container:Packages",
|
||||||
"identifier" : "50617D9323FCE48E0099B055",
|
"identifier" : "BriefTests",
|
||||||
"name" : "SecretiveTests"
|
"name" : "BriefTests"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target" : {
|
||||||
|
"containerPath" : "container:Packages",
|
||||||
|
"identifier" : "SecretKitTests",
|
||||||
|
"name" : "SecretKitTests"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target" : {
|
||||||
|
"containerPath" : "container:Packages",
|
||||||
|
"identifier" : "SecretAgentKitTests",
|
||||||
|
"name" : "SecretAgentKitTests"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ let package = Package(
|
|||||||
targets: ["SmartCardSecretKit"]),
|
targets: ["SmartCardSecretKit"]),
|
||||||
.library(
|
.library(
|
||||||
name: "SecretAgentKit",
|
name: "SecretAgentKit",
|
||||||
targets: ["SecretAgentKit"]),
|
targets: ["SecretAgentKit", "XPCWrappers"]),
|
||||||
.library(
|
|
||||||
name: "SecretAgentKitHeaders",
|
|
||||||
targets: ["SecretAgentKitHeaders"]),
|
|
||||||
.library(
|
.library(
|
||||||
name: "Brief",
|
name: "Brief",
|
||||||
targets: ["Brief"]),
|
targets: ["Brief"]),
|
||||||
|
.library(
|
||||||
|
name: "XPCWrappers",
|
||||||
|
targets: ["XPCWrappers"]),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
],
|
],
|
||||||
@@ -57,20 +57,17 @@ let package = Package(
|
|||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "SecretAgentKit",
|
name: "SecretAgentKit",
|
||||||
dependencies: ["SecretKit", "SecretAgentKitHeaders"],
|
dependencies: ["SecretKit"],
|
||||||
resources: [localization],
|
resources: [localization],
|
||||||
swiftSettings: swiftSettings,
|
swiftSettings: swiftSettings,
|
||||||
),
|
),
|
||||||
.systemLibrary(
|
|
||||||
name: "SecretAgentKitHeaders",
|
|
||||||
),
|
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "SecretAgentKitTests",
|
name: "SecretAgentKitTests",
|
||||||
dependencies: ["SecretAgentKit"],
|
dependencies: ["SecretAgentKit"],
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "Brief",
|
name: "Brief",
|
||||||
dependencies: [],
|
dependencies: ["XPCWrappers"],
|
||||||
resources: [localization],
|
resources: [localization],
|
||||||
swiftSettings: swiftSettings,
|
swiftSettings: swiftSettings,
|
||||||
),
|
),
|
||||||
@@ -78,6 +75,10 @@ let package = Package(
|
|||||||
name: "BriefTests",
|
name: "BriefTests",
|
||||||
dependencies: ["Brief"],
|
dependencies: ["Brief"],
|
||||||
),
|
),
|
||||||
|
.target(
|
||||||
|
name: "XPCWrappers",
|
||||||
|
swiftSettings: swiftSettings,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -89,5 +90,6 @@ var swiftSettings: [PackageDescription.SwiftSetting] {
|
|||||||
[
|
[
|
||||||
.swiftLanguageMode(.v6),
|
.swiftLanguageMode(.v6),
|
||||||
.treatAllWarnings(as: .error),
|
.treatAllWarnings(as: .error),
|
||||||
|
.strictMemorySafety()
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,12 +5,20 @@ public struct SemVer: Sendable {
|
|||||||
|
|
||||||
/// The SemVer broken into an array of integers.
|
/// The SemVer broken into an array of integers.
|
||||||
let versionNumbers: [Int]
|
let versionNumbers: [Int]
|
||||||
|
public let previewDescription: String?
|
||||||
|
|
||||||
|
public var isTestBuild: Bool {
|
||||||
|
versionNumbers == [0, 0, 0]
|
||||||
|
}
|
||||||
|
|
||||||
/// Initializes a SemVer from a string representation.
|
/// Initializes a SemVer from a string representation.
|
||||||
/// - Parameter version: A string representation of the SemVer, formatted as "major.minor.patch".
|
/// - Parameter version: A string representation of the SemVer, formatted as "major.minor.patch".
|
||||||
public init(_ version: String) {
|
public init(_ version: String) {
|
||||||
// Betas have the format 1.2.3_beta1
|
// Betas have the format 1.2.3_beta1
|
||||||
let strippedBeta = version.split(separator: "_").first!
|
// Nightlies have the format 0.0.0_nightly-2025-09-03
|
||||||
|
let splitFull = version.split(separator: "_")
|
||||||
|
let strippedBeta = splitFull.first!
|
||||||
|
previewDescription = splitFull.count > 1 ? String(splitFull[1]) : nil
|
||||||
var split = strippedBeta.split(separator: ".").compactMap { Int($0) }
|
var split = strippedBeta.split(separator: ".").compactMap { Int($0) }
|
||||||
while split.count < 3 {
|
while split.count < 3 {
|
||||||
split.append(0)
|
split.append(0)
|
||||||
@@ -22,6 +30,7 @@ public struct SemVer: Sendable {
|
|||||||
/// - Parameter version: An `OperatingSystemVersion` representation of the SemVer.
|
/// - Parameter version: An `OperatingSystemVersion` representation of the SemVer.
|
||||||
public init(_ version: OperatingSystemVersion) {
|
public init(_ version: OperatingSystemVersion) {
|
||||||
versionNumbers = [version.majorVersion, version.minorVersion, version.patchVersion]
|
versionNumbers = [version.majorVersion, version.minorVersion, version.patchVersion]
|
||||||
|
previewDescription = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Observation
|
import Observation
|
||||||
|
import XPCWrappers
|
||||||
|
|
||||||
/// A concrete implementation of ``UpdaterProtocol`` which considers the current release and OS version.
|
/// A concrete implementation of ``UpdaterProtocol`` which considers the current release and OS version.
|
||||||
@Observable public final class Updater: UpdaterProtocol, Sendable {
|
@Observable public final class Updater: UpdaterProtocol, Sendable {
|
||||||
@@ -13,12 +14,11 @@ import Observation
|
|||||||
state.update
|
state.update
|
||||||
}
|
}
|
||||||
|
|
||||||
public let testBuild: Bool
|
/// The current version of the app that is running.
|
||||||
|
public let currentVersion: SemVer
|
||||||
|
|
||||||
/// The current OS version.
|
/// The current OS version.
|
||||||
private let osVersion: SemVer
|
private let osVersion: SemVer
|
||||||
/// The current version of the app that is running.
|
|
||||||
private let currentVersion: SemVer
|
|
||||||
|
|
||||||
/// Initializes an Updater.
|
/// Initializes an Updater.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
@@ -34,28 +34,25 @@ import Observation
|
|||||||
) {
|
) {
|
||||||
self.osVersion = osVersion
|
self.osVersion = osVersion
|
||||||
self.currentVersion = currentVersion
|
self.currentVersion = currentVersion
|
||||||
testBuild = currentVersion == SemVer("0.0.0")
|
Task {
|
||||||
if checkOnLaunch {
|
if checkOnLaunch {
|
||||||
// Don't do a launch check if the user hasn't seen the setup prompt explaining updater yet.
|
try await checkForUpdates()
|
||||||
Task {
|
|
||||||
await checkForUpdates()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Task {
|
|
||||||
while !Task.isCancelled {
|
while !Task.isCancelled {
|
||||||
try? await Task.sleep(for: .seconds(Int(checkFrequency)))
|
try? await Task.sleep(for: .seconds(Int(checkFrequency)))
|
||||||
await checkForUpdates()
|
try await checkForUpdates()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manually trigger an update check.
|
/// Manually trigger an update check.
|
||||||
public func checkForUpdates() async {
|
public func checkForUpdates() async throws {
|
||||||
guard let (data, _) = try? await URLSession.shared.data(from: Constants.updateURL) else { return }
|
let session = try await XPCTypedSession<[Release], Never>(serviceName: "com.maxgoedjen.Secretive.SecretiveUpdater")
|
||||||
guard let releases = try? JSONDecoder().decode([Release].self, from: data) else { return }
|
await evaluate(releases: try await session.send())
|
||||||
await evaluate(releases: releases)
|
session.complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Ignores a specified release. `update` will be nil if the user has ignored the latest available release.
|
/// Ignores a specified release. `update` will be nil if the user has ignored the latest available release.
|
||||||
/// - Parameter release: The release to ignore.
|
/// - Parameter release: The release to ignore.
|
||||||
public func ignore(release: Release) async {
|
public func ignore(release: Release) async {
|
||||||
@@ -102,11 +99,3 @@ extension Updater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Updater {
|
|
||||||
|
|
||||||
enum Constants {
|
|
||||||
static let updateURL = URL(string: "https://api.github.com/repos/maxgoedjen/secretive/releases")!
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ public protocol UpdaterProtocol: Observable, Sendable {
|
|||||||
|
|
||||||
/// The latest update
|
/// The latest update
|
||||||
@MainActor var update: Release? { get }
|
@MainActor var update: Release? { get }
|
||||||
/// A boolean describing whether or not the current build of the app is a "test" build (ie, a debug build or otherwise special build)
|
|
||||||
var testBuild: Bool { get }
|
var currentVersion: SemVer { get }
|
||||||
|
|
||||||
func ignore(release: Release) async
|
func ignore(release: Release) async
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,46 +31,29 @@ public final class Agent: Sendable {
|
|||||||
|
|
||||||
extension Agent {
|
extension Agent {
|
||||||
|
|
||||||
/// Handles an incoming request.
|
public func handle(request: SSHAgent.Request, provenance: SigningRequestProvenance) async -> Data {
|
||||||
/// - Parameters:
|
|
||||||
/// - data: The data to handle.
|
|
||||||
/// - provenance: The origin of the request.
|
|
||||||
/// - Returns: A response data payload.
|
|
||||||
public func handle(data: Data, provenance: SigningRequestProvenance) async throws -> Data {
|
|
||||||
logger.debug("Agent handling new data")
|
|
||||||
guard data.count > 4 else {
|
|
||||||
throw InvalidDataProvidedError()
|
|
||||||
}
|
|
||||||
let requestTypeInt = data[4]
|
|
||||||
guard let requestType = SSHAgent.RequestType(rawValue: requestTypeInt) else {
|
|
||||||
logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription)")
|
|
||||||
return SSHAgent.ResponseType.agentFailure.data.lengthAndData
|
|
||||||
}
|
|
||||||
logger.debug("Agent handling request of type \(requestType.debugDescription)")
|
|
||||||
let subData = Data(data[5...])
|
|
||||||
let response = await handle(requestType: requestType, data: subData, provenance: provenance)
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handle(requestType: SSHAgent.RequestType, data: Data, provenance: SigningRequestProvenance) async -> Data {
|
|
||||||
// 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()
|
||||||
do {
|
do {
|
||||||
switch requestType {
|
switch request {
|
||||||
case .requestIdentities:
|
case .requestIdentities:
|
||||||
response.append(SSHAgent.ResponseType.agentIdentitiesAnswer.data)
|
response.append(SSHAgent.Response.agentIdentitiesAnswer.data)
|
||||||
response.append(await identities())
|
response.append(await identities())
|
||||||
logger.debug("Agent returned \(SSHAgent.ResponseType.agentIdentitiesAnswer.debugDescription)")
|
logger.debug("Agent returned \(SSHAgent.Response.agentIdentitiesAnswer.debugDescription)")
|
||||||
case .signRequest:
|
case .signRequest(let context):
|
||||||
response.append(SSHAgent.ResponseType.agentSignResponse.data)
|
response.append(SSHAgent.Response.agentSignResponse.data)
|
||||||
response.append(try await sign(data: data, provenance: provenance))
|
response.append(try await sign(data: context.dataToSign, keyBlob: context.keyBlob, provenance: provenance))
|
||||||
logger.debug("Agent returned \(SSHAgent.ResponseType.agentSignResponse.debugDescription)")
|
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
|
||||||
|
case .unknown(let value):
|
||||||
|
logger.error("Agent received unknown request of type \(value).")
|
||||||
|
default:
|
||||||
|
logger.debug("Agent received valid request of type \(request.debugDescription), but not currently supported.")
|
||||||
|
throw UnhandledRequestError()
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
response.removeAll()
|
response = SSHAgent.Response.agentFailure.data
|
||||||
response.append(SSHAgent.ResponseType.agentFailure.data)
|
logger.debug("Agent returned \(SSHAgent.Response.agentFailure.debugDescription)")
|
||||||
logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription)")
|
|
||||||
}
|
}
|
||||||
return response.lengthAndData
|
return response.lengthAndData
|
||||||
}
|
}
|
||||||
@@ -101,7 +84,7 @@ extension Agent {
|
|||||||
}
|
}
|
||||||
logger.log("Agent enumerated \(count) identities")
|
logger.log("Agent enumerated \(count) identities")
|
||||||
var countBigEndian = UInt32(count).bigEndian
|
var countBigEndian = UInt32(count).bigEndian
|
||||||
let countData = Data(bytes: &countBigEndian, count: UInt32.bitWidth/8)
|
let countData = unsafe Data(bytes: &countBigEndian, count: MemoryLayout<UInt32>.size)
|
||||||
return countData + keyData
|
return countData + keyData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,27 +93,16 @@ 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, provenance: SigningRequestProvenance) async throws -> Data {
|
func sign(data: Data, keyBlob: Data, provenance: SigningRequestProvenance) async throws -> Data {
|
||||||
let reader = OpenSSHReader(data: data)
|
guard let (secret, store) = await secret(matching: keyBlob) else {
|
||||||
let payloadHash = reader.readNextChunk()
|
let keyBlobHex = keyBlob.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }.joined()
|
||||||
let hash: Data
|
logger.debug("Agent did not have a key matching \(keyBlobHex)")
|
||||||
|
|
||||||
// Check if hash is actually an openssh certificate and reconstruct the public key if it is
|
|
||||||
if let certificatePublicKey = await certificateHandler.publicKeyHash(from: payloadHash) {
|
|
||||||
hash = certificatePublicKey
|
|
||||||
} else {
|
|
||||||
hash = payloadHash
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let (secret, store) = await secret(matching: hash) else {
|
|
||||||
logger.debug("Agent did not have a key matching \(hash as NSData)")
|
|
||||||
throw NoMatchingKeyError()
|
throw NoMatchingKeyError()
|
||||||
}
|
}
|
||||||
|
|
||||||
try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance)
|
try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance)
|
||||||
|
|
||||||
let dataToSign = reader.readNextChunk()
|
let rawRepresentation = try await store.sign(data: data, with: secret, for: provenance)
|
||||||
let rawRepresentation = try await store.sign(data: dataToSign, with: secret, for: provenance)
|
|
||||||
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)
|
try await witness?.witness(accessTo: secret, from: store, by: provenance)
|
||||||
@@ -169,16 +141,16 @@ extension Agent {
|
|||||||
|
|
||||||
extension Agent {
|
extension Agent {
|
||||||
|
|
||||||
struct InvalidDataProvidedError: Error {}
|
|
||||||
struct NoMatchingKeyError: Error {}
|
struct NoMatchingKeyError: Error {}
|
||||||
|
struct UnhandledRequestError: Error {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SSHAgent.ResponseType {
|
extension SSHAgent.Response {
|
||||||
|
|
||||||
var data: Data {
|
var data: Data {
|
||||||
var raw = self.rawValue
|
var raw = self.rawValue
|
||||||
return Data(bytes: &raw, count: UInt8.bitWidth/8)
|
return unsafe Data(bytes: &raw, count: MemoryLayout<UInt8>.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,12 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// Protocol abstraction of the reading aspects of FileHandle.
|
extension FileHandle {
|
||||||
public protocol FileHandleReader: Sendable {
|
|
||||||
|
|
||||||
/// Gets data that is available for reading.
|
|
||||||
var availableData: Data { get }
|
|
||||||
/// A file descriptor of the handle.
|
|
||||||
var fileDescriptor: Int32 { get }
|
|
||||||
/// The process ID of the process coonnected to the other end of the FileHandle.
|
|
||||||
var pidOfConnectedProcess: Int32 { get }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Protocol abstraction of the writing aspects of FileHandle.
|
|
||||||
public protocol FileHandleWriter: Sendable {
|
|
||||||
|
|
||||||
/// Writes data to the handle.
|
|
||||||
func write(_ data: Data)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension FileHandle: FileHandleReader, FileHandleWriter {
|
|
||||||
|
|
||||||
public var pidOfConnectedProcess: Int32 {
|
public var pidOfConnectedProcess: Int32 {
|
||||||
let pidPointer = UnsafeMutableRawPointer.allocate(byteCount: 4, alignment: 1)
|
let pidPointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout<Int32>.size, alignment: 1)
|
||||||
var len = socklen_t(MemoryLayout<Int32>.size)
|
var len = socklen_t(MemoryLayout<Int32>.size)
|
||||||
getsockopt(fileDescriptor, SOCK_STREAM, LOCAL_PEERPID, pidPointer, &len)
|
unsafe getsockopt(fileDescriptor, SOCK_STREAM, LOCAL_PEERPID, pidPointer, &len)
|
||||||
return pidPointer.load(as: Int32.self)
|
return unsafe pidPointer.load(as: Int32.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import OSLog
|
import OSLog
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
/// Manages storage and lookup for OpenSSH certificates.
|
/// Manages storage and lookup for OpenSSH certificates.
|
||||||
public actor OpenSSHCertificateHandler: Sendable {
|
public actor OpenSSHCertificateHandler: Sendable {
|
||||||
@@ -25,29 +26,6 @@ public actor OpenSSHCertificateHandler: Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reconstructs a public key from a ``Data``, if that ``Data`` contains an OpenSSH certificate hash. Currently only ecdsa certificates are supported
|
|
||||||
/// - Parameter certBlock: The openssh certificate to extract the public key from
|
|
||||||
/// - Returns: A ``Data`` object containing the public key in OpenSSH wire format if the ``Data`` is an OpenSSH certificate hash, otherwise nil.
|
|
||||||
public func publicKeyHash(from hash: Data) -> Data? {
|
|
||||||
let reader = OpenSSHReader(data: hash)
|
|
||||||
let certType = String(decoding: reader.readNextChunk(), as: UTF8.self)
|
|
||||||
switch certType {
|
|
||||||
case "ecdsa-sha2-nistp256-cert-v01@openssh.com",
|
|
||||||
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
|
|
||||||
"ecdsa-sha2-nistp521-cert-v01@openssh.com":
|
|
||||||
_ = reader.readNextChunk() // nonce
|
|
||||||
let curveIdentifier = reader.readNextChunk()
|
|
||||||
let publicKey = reader.readNextChunk()
|
|
||||||
|
|
||||||
let openSSHIdentifier = certType.replacingOccurrences(of: "-cert-v01@openssh.com", with: "")
|
|
||||||
return openSSHIdentifier.lengthAndData +
|
|
||||||
curveIdentifier.lengthAndData +
|
|
||||||
publicKey.lengthAndData
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempts to find an OpenSSH Certificate that corresponds to a ``Secret``
|
/// Attempts to find an OpenSSH Certificate that corresponds to a ``Secret``
|
||||||
/// - Parameter secret: The secret to search for a certificate with
|
/// - Parameter secret: The secret to search for a certificate with
|
||||||
/// - Returns: A (``Data``, ``Data``) tuple containing the certificate and certificate name, respectively.
|
/// - Returns: A (``Data``, ``Data``) tuple containing the certificate and certificate name, respectively.
|
||||||
47
Sources/Packages/Sources/SecretAgentKit/OpenSSHReader.swift
Normal file
47
Sources/Packages/Sources/SecretAgentKit/OpenSSHReader.swift
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Reads OpenSSH protocol data.
|
||||||
|
final class OpenSSHReader {
|
||||||
|
|
||||||
|
var remaining: Data
|
||||||
|
|
||||||
|
/// Initialize the reader with an OpenSSH data payload.
|
||||||
|
/// - Parameter data: The data to read.
|
||||||
|
init(data: Data) {
|
||||||
|
remaining = Data(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the next chunk of data from the playload.
|
||||||
|
/// - Returns: The next chunk of data.
|
||||||
|
func readNextChunk(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> Data {
|
||||||
|
let littleEndianLength = try readNextBytes(as: UInt32.self)
|
||||||
|
let length = convertEndianness ? Int(littleEndianLength.bigEndian) : Int(littleEndianLength)
|
||||||
|
guard remaining.count >= length else { throw .beyondBounds }
|
||||||
|
let dataRange = 0..<length
|
||||||
|
let ret = Data(remaining[dataRange])
|
||||||
|
remaining.removeSubrange(dataRange)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func readNextBytes<T>(as: T.Type) throws(OpenSSHReaderError) -> T {
|
||||||
|
let size = MemoryLayout<T>.size
|
||||||
|
guard remaining.count >= size else { throw .beyondBounds }
|
||||||
|
let lengthRange = 0..<size
|
||||||
|
let lengthChunk = remaining[lengthRange]
|
||||||
|
remaining.removeSubrange(lengthRange)
|
||||||
|
return unsafe lengthChunk.bytes.unsafeLoad(as: T.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String {
|
||||||
|
try String(decoding: readNextChunk(convertEndianness: convertEndianness), as: UTF8.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readNextChunkAsSubReader(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> OpenSSHReader {
|
||||||
|
OpenSSHReader(data: try readNextChunk(convertEndianness: convertEndianness))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum OpenSSHReaderError: Error, Codable {
|
||||||
|
case beyondBounds
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import Foundation
|
||||||
|
import OSLog
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
|
public protocol SSHAgentInputParserProtocol {
|
||||||
|
|
||||||
|
func parse(data: Data) async throws -> SSHAgent.Request
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
|
||||||
|
|
||||||
|
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "InputParser")
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public func parse(data: Data) throws(AgentParsingError) -> SSHAgent.Request {
|
||||||
|
logger.debug("Parsing new data")
|
||||||
|
guard data.count > 4 else {
|
||||||
|
throw .invalidData
|
||||||
|
}
|
||||||
|
let specifiedLength = unsafe (data[0..<4].bytes.unsafeLoad(as: UInt32.self).bigEndian) + 4
|
||||||
|
let rawRequestInt = data[4]
|
||||||
|
let remainingDataRange = 5..<min(Int(specifiedLength), data.count)
|
||||||
|
lazy var body: Data = { Data(data[remainingDataRange]) }()
|
||||||
|
switch rawRequestInt {
|
||||||
|
case SSHAgent.Request.requestIdentities.protocolID:
|
||||||
|
return .requestIdentities
|
||||||
|
case SSHAgent.Request.signRequest(.empty).protocolID:
|
||||||
|
do {
|
||||||
|
return .signRequest(try signatureRequestContext(from: body))
|
||||||
|
} catch {
|
||||||
|
throw .openSSHReader(error)
|
||||||
|
}
|
||||||
|
case SSHAgent.Request.addIdentity.protocolID:
|
||||||
|
return .addIdentity
|
||||||
|
case SSHAgent.Request.removeIdentity.protocolID:
|
||||||
|
return .removeIdentity
|
||||||
|
case SSHAgent.Request.removeAllIdentities.protocolID:
|
||||||
|
return .removeAllIdentities
|
||||||
|
case SSHAgent.Request.addIDConstrained.protocolID:
|
||||||
|
return .addIDConstrained
|
||||||
|
case SSHAgent.Request.addSmartcardKey.protocolID:
|
||||||
|
return .addSmartcardKey
|
||||||
|
case SSHAgent.Request.removeSmartcardKey.protocolID:
|
||||||
|
return .removeSmartcardKey
|
||||||
|
case SSHAgent.Request.lock.protocolID:
|
||||||
|
return .lock
|
||||||
|
case SSHAgent.Request.unlock.protocolID:
|
||||||
|
return .unlock
|
||||||
|
case SSHAgent.Request.addSmartcardKeyConstrained.protocolID:
|
||||||
|
return .addSmartcardKeyConstrained
|
||||||
|
case SSHAgent.Request.protocolExtension.protocolID:
|
||||||
|
return .protocolExtension
|
||||||
|
default:
|
||||||
|
return .unknown(rawRequestInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SSHAgentInputParser {
|
||||||
|
|
||||||
|
func signatureRequestContext(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext {
|
||||||
|
let reader = OpenSSHReader(data: data)
|
||||||
|
let rawKeyBlob = try reader.readNextChunk()
|
||||||
|
let keyBlob = certificatePublicKeyBlob(from: rawKeyBlob) ?? rawKeyBlob
|
||||||
|
let dataToSign = try reader.readNextChunk()
|
||||||
|
return SSHAgent.Request.SignatureRequestContext(keyBlob: keyBlob, dataToSign: dataToSign)
|
||||||
|
}
|
||||||
|
|
||||||
|
func certificatePublicKeyBlob(from hash: Data) -> Data? {
|
||||||
|
let reader = OpenSSHReader(data: hash)
|
||||||
|
do {
|
||||||
|
let certType = String(decoding: try reader.readNextChunk(), as: UTF8.self)
|
||||||
|
switch certType {
|
||||||
|
case "ecdsa-sha2-nistp256-cert-v01@openssh.com",
|
||||||
|
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
|
||||||
|
"ecdsa-sha2-nistp521-cert-v01@openssh.com":
|
||||||
|
_ = try reader.readNextChunk() // nonce
|
||||||
|
let curveIdentifier = try reader.readNextChunk()
|
||||||
|
let publicKey = try reader.readNextChunk()
|
||||||
|
let openSSHIdentifier = certType.replacingOccurrences(of: "-cert-v01@openssh.com", with: "")
|
||||||
|
return openSSHIdentifier.lengthAndData +
|
||||||
|
curveIdentifier.lengthAndData +
|
||||||
|
publicKey.lengthAndData
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension SSHAgentInputParser {
|
||||||
|
|
||||||
|
public enum AgentParsingError: Error, Codable {
|
||||||
|
case unknownRequest
|
||||||
|
case unhandledRequest
|
||||||
|
case invalidData
|
||||||
|
case openSSHReader(OpenSSHReaderError)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,39 +6,92 @@ public enum SSHAgent {}
|
|||||||
extension SSHAgent {
|
extension SSHAgent {
|
||||||
|
|
||||||
/// The type of the SSH Agent Request, as described in https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-5.1
|
/// The type of the SSH Agent Request, as described in https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-5.1
|
||||||
public enum RequestType: UInt8, CustomDebugStringConvertible {
|
public enum Request: CustomDebugStringConvertible, Codable, Sendable {
|
||||||
|
|
||||||
case requestIdentities = 11
|
case requestIdentities
|
||||||
case signRequest = 13
|
case signRequest(SignatureRequestContext)
|
||||||
|
case addIdentity
|
||||||
|
case removeIdentity
|
||||||
|
case removeAllIdentities
|
||||||
|
case addIDConstrained
|
||||||
|
case addSmartcardKey
|
||||||
|
case removeSmartcardKey
|
||||||
|
case lock
|
||||||
|
case unlock
|
||||||
|
case addSmartcardKeyConstrained
|
||||||
|
case protocolExtension
|
||||||
|
case unknown(UInt8)
|
||||||
|
|
||||||
|
public var protocolID: UInt8 {
|
||||||
|
switch self {
|
||||||
|
case .requestIdentities: 11
|
||||||
|
case .signRequest: 13
|
||||||
|
case .addIdentity: 17
|
||||||
|
case .removeIdentity: 18
|
||||||
|
case .removeAllIdentities: 19
|
||||||
|
case .addIDConstrained: 25
|
||||||
|
case .addSmartcardKey: 20
|
||||||
|
case .removeSmartcardKey: 21
|
||||||
|
case .lock: 22
|
||||||
|
case .unlock: 23
|
||||||
|
case .addSmartcardKeyConstrained: 26
|
||||||
|
case .protocolExtension: 27
|
||||||
|
case .unknown(let value): value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .requestIdentities:
|
case .requestIdentities: "SSH_AGENTC_REQUEST_IDENTITIES"
|
||||||
return "RequestIdentities"
|
case .signRequest: "SSH_AGENTC_SIGN_REQUEST"
|
||||||
case .signRequest:
|
case .addIdentity: "SSH_AGENTC_ADD_IDENTITY"
|
||||||
return "SignRequest"
|
case .removeIdentity: "SSH_AGENTC_REMOVE_IDENTITY"
|
||||||
}
|
case .removeAllIdentities: "SSH_AGENTC_REMOVE_ALL_IDENTITIES"
|
||||||
|
case .addIDConstrained: "SSH_AGENTC_ADD_ID_CONSTRAINED"
|
||||||
|
case .addSmartcardKey: "SSH_AGENTC_ADD_SMARTCARD_KEY"
|
||||||
|
case .removeSmartcardKey: "SSH_AGENTC_REMOVE_SMARTCARD_KEY"
|
||||||
|
case .lock: "SSH_AGENTC_LOCK"
|
||||||
|
case .unlock: "SSH_AGENTC_UNLOCK"
|
||||||
|
case .addSmartcardKeyConstrained: "SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED"
|
||||||
|
case .protocolExtension: "SSH_AGENTC_EXTENSION"
|
||||||
|
case .unknown: "UNKNOWN_MESSAGE"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct SignatureRequestContext: Sendable, Codable {
|
||||||
|
public let keyBlob: Data
|
||||||
|
public let dataToSign: Data
|
||||||
|
|
||||||
|
public init(keyBlob: Data, dataToSign: Data) {
|
||||||
|
self.keyBlob = keyBlob
|
||||||
|
self.dataToSign = dataToSign
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var empty: SignatureRequestContext {
|
||||||
|
SignatureRequestContext(keyBlob: Data(), dataToSign: Data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
public enum ResponseType: UInt8, CustomDebugStringConvertible {
|
public enum Response: UInt8, CustomDebugStringConvertible {
|
||||||
|
|
||||||
case agentFailure = 5
|
case agentFailure = 5
|
||||||
case agentSuccess = 6
|
case agentSuccess = 6
|
||||||
case agentIdentitiesAnswer = 12
|
case agentIdentitiesAnswer = 12
|
||||||
case agentSignResponse = 14
|
case agentSignResponse = 14
|
||||||
|
case agentExtensionFailure = 28
|
||||||
|
case agentExtensionResponse = 29
|
||||||
|
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .agentFailure:
|
case .agentFailure: "SSH_AGENT_FAILURE"
|
||||||
return "AgentFailure"
|
case .agentSuccess: "SSH_AGENT_SUCCESS"
|
||||||
case .agentSuccess:
|
case .agentIdentitiesAnswer: "SSH_AGENT_IDENTITIES_ANSWER"
|
||||||
return "AgentSuccess"
|
case .agentSignResponse: "SSH_AGENT_SIGN_RESPONSE"
|
||||||
case .agentIdentitiesAnswer:
|
case .agentExtensionFailure: "SSH_AGENT_EXTENSION_FAILURE"
|
||||||
return "AgentIdentitiesAnswer"
|
case .agentExtensionResponse: "SSH_AGENT_EXTENSION_RESPONSE"
|
||||||
case .agentSignResponse:
|
|
||||||
return "AgentSignResponse"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import Foundation
|
|||||||
import AppKit
|
import AppKit
|
||||||
import Security
|
import Security
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import SecretAgentKitHeaders
|
|
||||||
|
|
||||||
/// An object responsible for generating ``SecretKit.SigningRequestProvenance`` objects.
|
/// An object responsible for generating ``SecretKit.SigningRequestProvenance`` objects.
|
||||||
struct SigningRequestTracer {
|
struct SigningRequestTracer {
|
||||||
@@ -10,12 +9,11 @@ struct SigningRequestTracer {
|
|||||||
|
|
||||||
extension SigningRequestTracer {
|
extension SigningRequestTracer {
|
||||||
|
|
||||||
/// Generates a ``SecretKit.SigningRequestProvenance`` from a ``FileHandleReader``.
|
/// Generates a ``SecretKit.SigningRequestProvenance`` from a ``FileHandle``.
|
||||||
/// - Parameter fileHandleReader: The reader involved in processing the request.
|
/// - Parameter fileHandle: The reader involved in processing the request.
|
||||||
/// - Returns: A ``SecretKit.SigningRequestProvenance`` describing the origin of the request.
|
/// - Returns: A ``SecretKit.SigningRequestProvenance`` describing the origin of the request.
|
||||||
func provenance(from fileHandleReader: FileHandleReader) -> SigningRequestProvenance {
|
func provenance(from fileHandle: FileHandle) -> SigningRequestProvenance {
|
||||||
let firstInfo = process(from: fileHandleReader.pidOfConnectedProcess)
|
let firstInfo = process(from: fileHandle.pidOfConnectedProcess)
|
||||||
|
|
||||||
var provenance = SigningRequestProvenance(root: firstInfo)
|
var provenance = SigningRequestProvenance(root: firstInfo)
|
||||||
while NSRunningApplication(processIdentifier: provenance.origin.pid) == nil && provenance.origin.parentPID != nil {
|
while NSRunningApplication(processIdentifier: provenance.origin.pid) == nil && provenance.origin.parentPID != nil {
|
||||||
provenance.chain.append(process(from: provenance.origin.parentPID!))
|
provenance.chain.append(process(from: provenance.origin.parentPID!))
|
||||||
@@ -27,11 +25,11 @@ extension SigningRequestTracer {
|
|||||||
/// - Parameter pid: The process ID to look up.
|
/// - Parameter pid: The process ID to look up.
|
||||||
/// - Returns: a `kinfo_proc` struct describing the process ID.
|
/// - Returns: a `kinfo_proc` struct describing the process ID.
|
||||||
func pidAndNameInfo(from pid: Int32) -> kinfo_proc {
|
func pidAndNameInfo(from pid: Int32) -> kinfo_proc {
|
||||||
var len = MemoryLayout<kinfo_proc>.size
|
var len = unsafe MemoryLayout<kinfo_proc>.size
|
||||||
let infoPointer = UnsafeMutableRawPointer.allocate(byteCount: len, alignment: 1)
|
let infoPointer = UnsafeMutableRawPointer.allocate(byteCount: len, alignment: 1)
|
||||||
var name: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid]
|
var name: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid]
|
||||||
sysctl(&name, UInt32(name.count), infoPointer, &len, nil, 0)
|
unsafe sysctl(&name, UInt32(name.count), infoPointer, &len, nil, 0)
|
||||||
return infoPointer.load(as: kinfo_proc.self)
|
return unsafe infoPointer.load(as: kinfo_proc.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a ``SecretKit.SigningRequestProvenance.Process`` from a provided process ID.
|
/// Generates a ``SecretKit.SigningRequestProvenance.Process`` from a provided process ID.
|
||||||
@@ -39,18 +37,18 @@ extension SigningRequestTracer {
|
|||||||
/// - Returns: A ``SecretKit.SigningRequestProvenance.Process`` describing the process.
|
/// - Returns: A ``SecretKit.SigningRequestProvenance.Process`` describing the process.
|
||||||
func process(from pid: Int32) -> SigningRequestProvenance.Process {
|
func process(from pid: Int32) -> SigningRequestProvenance.Process {
|
||||||
var pidAndNameInfo = self.pidAndNameInfo(from: pid)
|
var pidAndNameInfo = self.pidAndNameInfo(from: pid)
|
||||||
let ppid = pidAndNameInfo.kp_eproc.e_ppid != 0 ? pidAndNameInfo.kp_eproc.e_ppid : nil
|
let ppid = unsafe pidAndNameInfo.kp_eproc.e_ppid != 0 ? pidAndNameInfo.kp_eproc.e_ppid : nil
|
||||||
let procName = withUnsafeMutablePointer(to: &pidAndNameInfo.kp_proc.p_comm.0) { pointer in
|
let procName = unsafe withUnsafeMutablePointer(to: &pidAndNameInfo.kp_proc.p_comm.0) { pointer in
|
||||||
String(cString: pointer)
|
unsafe String(cString: pointer)
|
||||||
}
|
}
|
||||||
|
|
||||||
let pathPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(MAXPATHLEN))
|
let pathPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(MAXPATHLEN))
|
||||||
_ = proc_pidpath(pid, pathPointer, UInt32(MAXPATHLEN))
|
_ = unsafe proc_pidpath(pid, pathPointer, UInt32(MAXPATHLEN))
|
||||||
let path = String(cString: pathPointer)
|
let path = unsafe String(cString: pathPointer)
|
||||||
var secCode: Unmanaged<SecCode>!
|
var secCode: Unmanaged<SecCode>!
|
||||||
let flags: SecCSFlags = [.considerExpiration, .enforceRevocationChecks]
|
let flags: SecCSFlags = [.considerExpiration, .enforceRevocationChecks]
|
||||||
SecCodeCreateWithPID(pid, SecCSFlags(), &secCode)
|
unsafe SecCodeCreateWithPID(pid, SecCSFlags(), &secCode)
|
||||||
let valid = SecCodeCheckValidity(secCode.takeRetainedValue(), flags, nil) == errSecSuccess
|
let valid = unsafe SecCodeCheckValidity(secCode.takeRetainedValue(), flags, nil) == errSecSuccess
|
||||||
return SigningRequestProvenance.Process(pid: pid, processName: procName, appName: appName(for: pid), iconURL: iconURL(for: pid), path: path, validSignature: valid, parentPID: ppid)
|
return SigningRequestProvenance.Process(pid: pid, processName: procName, appName: appName(for: pid), iconURL: iconURL(for: pid), path: path, validSignature: valid, parentPID: ppid)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,3 +79,11 @@ extension SigningRequestTracer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// from libproc.h
|
||||||
|
@_silgen_name("proc_pidpath")
|
||||||
|
@discardableResult func proc_pidpath(_ pid: Int32, _ buffer: UnsafeMutableRawPointer!, _ buffersize: UInt32) -> Int32
|
||||||
|
|
||||||
|
//// from SecTask.h
|
||||||
|
@_silgen_name("SecCodeCreateWithPID")
|
||||||
|
@discardableResult func SecCodeCreateWithPID(_: Int32, _: SecCSFlags, _: UnsafeMutablePointer<Unmanaged<SecCode>?>!) -> OSStatus
|
||||||
|
|||||||
@@ -133,22 +133,21 @@ private extension SocketPort {
|
|||||||
|
|
||||||
convenience init(path: String) {
|
convenience init(path: String) {
|
||||||
var addr = sockaddr_un()
|
var addr = sockaddr_un()
|
||||||
|
|
||||||
|
let length = unsafe withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in
|
||||||
|
unsafe path.withCString { cstring in
|
||||||
|
let len = unsafe strlen(cstring)
|
||||||
|
unsafe strncpy(pointer, cstring, len)
|
||||||
|
return len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This doesn't seem to be _strictly_ neccessary with SocketPort.
|
||||||
|
// but just for good form.
|
||||||
addr.sun_family = sa_family_t(AF_UNIX)
|
addr.sun_family = sa_family_t(AF_UNIX)
|
||||||
|
// This mirrors the SUN_LEN macro format.
|
||||||
|
addr.sun_len = UInt8(MemoryLayout<sockaddr_un>.size - MemoryLayout.size(ofValue: addr.sun_path) + length)
|
||||||
|
|
||||||
var len: Int = 0
|
let data = unsafe Data(bytes: &addr, count: MemoryLayout<sockaddr_un>.size)
|
||||||
withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in
|
|
||||||
path.withCString { cstring in
|
|
||||||
len = strlen(cstring)
|
|
||||||
strncpy(pointer, cstring, len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addr.sun_len = UInt8(len+2)
|
|
||||||
|
|
||||||
var data: Data!
|
|
||||||
withUnsafePointer(to: &addr) { pointer in
|
|
||||||
data = Data(bytes: pointer, count: MemoryLayout<sockaddr_un>.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.init(protocolFamily: AF_UNIX, socketType: SOCK_STREAM, protocol: 0, address: data)!
|
self.init(protocolFamily: AF_UNIX, socketType: SOCK_STREAM, protocol: 0, address: data)!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <Security/Security.h>
|
|
||||||
|
|
||||||
|
|
||||||
// Forward declarations
|
|
||||||
|
|
||||||
// from libproc.h
|
|
||||||
int proc_pidpath(int pid, void * buffer, uint32_t buffersize);
|
|
||||||
|
|
||||||
// from SecTask.h
|
|
||||||
OSStatus SecCodeCreateWithPID(int32_t, SecCSFlags, SecCodeRef *);
|
|
||||||
|
|
||||||
//! Project version number for SecretAgentKit.
|
|
||||||
FOUNDATION_EXPORT double SecretAgentKitVersionNumber;
|
|
||||||
|
|
||||||
//! Project version string for SecretAgentKit.
|
|
||||||
FOUNDATION_EXPORT const unsigned char SecretAgentKitVersionString[];
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module SecretAgentKitHeaders [system] {
|
|
||||||
header "include/SecretAgentKit.h"
|
|
||||||
export *
|
|
||||||
}
|
|
||||||
@@ -36,12 +36,12 @@ public struct KeychainError: Error {
|
|||||||
/// A signing-related error.
|
/// A signing-related error.
|
||||||
public struct SigningError: Error {
|
public struct SigningError: Error {
|
||||||
/// The underlying error reported by the API, if one was returned.
|
/// The underlying error reported by the API, if one was returned.
|
||||||
public let error: SecurityError?
|
public let error: CFError?
|
||||||
|
|
||||||
/// Initializes a SigningError with an optional SecurityError.
|
/// Initializes a SigningError with an optional SecurityError.
|
||||||
/// - Parameter statusCode: The SecurityError, if one is applicable.
|
/// - Parameter statusCode: The SecurityError, if one is applicable.
|
||||||
public init(error: SecurityError?) {
|
public init(error: SecurityError?) {
|
||||||
self.error = error
|
self.error = unsafe error?.takeRetainedValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ extension Data {
|
|||||||
package var lengthAndData: Data {
|
package var lengthAndData: Data {
|
||||||
let rawLength = UInt32(count)
|
let rawLength = UInt32(count)
|
||||||
var endian = rawLength.bigEndian
|
var endian = rawLength.bigEndian
|
||||||
return Data(bytes: &endian, count: UInt32.bitWidth/8) + self
|
return unsafe Data(bytes: &endian, count: MemoryLayout<UInt32>.size) + self
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ extension OpenSSHPublicKeyWriter {
|
|||||||
|
|
||||||
extension OpenSSHPublicKeyWriter {
|
extension OpenSSHPublicKeyWriter {
|
||||||
|
|
||||||
public func rsaPublicKeyBlob<SecretType: Secret>(secret: SecretType) -> Data {
|
func rsaPublicKeyBlob<SecretType: Secret>(secret: SecretType) -> Data {
|
||||||
// Cheap way to pull out e and n as defined in https://datatracker.ietf.org/doc/html/rfc4253
|
// Cheap way to pull out e and n as defined in https://datatracker.ietf.org/doc/html/rfc4253
|
||||||
// Keychain stores it as a thin ASN.1 wrapper with this format:
|
// Keychain stores it as a thin ASN.1 wrapper with this format:
|
||||||
// [4 byte prefix][2 byte prefix][n][2 byte prefix][e]
|
// [4 byte prefix][2 byte prefix][n][2 byte prefix][e]
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// Reads OpenSSH protocol data.
|
|
||||||
public final class OpenSSHReader {
|
|
||||||
|
|
||||||
var remaining: Data
|
|
||||||
|
|
||||||
/// Initialize the reader with an OpenSSH data payload.
|
|
||||||
/// - Parameter data: The data to read.
|
|
||||||
public init(data: Data) {
|
|
||||||
remaining = Data(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads the next chunk of data from the playload.
|
|
||||||
/// - Returns: The next chunk of data.
|
|
||||||
public func readNextChunk() -> Data {
|
|
||||||
let lengthRange = 0..<(UInt32.bitWidth/8)
|
|
||||||
let lengthChunk = remaining[lengthRange]
|
|
||||||
remaining.removeSubrange(lengthRange)
|
|
||||||
let littleEndianLength = lengthChunk.bytes.unsafeLoad(as: UInt32.self)
|
|
||||||
let length = Int(littleEndianLength.bigEndian)
|
|
||||||
let dataRange = 0..<length
|
|
||||||
let ret = Data(remaining[dataRange])
|
|
||||||
remaining.removeSubrange(dataRange)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,7 @@ extension SecureEnclave {
|
|||||||
kSecReturnAttributes: true
|
kSecReturnAttributes: true
|
||||||
])
|
])
|
||||||
var privateUntyped: CFTypeRef?
|
var privateUntyped: CFTypeRef?
|
||||||
SecItemCopyMatching(privateAttributes, &privateUntyped)
|
unsafe SecItemCopyMatching(privateAttributes, &privateUntyped)
|
||||||
guard let privateTyped = privateUntyped as? [[CFString: Any]] else { return }
|
guard let privateTyped = privateUntyped as? [[CFString: Any]] else { return }
|
||||||
let migratedPublicKeys = Set(store.secrets.map(\.publicKey))
|
let migratedPublicKeys = Set(store.secrets.map(\.publicKey))
|
||||||
var migratedAny = false
|
var migratedAny = false
|
||||||
@@ -40,7 +40,7 @@ extension SecureEnclave {
|
|||||||
}
|
}
|
||||||
let ref = key[kSecValueRef] as! SecKey
|
let ref = key[kSecValueRef] as! SecKey
|
||||||
let attributes = SecKeyCopyAttributes(ref) as! [CFString: Any]
|
let attributes = SecKeyCopyAttributes(ref) as! [CFString: Any]
|
||||||
let tokenObjectID = attributes[Constants.tokenObjectID] as! Data
|
let tokenObjectID = unsafe attributes[Constants.tokenObjectID] as! Data
|
||||||
let accessControl = attributes[kSecAttrAccessControl] as! SecAccessControl
|
let accessControl = attributes[kSecAttrAccessControl] as! SecAccessControl
|
||||||
// Best guess.
|
// Best guess.
|
||||||
let auth: AuthenticationRequirement = String(describing: accessControl)
|
let auth: AuthenticationRequirement = String(describing: accessControl)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ extension SecureEnclave {
|
|||||||
/// - duration: The duration of the authorization context, in seconds.
|
/// - duration: The duration of the authorization context, in seconds.
|
||||||
init(secret: Secret, context: LAContext, duration: TimeInterval) {
|
init(secret: Secret, context: LAContext, duration: TimeInterval) {
|
||||||
self.secret = secret
|
self.secret = secret
|
||||||
self.context = context
|
unsafe self.context = context
|
||||||
let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value
|
let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value
|
||||||
self.monotonicExpiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds)
|
self.monotonicExpiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds)
|
||||||
}
|
}
|
||||||
@@ -56,11 +56,9 @@ extension SecureEnclave {
|
|||||||
formatter.unitsStyle = .spellOut
|
formatter.unitsStyle = .spellOut
|
||||||
formatter.allowedUnits = [.hour, .minute, .day]
|
formatter.allowedUnits = [.hour, .minute, .day]
|
||||||
|
|
||||||
if let durationString = formatter.string(from: duration) {
|
|
||||||
|
let durationString = formatter.string(from: duration)!
|
||||||
newContext.localizedReason = String(localized: .authContextPersistForDuration(secretName: secret.name, duration: durationString))
|
newContext.localizedReason = String(localized: .authContextPersistForDuration(secretName: secret.name, duration: durationString))
|
||||||
} else {
|
|
||||||
newContext.localizedReason = String(localized: .authContextPersistForDurationUnknown(secretName: secret.name))
|
|
||||||
}
|
|
||||||
let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason)
|
let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason)
|
||||||
guard success else { return }
|
guard success else { return }
|
||||||
let context = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration)
|
let context = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Foundation
|
|||||||
import Observation
|
import Observation
|
||||||
import Security
|
import Security
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
@preconcurrency import LocalAuthentication
|
import LocalAuthentication
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ extension SecureEnclave {
|
|||||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
|
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
|
||||||
var context: LAContext
|
var context: LAContext
|
||||||
if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) {
|
if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) {
|
||||||
context = existing.context
|
context = unsafe existing.context
|
||||||
} else {
|
} else {
|
||||||
let newContext = LAContext()
|
let newContext = LAContext()
|
||||||
newContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
|
newContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
|
||||||
@@ -57,7 +57,7 @@ extension SecureEnclave {
|
|||||||
kSecReturnData: true,
|
kSecReturnData: true,
|
||||||
])
|
])
|
||||||
var untyped: CFTypeRef?
|
var untyped: CFTypeRef?
|
||||||
let status = SecItemCopyMatching(queryAttributes, &untyped)
|
let status = unsafe SecItemCopyMatching(queryAttributes, &untyped)
|
||||||
if status != errSecSuccess {
|
if status != errSecSuccess {
|
||||||
throw KeychainError(statusCode: status)
|
throw KeychainError(statusCode: status)
|
||||||
}
|
}
|
||||||
@@ -121,12 +121,12 @@ extension SecureEnclave {
|
|||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
let access =
|
let access =
|
||||||
SecAccessControlCreateWithFlags(kCFAllocatorDefault,
|
unsafe SecAccessControlCreateWithFlags(kCFAllocatorDefault,
|
||||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||||
flags,
|
flags,
|
||||||
&accessError)
|
&accessError)
|
||||||
if let error = accessError {
|
if let error = unsafe accessError {
|
||||||
throw error.takeRetainedValue() as Error
|
throw unsafe error.takeRetainedValue() as Error
|
||||||
}
|
}
|
||||||
let dataRep: Data
|
let dataRep: Data
|
||||||
let publicKey: Data
|
let publicKey: Data
|
||||||
@@ -214,7 +214,7 @@ extension SecureEnclave.Store {
|
|||||||
kSecReturnAttributes: true
|
kSecReturnAttributes: true
|
||||||
])
|
])
|
||||||
var untyped: CFTypeRef?
|
var untyped: CFTypeRef?
|
||||||
SecItemCopyMatching(queryAttributes, &untyped)
|
unsafe SecItemCopyMatching(queryAttributes, &untyped)
|
||||||
guard let typed = untyped as? [[CFString: Any]] else { return }
|
guard let typed = untyped as? [[CFString: Any]] else { return }
|
||||||
let wrapped: [SecureEnclave.Secret] = typed.compactMap {
|
let wrapped: [SecureEnclave.Secret] = typed.compactMap {
|
||||||
do {
|
do {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Observation
|
import Observation
|
||||||
import Security
|
import Security
|
||||||
@preconcurrency import CryptoTokenKit
|
@unsafe @preconcurrency import CryptoTokenKit
|
||||||
import LocalAuthentication
|
import LocalAuthentication
|
||||||
import SecretKit
|
import SecretKit
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ extension SmartCard {
|
|||||||
kSecReturnRef: true
|
kSecReturnRef: true
|
||||||
])
|
])
|
||||||
var untyped: CFTypeRef?
|
var untyped: CFTypeRef?
|
||||||
let status = SecItemCopyMatching(attributes, &untyped)
|
let status = unsafe SecItemCopyMatching(attributes, &untyped)
|
||||||
if status != errSecSuccess {
|
if status != errSecSuccess {
|
||||||
throw KeychainError(statusCode: status)
|
throw KeychainError(statusCode: status)
|
||||||
}
|
}
|
||||||
@@ -80,8 +80,8 @@ extension SmartCard {
|
|||||||
let key = untypedSafe as! SecKey
|
let key = untypedSafe as! SecKey
|
||||||
var signError: SecurityError?
|
var signError: SecurityError?
|
||||||
guard let algorithm = signatureAlgorithm(for: secret) else { throw UnsupportKeyType() }
|
guard let algorithm = signatureAlgorithm(for: secret) else { throw UnsupportKeyType() }
|
||||||
guard let signature = SecKeyCreateSignature(key, algorithm, data as CFData, &signError) else {
|
guard let signature = unsafe SecKeyCreateSignature(key, algorithm, data as CFData, &signError) else {
|
||||||
throw SigningError(error: signError)
|
throw unsafe SigningError(error: signError)
|
||||||
}
|
}
|
||||||
return signature as Data
|
return signature as Data
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ extension SmartCard.Store {
|
|||||||
kSecReturnAttributes: true
|
kSecReturnAttributes: true
|
||||||
])
|
])
|
||||||
var untyped: CFTypeRef?
|
var untyped: CFTypeRef?
|
||||||
SecItemCopyMatching(attributes, &untyped)
|
unsafe SecItemCopyMatching(attributes, &untyped)
|
||||||
guard let typed = untyped as? [[CFString: Any]] else { return }
|
guard let typed = untyped as? [[CFString: Any]] else { return }
|
||||||
let wrapped: [SecretType] = typed.compactMap {
|
let wrapped: [SecretType] = typed.compactMap {
|
||||||
let name = $0[kSecAttrLabel] as? String ?? String(localized: .unnamedSecret)
|
let name = $0[kSecAttrLabel] as? String ?? String(localized: .unnamedSecret)
|
||||||
|
|||||||
14
Sources/Packages/Sources/XPCWrappers/XPCProtocol.swift
Normal file
14
Sources/Packages/Sources/XPCWrappers/XPCProtocol.swift
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
@objc protocol _XPCProtocol: Sendable {
|
||||||
|
func process(_ data: Data, with reply: @Sendable @escaping (Data?, Error?) -> Void)
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol XPCProtocol<Input, Output>: Sendable {
|
||||||
|
|
||||||
|
associatedtype Input: Codable
|
||||||
|
associatedtype Output: Codable
|
||||||
|
|
||||||
|
func process(_ data: Input) async throws -> Output
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public final class XPCServiceDelegate: NSObject, NSXPCListenerDelegate {
|
||||||
|
|
||||||
|
private let exportedObject: ErasedXPCProtocol
|
||||||
|
|
||||||
|
public init<XPCProtocolType: XPCProtocol>(exportedObject: XPCProtocolType) {
|
||||||
|
self.exportedObject = ErasedXPCProtocol(exportedObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
||||||
|
newConnection.exportedInterface = NSXPCInterface(with: (any _XPCProtocol).self)
|
||||||
|
let exportedObject = exportedObject
|
||||||
|
newConnection.exportedObject = exportedObject
|
||||||
|
newConnection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = Z72PRUAWF6")
|
||||||
|
newConnection.resume()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private final class ErasedXPCProtocol: NSObject, _XPCProtocol {
|
||||||
|
|
||||||
|
let _process: @Sendable (Data, @Sendable @escaping (Data?, (any Error)?) -> Void) -> Void
|
||||||
|
|
||||||
|
public init<XPCProtocolType: XPCProtocol>(_ exportedObject: XPCProtocolType) {
|
||||||
|
_process = { data, reply in
|
||||||
|
Task { [reply] in
|
||||||
|
do {
|
||||||
|
let decoded = try JSONDecoder().decode(XPCProtocolType.Input.self, from: data)
|
||||||
|
let result = try await exportedObject.process(decoded)
|
||||||
|
let encoded = try JSONEncoder().encode(result)
|
||||||
|
reply(encoded, nil)
|
||||||
|
} catch {
|
||||||
|
if let error = error as? Codable & Error {
|
||||||
|
reply(nil, NSError(error))
|
||||||
|
} else {
|
||||||
|
reply(nil, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func process(_ data: Data, with reply: @Sendable @escaping (Data?, (any Error)?) -> Void) {
|
||||||
|
_process(data, reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NSError {
|
||||||
|
|
||||||
|
private enum Constants {
|
||||||
|
static let domain = "com.maxgoedjen.secretive.xpcwrappers"
|
||||||
|
static let code = -1
|
||||||
|
static let dataKey = "underlying"
|
||||||
|
}
|
||||||
|
|
||||||
|
@nonobjc convenience init<ErrorType: Codable & Error>(_ error: ErrorType) {
|
||||||
|
let encoded = try? JSONEncoder().encode(error)
|
||||||
|
self.init(domain: Constants.domain, code: Constants.code, userInfo: [Constants.dataKey: encoded as Any])
|
||||||
|
}
|
||||||
|
|
||||||
|
@nonobjc public func underlying<ErrorType: Codable & Error>(as errorType: ErrorType.Type) -> ErrorType? {
|
||||||
|
guard domain == Constants.domain && code == Constants.code, let data = userInfo[Constants.dataKey] as? Data else { return nil }
|
||||||
|
return try? JSONDecoder().decode(ErrorType.self, from: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
53
Sources/Packages/Sources/XPCWrappers/XPCTypedSession.swift
Normal file
53
Sources/Packages/Sources/XPCWrappers/XPCTypedSession.swift
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct XPCTypedSession<ResponseType: Codable & Sendable, ErrorType: Error & Codable>: ~Copyable {
|
||||||
|
|
||||||
|
private let connection: NSXPCConnection
|
||||||
|
private let proxy: _XPCProtocol
|
||||||
|
|
||||||
|
public init(serviceName: String, warmup: Bool = false) async throws {
|
||||||
|
let connection = NSXPCConnection(serviceName: serviceName)
|
||||||
|
connection.remoteObjectInterface = NSXPCInterface(with: (any _XPCProtocol).self)
|
||||||
|
connection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = Z72PRUAWF6")
|
||||||
|
connection.resume()
|
||||||
|
guard let proxy = connection.remoteObjectProxy as? _XPCProtocol else { fatalError() }
|
||||||
|
self.connection = connection
|
||||||
|
self.proxy = proxy
|
||||||
|
if warmup {
|
||||||
|
_ = try? await send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func send(_ message: some Encodable = Data()) async throws -> ResponseType {
|
||||||
|
let encoded = try JSONEncoder().encode(message)
|
||||||
|
return try await withCheckedThrowingContinuation { continuation in
|
||||||
|
proxy.process(encoded) { data, error in
|
||||||
|
do {
|
||||||
|
if let error {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
guard let data else {
|
||||||
|
throw NoDataError()
|
||||||
|
}
|
||||||
|
let decoded = try JSONDecoder().decode(ResponseType.self, from: data)
|
||||||
|
continuation.resume(returning: decoded)
|
||||||
|
} catch {
|
||||||
|
if let typed = (error as NSError).underlying(as: ErrorType.self) {
|
||||||
|
continuation.resume(throwing: typed)
|
||||||
|
} else {
|
||||||
|
continuation.resume(throwing: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public func complete() {
|
||||||
|
connection.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct NoDataError: Error {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -8,19 +8,22 @@ import CryptoKit
|
|||||||
|
|
||||||
// MARK: Identity Listing
|
// MARK: Identity Listing
|
||||||
|
|
||||||
|
|
||||||
// let testProvenance = SigningRequestProvenance(root: .init(pid: 0, processName: "Test", appName: "Test", iconURL: nil, path: /, validSignature: true, parentPID: nil))
|
|
||||||
|
|
||||||
@Test func emptyStores() async throws {
|
@Test func emptyStores() async throws {
|
||||||
let agent = Agent(storeList: SecretStoreList())
|
let agent = Agent(storeList: SecretStoreList())
|
||||||
let response = try await agent.handle(data: Constants.Requests.requestIdentities, provenance: .test)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities)
|
||||||
|
let response = await agent.handle(request: request, provenance: .test)
|
||||||
#expect(response == Constants.Responses.requestIdentitiesEmpty)
|
#expect(response == Constants.Responses.requestIdentitiesEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func identitiesList() async throws {
|
@Test func identitiesList() async throws {
|
||||||
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)
|
let agent = Agent(storeList: list)
|
||||||
let response = try await agent.handle(data: Constants.Requests.requestIdentities, provenance: .test)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities)
|
||||||
|
let response = await agent.handle(request: request, provenance: .test)
|
||||||
|
|
||||||
|
let actual = OpenSSHReader(data: response)
|
||||||
|
let expected = OpenSSHReader(data: Constants.Responses.requestIdentitiesMultiple)
|
||||||
|
print(actual, expected)
|
||||||
#expect(response == Constants.Responses.requestIdentitiesMultiple)
|
#expect(response == Constants.Responses.requestIdentitiesMultiple)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,40 +32,42 @@ import CryptoKit
|
|||||||
@Test func noMatchingIdentities() async throws {
|
@Test func noMatchingIdentities() async throws {
|
||||||
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)
|
let agent = Agent(storeList: list)
|
||||||
let response = try await agent.handle(data: Constants.Requests.requestSignatureWithNoneMatching, provenance: .test)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignatureWithNoneMatching)
|
||||||
|
let response = await agent.handle(request: request, provenance: .test)
|
||||||
#expect(response == Constants.Responses.requestFailure)
|
#expect(response == Constants.Responses.requestFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test func ecdsaSignature() async throws {
|
@Test func ecdsaSignature() async throws {
|
||||||
// let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
||||||
// let requestReader = OpenSSHReader(data: Constants.Requests.requestSignature[5...])
|
guard case SSHAgent.Request.signRequest(let context) = request else { return }
|
||||||
// _ = requestReader.readNextChunk()
|
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
||||||
// let dataToSign = requestReader.readNextChunk()
|
let agent = Agent(storeList: list)
|
||||||
// let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
let response = await agent.handle(request: request, provenance: .test)
|
||||||
// let agent = Agent(storeList: list)
|
let responseReader = OpenSSHReader(data: response)
|
||||||
// await agent.handle(reader: stubReader, writer: stubWriter)
|
let length = try responseReader.readNextBytes(as: UInt32.self).bigEndian
|
||||||
// let outer = OpenSSHReader(data: stubWriter.data[5...])
|
let type = try responseReader.readNextBytes(as: UInt8.self).bigEndian
|
||||||
// let payload = outer.readNextChunk()
|
#expect(length == response.count - MemoryLayout<UInt32>.size)
|
||||||
// let inner = OpenSSHReader(data: payload)
|
#expect(type == SSHAgent.Response.agentSignResponse.rawValue)
|
||||||
// _ = inner.readNextChunk()
|
let outer = OpenSSHReader(data: responseReader.remaining)
|
||||||
// let signedData = inner.readNextChunk()
|
let inner = try outer.readNextChunkAsSubReader()
|
||||||
// let rsData = OpenSSHReader(data: signedData)
|
_ = try inner.readNextChunk()
|
||||||
// var r = rsData.readNextChunk()
|
let rsData = try inner.readNextChunkAsSubReader()
|
||||||
// var s = rsData.readNextChunk()
|
var r = try rsData.readNextChunk()
|
||||||
// // This is fine IRL, but it freaks out CryptoKit
|
var s = try rsData.readNextChunk()
|
||||||
// if r[0] == 0 {
|
// This is fine IRL, but it freaks out CryptoKit
|
||||||
// r.removeFirst()
|
if r[0] == 0 {
|
||||||
// }
|
r.removeFirst()
|
||||||
// if s[0] == 0 {
|
}
|
||||||
// s.removeFirst()
|
if s[0] == 0 {
|
||||||
// }
|
s.removeFirst()
|
||||||
// var rs = r
|
}
|
||||||
// rs.append(s)
|
var rs = r
|
||||||
// let signature = try P256.Signing.ECDSASignature(rawRepresentation: rs)
|
rs.append(s)
|
||||||
// // Correct signature
|
let signature = try P256.Signing.ECDSASignature(rawRepresentation: rs)
|
||||||
// #expect(try P256.Signing.PublicKey(x963Representation: Constants.Secrets.ecdsa256Secret.publicKey)
|
// Correct signature
|
||||||
// .isValidSignature(signature, for: dataToSign))
|
#expect(try P256.Signing.PublicKey(x963Representation: Constants.Secrets.ecdsa256Secret.publicKey)
|
||||||
// }
|
.isValidSignature(signature, for: context.dataToSign))
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Witness protocol
|
// MARK: Witness protocol
|
||||||
|
|
||||||
@@ -72,7 +77,7 @@ import CryptoKit
|
|||||||
return true
|
return true
|
||||||
}, witness: { _, _ in })
|
}, witness: { _, _ in })
|
||||||
let agent = Agent(storeList: list, witness: witness)
|
let agent = Agent(storeList: list, witness: witness)
|
||||||
let response = try await agent.handle(data: Constants.Requests.requestSignature, provenance: .test)
|
let response = await agent.handle(request: .signRequest(.empty), provenance: .test)
|
||||||
#expect(response == Constants.Responses.requestFailure)
|
#expect(response == Constants.Responses.requestFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +90,8 @@ import CryptoKit
|
|||||||
witnessed = true
|
witnessed = true
|
||||||
})
|
})
|
||||||
let agent = Agent(storeList: list, witness: witness)
|
let agent = Agent(storeList: list, witness: witness)
|
||||||
_ = try await agent.handle(data: Constants.Requests.requestSignature, provenance: .test)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
||||||
|
_ = await agent.handle(request: request, provenance: .test)
|
||||||
#expect(witnessed)
|
#expect(witnessed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +106,8 @@ import CryptoKit
|
|||||||
witnessTrace = trace
|
witnessTrace = trace
|
||||||
})
|
})
|
||||||
let agent = Agent(storeList: list, witness: witness)
|
let agent = Agent(storeList: list, witness: witness)
|
||||||
_ = try await agent.handle(data: Constants.Requests.requestSignature, provenance: .test)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
||||||
|
_ = await agent.handle(request: request, provenance: .test)
|
||||||
#expect(witnessTrace == speakNowTrace)
|
#expect(witnessTrace == speakNowTrace)
|
||||||
#expect(witnessTrace == .test)
|
#expect(witnessTrace == .test)
|
||||||
}
|
}
|
||||||
@@ -112,7 +119,8 @@ import CryptoKit
|
|||||||
let store = await list.stores.first?.base as! Stub.Store
|
let store = await list.stores.first?.base as! Stub.Store
|
||||||
store.shouldThrow = true
|
store.shouldThrow = true
|
||||||
let agent = Agent(storeList: list)
|
let agent = Agent(storeList: list)
|
||||||
let response = try await agent.handle(data: Constants.Requests.requestSignature, provenance: .test)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
||||||
|
let response = await agent.handle(request: request, provenance: .test)
|
||||||
#expect(response == Constants.Responses.requestFailure)
|
#expect(response == Constants.Responses.requestFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +128,7 @@ import CryptoKit
|
|||||||
|
|
||||||
@Test func unhandledAdd() async throws {
|
@Test func unhandledAdd() async throws {
|
||||||
let agent = Agent(storeList: SecretStoreList())
|
let agent = Agent(storeList: SecretStoreList())
|
||||||
let response = try await agent.handle(data: Constants.Requests.addIdentity, provenance: .test)
|
let response = await agent.handle(request: .addIdentity, provenance: .test)
|
||||||
#expect(response == Constants.Responses.requestFailure)
|
#expect(response == Constants.Responses.requestFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,14 +154,13 @@ extension AgentTests {
|
|||||||
|
|
||||||
enum Requests {
|
enum Requests {
|
||||||
static let requestIdentities = Data(base64Encoded: "AAAAAQs=")!
|
static let requestIdentities = Data(base64Encoded: "AAAAAQs=")!
|
||||||
static let addIdentity = Data(base64Encoded: "AAAAARE=")!
|
|
||||||
static let requestSignatureWithNoneMatching = Data(base64Encoded: "AAABhA0AAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQAAAO8AAAAgbqmrqPUtJ8mmrtaSVexjMYyXWNqjHSnoto7zgv86xvcyAAAAA2dpdAAAAA5zc2gtY29ubmVjdGlvbgAAAAlwdWJsaWNrZXkBAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQAAAAA=")!
|
static let requestSignatureWithNoneMatching = Data(base64Encoded: "AAABhA0AAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQAAAO8AAAAgbqmrqPUtJ8mmrtaSVexjMYyXWNqjHSnoto7zgv86xvcyAAAAA2dpdAAAAA5zc2gtY29ubmVjdGlvbgAAAAlwdWJsaWNrZXkBAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQAAAAA=")!
|
||||||
static let requestSignature = Data(base64Encoded: "AAABRA0AAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKzOkUiVJEcACMtAd9X7xalbc0FYZyhbmv2dsWl4IP2GWIi+RcsaHQNw+nAIQ8CKEYmLnl0VLDp5Ef8KMhgIy08AAADPAAAAIBIFsbCZ4/dhBmLNGHm0GKj7EJ4N8k/jXRxlyg+LFIYzMgAAAANnaXQAAAAOc3NoLWNvbm5lY3Rpb24AAAAJcHVibGlja2V5AQAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSszpFIlSRHAAjLQHfV+8WpW3NBWGcoW5r9nbFpeCD9hliIvkXLGh0DcPpwCEPAihGJi55dFSw6eRH/CjIYCMtPAAAAAA==")!
|
static let requestSignature = Data(base64Encoded: "AAABRA0AAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKzOkUiVJEcACMtAd9X7xalbc0FYZyhbmv2dsWl4IP2GWIi+RcsaHQNw+nAIQ8CKEYmLnl0VLDp5Ef8KMhgIy08AAADPAAAAIBIFsbCZ4/dhBmLNGHm0GKj7EJ4N8k/jXRxlyg+LFIYzMgAAAANnaXQAAAAOc3NoLWNvbm5lY3Rpb24AAAAJcHVibGlja2V5AQAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSszpFIlSRHAAjLQHfV+8WpW3NBWGcoW5r9nbFpeCD9hliIvkXLGh0DcPpwCEPAihGJi55dFSw6eRH/CjIYCMtPAAAAAA==")!
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Responses {
|
enum Responses {
|
||||||
static let requestIdentitiesEmpty = Data(base64Encoded: "AAAABQwAAAAA")!
|
static let requestIdentitiesEmpty = Data(base64Encoded: "AAAABQwAAAAA")!
|
||||||
static let requestIdentitiesMultiple = Data(base64Encoded: "AAABKwwAAAACAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSszpFIlSRHAAjLQHfV+8WpW3NBWGcoW5r9nbFpeCD9hliIvkXLGh0DcPpwCEPAihGJi55dFSw6eRH/CjIYCMtPAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBLKSzA5q3jCb3q0JKigvcxfWVGrJ+bklpG0Zc9YzUwrbsh9SipvlSJi+sHQI+O0m88DOpRBAtuAHX60euD/Yv250tovN7/+MEFbXGZ/hLdd0BoFpWbLfJcQj806KJGlcDAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0")!
|
static let requestIdentitiesMultiple = Data(base64Encoded: "AAABLwwAAAACAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSszpFIlSRHAAjLQHfV+8WpW3NBWGcoW5r9nbFpeCD9hliIvkXLGh0DcPpwCEPAihGJi55dFSw6eRH/CjIYCMtPAAAAFWVjZHNhLTI1NkBleGFtcGxlLmNvbQAAAIgAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAGEEspLMDmreMJverQkqKC9zF9ZUasn5uSWkbRlz1jNTCtuyH1KKm+VImL6wdAj47SbzwM6lEEC24AdfrR64P9i/bnS2i83v/4wQVtcZn+Et13QGgWlZst8lxCPzTookaVwMAAAAFWVjZHNhLTM4NEBleGFtcGxlLmNvbQ==")!
|
||||||
static let requestFailure = Data(base64Encoded: "AAAAAQU=")!
|
static let requestFailure = Data(base64Encoded: "AAAAAQU=")!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
@testable import SecretKit
|
@testable import SecretAgentKit
|
||||||
@testable import SecureEnclaveSecretKit
|
@testable import SecureEnclaveSecretKit
|
||||||
@testable import SmartCardSecretKit
|
@testable import SmartCardSecretKit
|
||||||
|
|
||||||
@Suite struct OpenSSHReaderTests {
|
@Suite struct OpenSSHReaderTests {
|
||||||
|
|
||||||
@Test func signatureRequest() {
|
@Test func signatureRequest() throws {
|
||||||
let reader = OpenSSHReader(data: Constants.signatureRequest)
|
let reader = OpenSSHReader(data: Constants.signatureRequest)
|
||||||
let hash = reader.readNextChunk()
|
let hash = try reader.readNextChunk()
|
||||||
#expect(hash == Data(base64Encoded: "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQ=="))
|
#expect(hash == Data(base64Encoded: "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQ=="))
|
||||||
let dataToSign = reader.readNextChunk()
|
let dataToSign = try reader.readNextChunk()
|
||||||
#expect(dataToSign == Data(base64Encoded: "AAAAICi5xf1ixOestUlxdjvt/BDcM+rzhwy7Vo8cW5YcxA8+MgAAAANnaXQAAAAOc3NoLWNvbm5lY3Rpb24AAAAJcHVibGlja2V5AQAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAAiAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRKgm5CWzh8uUtcFQmiaCj5qUtHoDOH6zKpZYJ5UUUP1L55+hFUvKFdN3DwNgc9BHREskSgP7G5p9GgbYIyMBhC8buOVFxFtUxM+k8cktLt77iKkd1qNQq0FkQ55Kxv6QU="))
|
#expect(dataToSign == Data(base64Encoded: "AAAAICi5xf1ixOestUlxdjvt/BDcM+rzhwy7Vo8cW5YcxA8+MgAAAANnaXQAAAAOc3NoLWNvbm5lY3Rpb24AAAAJcHVibGlja2V5AQAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAAiAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRKgm5CWzh8uUtcFQmiaCj5qUtHoDOH6zKpZYJ5UUUP1L55+hFUvKFdN3DwNgc9BHREskSgP7G5p9GgbYIyMBhC8buOVFxFtUxM+k8cktLt77iKkd1qNQq0FkQ55Kxv6QU="))
|
||||||
let empty = reader.readNextChunk()
|
let empty = try reader.readNextChunk()
|
||||||
#expect(empty.isEmpty)
|
#expect(empty.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ extension Stub {
|
|||||||
let privateKey: Data
|
let privateKey: Data
|
||||||
|
|
||||||
init(keySize: Int, publicKey: Data, privateKey: Data) {
|
init(keySize: Int, publicKey: Data, privateKey: Data) {
|
||||||
self.attributes = Attributes(keyType: .init(algorithm: .ecdsa, size: keySize), authentication: .notRequired)
|
self.attributes = Attributes(keyType: .init(algorithm: .ecdsa, size: keySize), authentication: .notRequired, publicKeyAttribution: "ecdsa-\(keySize)@example.com")
|
||||||
self.publicKey = publicKey
|
self.publicKey = publicKey
|
||||||
self.privateKey = privateKey
|
self.privateKey = privateKey
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,11 +34,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
logger.debug("SecretAgent finished launching")
|
logger.debug("SecretAgent finished launching")
|
||||||
Task {
|
Task {
|
||||||
|
let inputParser = try await XPCAgentInputParser()
|
||||||
for await session in socketController.sessions {
|
for await session in socketController.sessions {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
for await message in session.messages {
|
for await message in session.messages {
|
||||||
let agentResponse = try await agent.handle(data: message, provenance: session.provenance)
|
let request = try await inputParser.parse(data: message)
|
||||||
|
let agentResponse = await agent.handle(request: request, provenance: session.provenance)
|
||||||
try await session.write(agentResponse)
|
try await session.write(agentResponse)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -58,7 +60,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
updater.update
|
updater.update
|
||||||
} onChange: { [updater, notifier] in
|
} onChange: { [updater, notifier] in
|
||||||
Task {
|
Task {
|
||||||
guard !updater.testBuild else { return }
|
guard !updater.currentVersion.isTestBuild else { return }
|
||||||
await notifier.notify(update: updater.update!) { release in
|
await notifier.notify(update: updater.update!) { release in
|
||||||
await updater.ignore(release: release)
|
await updater.ignore(release: release)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,22 +9,7 @@
|
|||||||
<key>Website</key>
|
<key>Website</key>
|
||||||
<string>https://github.com/maxgoedjen/secretive</string>
|
<string>https://github.com/maxgoedjen/secretive</string>
|
||||||
<key>Connections</key>
|
<key>Connections</key>
|
||||||
<array>
|
<array/>
|
||||||
<dict>
|
|
||||||
<key>IsIncoming</key>
|
|
||||||
<false/>
|
|
||||||
<key>Host</key>
|
|
||||||
<string>api.github.com</string>
|
|
||||||
<key>NetworkProtocol</key>
|
|
||||||
<string>TCP</string>
|
|
||||||
<key>Port</key>
|
|
||||||
<string>443</string>
|
|
||||||
<key>Purpose</key>
|
|
||||||
<string>Secretive checks GitHub for new versions and security updates.</string>
|
|
||||||
<key>DenyConsequences</key>
|
|
||||||
<string>If you deny these connections, you will not be notified about new versions and critical security updates.</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>Services</key>
|
<key>Services</key>
|
||||||
<array/>
|
<array/>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
23
Sources/SecretAgent/XPCInputParser.swift
Normal file
23
Sources/SecretAgent/XPCInputParser.swift
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import Foundation
|
||||||
|
import SecretAgentKit
|
||||||
|
import Brief
|
||||||
|
import XPCWrappers
|
||||||
|
|
||||||
|
/// Delegates all agent input parsing to an XPC service which wraps OpenSSH
|
||||||
|
public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
|
||||||
|
|
||||||
|
private let session: XPCTypedSession<SSHAgent.Request, SSHAgentInputParser.AgentParsingError>
|
||||||
|
|
||||||
|
public init() async throws {
|
||||||
|
session = try await XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretAgentInputParser", warmup: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func parse(data: Data) async throws -> SSHAgent.Request {
|
||||||
|
try await session.send(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
session.complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
Sources/SecretAgentInputParser/Info.plist
Normal file
11
Sources/SecretAgentInputParser/Info.plist
Normal file
@@ -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>
|
||||||
17
Sources/SecretAgentInputParser/SecretAgentInputParser.swift
Normal file
17
Sources/SecretAgentInputParser/SecretAgentInputParser.swift
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import Foundation
|
||||||
|
import OSLog
|
||||||
|
import XPCWrappers
|
||||||
|
import SecretAgentKit
|
||||||
|
|
||||||
|
final class SecretAgentInputParser: NSObject, XPCProtocol {
|
||||||
|
|
||||||
|
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.SecretAgentInputParser", category: "SecretAgentInputParser")
|
||||||
|
|
||||||
|
func process(_ data: Data) async throws -> SSHAgent.Request {
|
||||||
|
let parser = SSHAgentInputParser()
|
||||||
|
let result = try parser.parse(data: data)
|
||||||
|
logger.log("Parser parsed message as type \(result.debugDescription)")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
7
Sources/SecretAgentInputParser/main.swift
Normal file
7
Sources/SecretAgentInputParser/main.swift
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import Foundation
|
||||||
|
import XPCWrappers
|
||||||
|
|
||||||
|
let delegate = XPCServiceDelegate(exportedObject: SecretAgentInputParser())
|
||||||
|
let listener = NSXPCListener.service()
|
||||||
|
listener.delegate = delegate
|
||||||
|
listener.resume()
|
||||||
@@ -25,11 +25,13 @@
|
|||||||
501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
50153E20250AFCB200525160 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E1F250AFCB200525160 /* UpdateView.swift */; };
|
50153E20250AFCB200525160 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E1F250AFCB200525160 /* UpdateView.swift */; };
|
||||||
50153E22250DECA300525160 /* SecretListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListItemView.swift */; };
|
50153E22250DECA300525160 /* SecretListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListItemView.swift */; };
|
||||||
|
501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501578122E6C0479004A37D0 /* XPCInputParser.swift */; };
|
||||||
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
|
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
|
||||||
504788EC2E680DC800B4556F /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788EB2E680DC400B4556F /* URLs.swift */; };
|
504788EC2E680DC800B4556F /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788EB2E680DC400B4556F /* URLs.swift */; };
|
||||||
504788F22E681F3A00B4556F /* Instructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F12E681F3A00B4556F /* Instructions.swift */; };
|
504788F22E681F3A00B4556F /* Instructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F12E681F3A00B4556F /* Instructions.swift */; };
|
||||||
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 */; };
|
||||||
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
|
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
|
||||||
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; };
|
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; };
|
||||||
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
|
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
|
||||||
@@ -42,6 +44,17 @@
|
|||||||
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C72516FE6E004B5A36 /* CopyableView.swift */; };
|
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C72516FE6E004B5A36 /* CopyableView.swift */; };
|
||||||
506772C72424784600034DED /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 506772C62424784600034DED /* Credits.rtf */; };
|
506772C72424784600034DED /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 506772C62424784600034DED /* Credits.rtf */; };
|
||||||
506772C92425BB8500034DED /* NoStoresView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506772C82425BB8500034DED /* NoStoresView.swift */; };
|
506772C92425BB8500034DED /* NoStoresView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506772C82425BB8500034DED /* NoStoresView.swift */; };
|
||||||
|
50692D1D2E6FDB880043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
50692D282E6FDB8D0043C7BB /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50692D242E6FDB8D0043C7BB /* main.swift */; };
|
||||||
|
50692D2D2E6FDC000043C7BB /* XPCWrappers in Frameworks */ = {isa = PBXBuildFile; productRef = 50692D2C2E6FDC000043C7BB /* XPCWrappers */; };
|
||||||
|
50692D2F2E6FDC2B0043C7BB /* SecretiveUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50692D2E2E6FDC290043C7BB /* SecretiveUpdater.swift */; };
|
||||||
|
50692D312E6FDC390043C7BB /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 50692D302E6FDC390043C7BB /* Brief */; };
|
||||||
|
50692E5B2E6FF9D20043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
50692E682E6FF9E20043C7BB /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50692E632E6FF9E20043C7BB /* main.swift */; };
|
||||||
|
50692E692E6FF9E20043C7BB /* SecretAgentInputParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50692E642E6FF9E20043C7BB /* SecretAgentInputParser.swift */; };
|
||||||
|
50692E6C2E6FFA510043C7BB /* SecretAgentKit in Frameworks */ = {isa = PBXBuildFile; productRef = 50692E6B2E6FFA510043C7BB /* SecretAgentKit */; };
|
||||||
|
50692E6D2E6FFA5F0043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
50692E702E6FFA6E0043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5079BA0E250F29BF00EA86F4 /* StoreListView.swift */; };
|
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5079BA0E250F29BF00EA86F4 /* StoreListView.swift */; };
|
||||||
508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58A9241E06B40069DC07 /* PreviewUpdater.swift */; };
|
508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58A9241E06B40069DC07 /* PreviewUpdater.swift */; };
|
||||||
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */; };
|
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */; };
|
||||||
@@ -70,9 +83,68 @@
|
|||||||
remoteGlobalIDString = 50A3B78924026B7500D209EA;
|
remoteGlobalIDString = 50A3B78924026B7500D209EA;
|
||||||
remoteInfo = SecretAgent;
|
remoteInfo = SecretAgent;
|
||||||
};
|
};
|
||||||
|
501577D32E6BC5DD004A37D0 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 501577BC2E6BC5B4004A37D0;
|
||||||
|
remoteInfo = ReleasesDownloader;
|
||||||
|
};
|
||||||
|
50692D1B2E6FDB880043C7BB /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 50692D112E6FDB880043C7BB;
|
||||||
|
remoteInfo = SecretiveUpdater;
|
||||||
|
};
|
||||||
|
50692E592E6FF9D20043C7BB /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 50692E4F2E6FF9D20043C7BB;
|
||||||
|
remoteInfo = SecretAgentInputParser;
|
||||||
|
};
|
||||||
|
50692E6E2E6FFA5F0043C7BB /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 50692D112E6FDB880043C7BB;
|
||||||
|
remoteInfo = SecretiveUpdater;
|
||||||
|
};
|
||||||
|
50692E712E6FFA6E0043C7BB /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 50692E4F2E6FF9D20043C7BB;
|
||||||
|
remoteInfo = SecretAgentInputParser;
|
||||||
|
};
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
501577C92E6BC5B4004A37D0 /* Embed XPC Services */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
|
||||||
|
dstSubfolderSpec = 16;
|
||||||
|
files = (
|
||||||
|
50692D1D2E6FDB880043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */,
|
||||||
|
50692E5B2E6FF9D20043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */,
|
||||||
|
);
|
||||||
|
name = "Embed XPC Services";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
501577D22E6BC5D4004A37D0 /* Embed XPC Services */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
|
||||||
|
dstSubfolderSpec = 16;
|
||||||
|
files = (
|
||||||
|
50692E6D2E6FFA5F0043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */,
|
||||||
|
50692E702E6FFA6E0043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */,
|
||||||
|
);
|
||||||
|
name = "Embed XPC Services";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
50617DBF23FCE4AB0099B055 /* Embed Frameworks */ = {
|
50617DBF23FCE4AB0099B055 /* Embed Frameworks */ = {
|
||||||
isa = PBXCopyFilesBuildPhase;
|
isa = PBXCopyFilesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -113,11 +185,13 @@
|
|||||||
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Packages/Resources/Localizable.xcstrings; sourceTree = SOURCE_ROOT; };
|
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Packages/Resources/Localizable.xcstrings; sourceTree = SOURCE_ROOT; };
|
||||||
50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; };
|
50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; };
|
||||||
50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = "<group>"; };
|
50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = "<group>"; };
|
||||||
|
501578122E6C0479004A37D0 /* XPCInputParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCInputParser.swift; sourceTree = "<group>"; };
|
||||||
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
|
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
|
||||||
504788EB2E680DC400B4556F /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = "<group>"; };
|
504788EB2E680DC400B4556F /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = "<group>"; };
|
||||||
504788F12E681F3A00B4556F /* Instructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instructions.swift; sourceTree = "<group>"; };
|
504788F12E681F3A00B4556F /* Instructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instructions.swift; sourceTree = "<group>"; };
|
||||||
504788F32E681F6900B4556F /* ToolConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolConfigurationView.swift; sourceTree = "<group>"; };
|
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>"; };
|
||||||
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; };
|
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; };
|
||||||
50571E0424393D1500F76F6C /* LaunchAgentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAgentController.swift; sourceTree = "<group>"; };
|
50571E0424393D1500F76F6C /* LaunchAgentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAgentController.swift; sourceTree = "<group>"; };
|
||||||
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; };
|
||||||
@@ -133,6 +207,15 @@
|
|||||||
5066A6C72516FE6E004B5A36 /* CopyableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableView.swift; sourceTree = "<group>"; };
|
5066A6C72516FE6E004B5A36 /* CopyableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableView.swift; sourceTree = "<group>"; };
|
||||||
506772C62424784600034DED /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = "<group>"; };
|
506772C62424784600034DED /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = "<group>"; };
|
||||||
506772C82425BB8500034DED /* NoStoresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoStoresView.swift; sourceTree = "<group>"; };
|
506772C82425BB8500034DED /* NoStoresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoStoresView.swift; sourceTree = "<group>"; };
|
||||||
|
50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
|
||||||
|
50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretiveUpdater.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
50692D232E6FDB8D0043C7BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
50692D242E6FDB8D0043C7BB /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||||
|
50692D2E2E6FDC290043C7BB /* SecretiveUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretiveUpdater.swift; sourceTree = "<group>"; };
|
||||||
|
50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretAgentInputParser.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
50692E622E6FF9E20043C7BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
50692E632E6FF9E20043C7BB /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||||
|
50692E642E6FF9E20043C7BB /* SecretAgentInputParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretAgentInputParser.swift; sourceTree = "<group>"; };
|
||||||
5079BA0E250F29BF00EA86F4 /* StoreListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreListView.swift; sourceTree = "<group>"; };
|
5079BA0E250F29BF00EA86F4 /* StoreListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreListView.swift; sourceTree = "<group>"; };
|
||||||
508A58A9241E06B40069DC07 /* PreviewUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewUpdater.swift; sourceTree = "<group>"; };
|
508A58A9241E06B40069DC07 /* PreviewUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewUpdater.swift; sourceTree = "<group>"; };
|
||||||
508A58AB241E121B0069DC07 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
508A58AB241E121B0069DC07 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
||||||
@@ -170,6 +253,23 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
50692D0F2E6FDB880043C7BB /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
50692D2D2E6FDC000043C7BB /* XPCWrappers in Frameworks */,
|
||||||
|
50692D312E6FDC390043C7BB /* Brief in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
50692E4D2E6FF9D20043C7BB /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
50692E6C2E6FFA510043C7BB /* SecretAgentKit in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
50A3B78724026B7500D209EA /* Frameworks */ = {
|
50A3B78724026B7500D209EA /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -198,6 +298,7 @@
|
|||||||
children = (
|
children = (
|
||||||
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */,
|
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */,
|
||||||
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */,
|
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */,
|
||||||
|
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */,
|
||||||
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
|
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
|
||||||
);
|
);
|
||||||
path = Styles;
|
path = Styles;
|
||||||
@@ -249,6 +350,8 @@
|
|||||||
50617D8123FCE48E0099B055 /* Secretive */,
|
50617D8123FCE48E0099B055 /* Secretive */,
|
||||||
50A3B78B24026B7500D209EA /* SecretAgent */,
|
50A3B78B24026B7500D209EA /* SecretAgent */,
|
||||||
508A58AF241E144C0069DC07 /* Config */,
|
508A58AF241E144C0069DC07 /* Config */,
|
||||||
|
50692D272E6FDB8D0043C7BB /* SecretiveUpdater */,
|
||||||
|
50692E662E6FF9E20043C7BB /* SecretAgentInputParser */,
|
||||||
50617D8023FCE48E0099B055 /* Products */,
|
50617D8023FCE48E0099B055 /* Products */,
|
||||||
5099A08B240243730062B6F2 /* Frameworks */,
|
5099A08B240243730062B6F2 /* Frameworks */,
|
||||||
);
|
);
|
||||||
@@ -259,6 +362,8 @@
|
|||||||
children = (
|
children = (
|
||||||
50617D7F23FCE48E0099B055 /* Secretive.app */,
|
50617D7F23FCE48E0099B055 /* Secretive.app */,
|
||||||
50A3B78A24026B7500D209EA /* SecretAgent.app */,
|
50A3B78A24026B7500D209EA /* SecretAgent.app */,
|
||||||
|
50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */,
|
||||||
|
50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -292,6 +397,27 @@
|
|||||||
path = "Preview Content";
|
path = "Preview Content";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
50692D272E6FDB8D0043C7BB /* SecretiveUpdater */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
50692D232E6FDB8D0043C7BB /* Info.plist */,
|
||||||
|
50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */,
|
||||||
|
50692D242E6FDB8D0043C7BB /* main.swift */,
|
||||||
|
50692D2E2E6FDC290043C7BB /* SecretiveUpdater.swift */,
|
||||||
|
);
|
||||||
|
path = SecretiveUpdater;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
50692E662E6FF9E20043C7BB /* SecretAgentInputParser */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
50692E622E6FF9E20043C7BB /* Info.plist */,
|
||||||
|
50692E632E6FF9E20043C7BB /* main.swift */,
|
||||||
|
50692E642E6FF9E20043C7BB /* SecretAgentInputParser.swift */,
|
||||||
|
);
|
||||||
|
path = SecretAgentInputParser;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
508A58AF241E144C0069DC07 /* Config */ = {
|
508A58AF241E144C0069DC07 /* Config */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -336,6 +462,7 @@
|
|||||||
children = (
|
children = (
|
||||||
50020BAF24064869003D4025 /* AppDelegate.swift */,
|
50020BAF24064869003D4025 /* AppDelegate.swift */,
|
||||||
5018F54E24064786002EB505 /* Notifier.swift */,
|
5018F54E24064786002EB505 /* Notifier.swift */,
|
||||||
|
501578122E6C0479004A37D0 /* XPCInputParser.swift */,
|
||||||
50A3B79524026B7600D209EA /* Main.storyboard */,
|
50A3B79524026B7600D209EA /* Main.storyboard */,
|
||||||
50A3B79824026B7600D209EA /* Info.plist */,
|
50A3B79824026B7600D209EA /* Info.plist */,
|
||||||
508BF29425B4F140009EFB7E /* InternetAccessPolicy.plist */,
|
508BF29425B4F140009EFB7E /* InternetAccessPolicy.plist */,
|
||||||
@@ -365,11 +492,14 @@
|
|||||||
50617D7D23FCE48D0099B055 /* Resources */,
|
50617D7D23FCE48D0099B055 /* Resources */,
|
||||||
50617DBF23FCE4AB0099B055 /* Embed Frameworks */,
|
50617DBF23FCE4AB0099B055 /* Embed Frameworks */,
|
||||||
50C385AF240E438B00AF2719 /* CopyFiles */,
|
50C385AF240E438B00AF2719 /* CopyFiles */,
|
||||||
|
501577C92E6BC5B4004A37D0 /* Embed XPC Services */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
50142167278126B500BBAA70 /* PBXTargetDependency */,
|
50142167278126B500BBAA70 /* PBXTargetDependency */,
|
||||||
|
50692D1C2E6FDB880043C7BB /* PBXTargetDependency */,
|
||||||
|
50692E5A2E6FF9D20043C7BB /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = Secretive;
|
name = Secretive;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
@@ -382,6 +512,47 @@
|
|||||||
productReference = 50617D7F23FCE48E0099B055 /* Secretive.app */;
|
productReference = 50617D7F23FCE48E0099B055 /* Secretive.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
|
50692D112E6FDB880043C7BB /* SecretiveUpdater */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 50692D1F2E6FDB880043C7BB /* Build configuration list for PBXNativeTarget "SecretiveUpdater" */;
|
||||||
|
buildPhases = (
|
||||||
|
50692D0E2E6FDB880043C7BB /* Sources */,
|
||||||
|
50692D0F2E6FDB880043C7BB /* Frameworks */,
|
||||||
|
50692D102E6FDB880043C7BB /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = SecretiveUpdater;
|
||||||
|
packageProductDependencies = (
|
||||||
|
50692D2C2E6FDC000043C7BB /* XPCWrappers */,
|
||||||
|
50692D302E6FDC390043C7BB /* Brief */,
|
||||||
|
);
|
||||||
|
productName = SecretiveUpdater;
|
||||||
|
productReference = 50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */;
|
||||||
|
productType = "com.apple.product-type.xpc-service";
|
||||||
|
};
|
||||||
|
50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 50692E5D2E6FF9D20043C7BB /* Build configuration list for PBXNativeTarget "SecretAgentInputParser" */;
|
||||||
|
buildPhases = (
|
||||||
|
50692E4C2E6FF9D20043C7BB /* Sources */,
|
||||||
|
50692E4D2E6FF9D20043C7BB /* Frameworks */,
|
||||||
|
50692E4E2E6FF9D20043C7BB /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = SecretAgentInputParser;
|
||||||
|
packageProductDependencies = (
|
||||||
|
50692E6B2E6FFA510043C7BB /* SecretAgentKit */,
|
||||||
|
);
|
||||||
|
productName = SecretAgentInputParser;
|
||||||
|
productReference = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */;
|
||||||
|
productType = "com.apple.product-type.xpc-service";
|
||||||
|
};
|
||||||
50A3B78924026B7500D209EA /* SecretAgent */ = {
|
50A3B78924026B7500D209EA /* SecretAgent */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 50A3B79A24026B7600D209EA /* Build configuration list for PBXNativeTarget "SecretAgent" */;
|
buildConfigurationList = 50A3B79A24026B7600D209EA /* Build configuration list for PBXNativeTarget "SecretAgent" */;
|
||||||
@@ -390,10 +561,14 @@
|
|||||||
50A3B78724026B7500D209EA /* Frameworks */,
|
50A3B78724026B7500D209EA /* Frameworks */,
|
||||||
50A3B78824026B7500D209EA /* Resources */,
|
50A3B78824026B7500D209EA /* Resources */,
|
||||||
50A5C18E240E4B4B00E2996C /* Embed Frameworks */,
|
50A5C18E240E4B4B00E2996C /* Embed Frameworks */,
|
||||||
|
501577D22E6BC5D4004A37D0 /* Embed XPC Services */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
|
501577D42E6BC5DD004A37D0 /* PBXTargetDependency */,
|
||||||
|
50692E6F2E6FFA5F0043C7BB /* PBXTargetDependency */,
|
||||||
|
50692E722E6FFA6E0043C7BB /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = SecretAgent;
|
name = SecretAgent;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
@@ -414,13 +589,19 @@
|
|||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastSwiftUpdateCheck = 1220;
|
LastSwiftUpdateCheck = 2600;
|
||||||
LastUpgradeCheck = 2600;
|
LastUpgradeCheck = 2600;
|
||||||
ORGANIZATIONNAME = "Max Goedjen";
|
ORGANIZATIONNAME = "Max Goedjen";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
50617D7E23FCE48D0099B055 = {
|
50617D7E23FCE48D0099B055 = {
|
||||||
CreatedOnToolsVersion = 11.3;
|
CreatedOnToolsVersion = 11.3;
|
||||||
};
|
};
|
||||||
|
50692D112E6FDB880043C7BB = {
|
||||||
|
CreatedOnToolsVersion = 26.0;
|
||||||
|
};
|
||||||
|
50692E4F2E6FF9D20043C7BB = {
|
||||||
|
CreatedOnToolsVersion = 26.0;
|
||||||
|
};
|
||||||
50A3B78924026B7500D209EA = {
|
50A3B78924026B7500D209EA = {
|
||||||
CreatedOnToolsVersion = 11.4;
|
CreatedOnToolsVersion = 11.4;
|
||||||
};
|
};
|
||||||
@@ -450,6 +631,8 @@
|
|||||||
targets = (
|
targets = (
|
||||||
50617D7E23FCE48D0099B055 /* Secretive */,
|
50617D7E23FCE48D0099B055 /* Secretive */,
|
||||||
50A3B78924026B7500D209EA /* SecretAgent */,
|
50A3B78924026B7500D209EA /* SecretAgent */,
|
||||||
|
50692D112E6FDB880043C7BB /* SecretiveUpdater */,
|
||||||
|
50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@@ -467,6 +650,20 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
50692D102E6FDB880043C7BB /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
50692E4E2E6FF9D20043C7BB /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
50A3B78824026B7500D209EA /* Resources */ = {
|
50A3B78824026B7500D209EA /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -491,6 +688,7 @@
|
|||||||
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
|
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
|
||||||
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
||||||
504788EC2E680DC800B4556F /* URLs.swift in Sources */,
|
504788EC2E680DC800B4556F /* URLs.swift in Sources */,
|
||||||
|
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */,
|
||||||
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
||||||
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
|
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
|
||||||
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
||||||
@@ -520,12 +718,31 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
50692D0E2E6FDB880043C7BB /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
50692D2F2E6FDC2B0043C7BB /* SecretiveUpdater.swift in Sources */,
|
||||||
|
50692D282E6FDB8D0043C7BB /* main.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
50692E4C2E6FF9D20043C7BB /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
50692E682E6FF9E20043C7BB /* main.swift in Sources */,
|
||||||
|
50692E692E6FF9E20043C7BB /* SecretAgentInputParser.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
50A3B78624026B7500D209EA /* Sources */ = {
|
50A3B78624026B7500D209EA /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
50020BB024064869003D4025 /* AppDelegate.swift in Sources */,
|
50020BB024064869003D4025 /* AppDelegate.swift in Sources */,
|
||||||
5018F54F24064786002EB505 /* Notifier.swift in Sources */,
|
5018F54F24064786002EB505 /* Notifier.swift in Sources */,
|
||||||
|
501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -537,6 +754,30 @@
|
|||||||
target = 50A3B78924026B7500D209EA /* SecretAgent */;
|
target = 50A3B78924026B7500D209EA /* SecretAgent */;
|
||||||
targetProxy = 50142166278126B500BBAA70 /* PBXContainerItemProxy */;
|
targetProxy = 50142166278126B500BBAA70 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
501577D42E6BC5DD004A37D0 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
targetProxy = 501577D32E6BC5DD004A37D0 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
50692D1C2E6FDB880043C7BB /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 50692D112E6FDB880043C7BB /* SecretiveUpdater */;
|
||||||
|
targetProxy = 50692D1B2E6FDB880043C7BB /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
50692E5A2E6FF9D20043C7BB /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */;
|
||||||
|
targetProxy = 50692E592E6FF9D20043C7BB /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
50692E6F2E6FFA5F0043C7BB /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 50692D112E6FDB880043C7BB /* SecretiveUpdater */;
|
||||||
|
targetProxy = 50692E6E2E6FFA5F0043C7BB /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
50692E722E6FFA6E0043C7BB /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */;
|
||||||
|
targetProxy = 50692E712E6FFA6E0043C7BB /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
/* Begin PBXVariantGroup section */
|
||||||
@@ -621,6 +862,8 @@
|
|||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_STRICT_MEMORY_SAFETY = YES;
|
||||||
|
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||||
SWIFT_VERSION = 6.0;
|
SWIFT_VERSION = 6.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -688,6 +931,8 @@
|
|||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
SWIFT_STRICT_MEMORY_SAFETY = YES;
|
||||||
|
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||||
SWIFT_VERSION = 6.0;
|
SWIFT_VERSION = 6.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@@ -708,7 +953,7 @@
|
|||||||
ENABLE_ENHANCED_SECURITY = YES;
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
ENABLE_POINTER_AUTHENTICATION = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
@@ -748,7 +993,7 @@
|
|||||||
ENABLE_ENHANCED_SECURITY = YES;
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
ENABLE_POINTER_AUTHENTICATION = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
@@ -772,6 +1017,225 @@
|
|||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
50692D202E6FDB880043C7BB /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
|
ENABLE_APP_SANDBOX = YES;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||||
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_USB = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = SecretiveUpdater/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
50692D212E6FDB880043C7BB /* Test */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
ENABLE_APP_SANDBOX = YES;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||||
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_USB = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = SecretiveUpdater/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
50692D222E6FDB880043C7BB /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6;
|
||||||
|
ENABLE_APP_SANDBOX = YES;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||||
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
|
||||||
|
ENABLE_RESOURCE_ACCESS_USB = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = SecretiveUpdater/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
50692E5E2E6FF9D20043C7BB /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
|
ENABLE_APP_SANDBOX = YES;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = SecretAgentInputParser/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
50692E5F2E6FF9D20043C7BB /* Test */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
ENABLE_APP_SANDBOX = YES;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = SecretAgentInputParser/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
50692E602E6FF9D20043C7BB /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6;
|
||||||
|
ENABLE_APP_SANDBOX = YES;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = SecretAgentInputParser/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
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;
|
||||||
|
};
|
||||||
508A5914241EF1A00069DC07 /* Test */ = {
|
508A5914241EF1A00069DC07 /* Test */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 508A58AB241E121B0069DC07 /* Config.xcconfig */;
|
baseConfigurationReference = 508A58AB241E121B0069DC07 /* Config.xcconfig */;
|
||||||
@@ -842,6 +1306,8 @@
|
|||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_STRICT_MEMORY_SAFETY = YES;
|
||||||
|
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||||
SWIFT_VERSION = 6.0;
|
SWIFT_VERSION = 6.0;
|
||||||
};
|
};
|
||||||
name = Test;
|
name = Test;
|
||||||
@@ -860,7 +1326,7 @@
|
|||||||
ENABLE_ENHANCED_SECURITY = YES;
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = NO;
|
ENABLE_HARDENED_RUNTIME = NO;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
ENABLE_POINTER_AUTHENTICATION = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
@@ -894,7 +1360,7 @@
|
|||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
@@ -929,7 +1395,7 @@
|
|||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
@@ -965,7 +1431,7 @@
|
|||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
@@ -1011,6 +1477,26 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
50692D1F2E6FDB880043C7BB /* Build configuration list for PBXNativeTarget "SecretiveUpdater" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
50692D202E6FDB880043C7BB /* Debug */,
|
||||||
|
50692D212E6FDB880043C7BB /* Test */,
|
||||||
|
50692D222E6FDB880043C7BB /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
50692E5D2E6FF9D20043C7BB /* Build configuration list for PBXNativeTarget "SecretAgentInputParser" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
50692E5E2E6FF9D20043C7BB /* Debug */,
|
||||||
|
50692E5F2E6FF9D20043C7BB /* Test */,
|
||||||
|
50692E602E6FF9D20043C7BB /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
50A3B79A24026B7600D209EA /* Build configuration list for PBXNativeTarget "SecretAgent" */ = {
|
50A3B79A24026B7600D209EA /* Build configuration list for PBXNativeTarget "SecretAgent" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
@@ -1060,6 +1546,18 @@
|
|||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = Brief;
|
productName = Brief;
|
||||||
};
|
};
|
||||||
|
50692D2C2E6FDC000043C7BB /* XPCWrappers */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = XPCWrappers;
|
||||||
|
};
|
||||||
|
50692D302E6FDC390043C7BB /* Brief */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = Brief;
|
||||||
|
};
|
||||||
|
50692E6B2E6FFA510043C7BB /* SecretAgentKit */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = SecretAgentKit;
|
||||||
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = 50617D7723FCE48D0099B055 /* Project object */;
|
rootObject = 50617D7723FCE48D0099B055 /* Project object */;
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2600"
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<TestPlans>
|
||||||
|
<TestPlanReference
|
||||||
|
reference = "container:Config/Secretive.xctestplan">
|
||||||
|
</TestPlanReference>
|
||||||
|
</TestPlans>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -25,6 +25,9 @@ extension EnvironmentValues {
|
|||||||
}()
|
}()
|
||||||
@Entry var updater: any UpdaterProtocol = _updater
|
@Entry var updater: any UpdaterProtocol = _updater
|
||||||
|
|
||||||
|
private static let _justUpdatedChecker = JustUpdatedChecker()
|
||||||
|
@Entry var justUpdatedChecker: any JustUpdatedCheckerProtocol = _justUpdatedChecker
|
||||||
|
|
||||||
@MainActor var secretStoreList: SecretStoreList {
|
@MainActor var secretStoreList: SecretStoreList {
|
||||||
EnvironmentValues._secretStoreList
|
EnvironmentValues._secretStoreList
|
||||||
}
|
}
|
||||||
@@ -33,8 +36,8 @@ extension EnvironmentValues {
|
|||||||
@main
|
@main
|
||||||
struct Secretive: App {
|
struct Secretive: App {
|
||||||
|
|
||||||
private let justUpdatedChecker = JustUpdatedChecker()
|
|
||||||
@Environment(\.agentStatusChecker) var agentStatusChecker
|
@Environment(\.agentStatusChecker) var agentStatusChecker
|
||||||
|
@Environment(\.justUpdatedChecker) var justUpdatedChecker
|
||||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||||
@State private var showingSetup = false
|
@State private var showingSetup = false
|
||||||
@State private var showingIntegrations = false
|
@State private var showingIntegrations = false
|
||||||
@@ -52,7 +55,7 @@ struct Secretive: App {
|
|||||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
||||||
guard hasRunSetup else { return }
|
guard hasRunSetup else { return }
|
||||||
agentStatusChecker.check()
|
agentStatusChecker.check()
|
||||||
if agentStatusChecker.running && justUpdatedChecker.justUpdated {
|
if agentStatusChecker.running && justUpdatedChecker.justUpdatedBuild {
|
||||||
// Relaunch the agent, since it'll be running from earlier update still
|
// Relaunch the agent, since it'll be running from earlier update still
|
||||||
reinstallAgent()
|
reinstallAgent()
|
||||||
} else if !agentStatusChecker.running && !agentStatusChecker.developmentBuild {
|
} else if !agentStatusChecker.running && !agentStatusChecker.developmentBuild {
|
||||||
@@ -89,7 +92,6 @@ struct Secretive: App {
|
|||||||
extension Secretive {
|
extension Secretive {
|
||||||
|
|
||||||
private func reinstallAgent() {
|
private func reinstallAgent() {
|
||||||
justUpdatedChecker.check()
|
|
||||||
Task {
|
Task {
|
||||||
_ = await LaunchAgentController().install()
|
_ = await LaunchAgentController().install()
|
||||||
try? await Task.sleep(for: .seconds(1))
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
|||||||
@@ -1,23 +1,33 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
protocol JustUpdatedCheckerProtocol: Observable {
|
@MainActor protocol JustUpdatedCheckerProtocol: Observable {
|
||||||
var justUpdated: Bool { get }
|
var justUpdatedBuild: Bool { get }
|
||||||
|
var justUpdatedOS: Bool { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Observable class JustUpdatedChecker: JustUpdatedCheckerProtocol {
|
@Observable @MainActor class JustUpdatedChecker: JustUpdatedCheckerProtocol {
|
||||||
|
|
||||||
var justUpdated: Bool = false
|
var justUpdatedBuild: Bool = false
|
||||||
|
var justUpdatedOS: Bool = false
|
||||||
|
|
||||||
init() {
|
nonisolated init() {
|
||||||
|
Task { @MainActor in
|
||||||
check()
|
check()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func check() {
|
private func check() {
|
||||||
let lastBuild = UserDefaults.standard.object(forKey: Constants.previousVersionUserDefaultsKey) as? String ?? "None"
|
let lastBuild = UserDefaults.standard.object(forKey: Constants.previousVersionUserDefaultsKey) as? String
|
||||||
|
let lastOS = UserDefaults.standard.object(forKey: Constants.previousOSVersionUserDefaultsKey) as? String
|
||||||
let currentBuild = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String
|
let currentBuild = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String
|
||||||
|
let osRaw = ProcessInfo.processInfo.operatingSystemVersion
|
||||||
|
let currentOS = "\(osRaw.majorVersion).\(osRaw.minorVersion).\(osRaw.patchVersion)"
|
||||||
UserDefaults.standard.set(currentBuild, forKey: Constants.previousVersionUserDefaultsKey)
|
UserDefaults.standard.set(currentBuild, forKey: Constants.previousVersionUserDefaultsKey)
|
||||||
justUpdated = lastBuild != currentBuild
|
UserDefaults.standard.set(currentOS, forKey: Constants.previousOSVersionUserDefaultsKey)
|
||||||
|
justUpdatedBuild = lastBuild != currentBuild
|
||||||
|
// To prevent this showing on first lauch for every user, only show if lastBuild is non-nil.
|
||||||
|
justUpdatedOS = lastBuild != nil && lastOS != currentOS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -28,6 +38,7 @@ extension JustUpdatedChecker {
|
|||||||
|
|
||||||
enum Constants {
|
enum Constants {
|
||||||
static let previousVersionUserDefaultsKey = "com.maxgoedjen.Secretive.lastBuild"
|
static let previousVersionUserDefaultsKey = "com.maxgoedjen.Secretive.lastBuild"
|
||||||
|
static let previousOSVersionUserDefaultsKey = "com.maxgoedjen.Secretive.lastOS"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,4 +9,17 @@ extension URL {
|
|||||||
static var socketPath: String {
|
static var socketPath: String {
|
||||||
URL.agentHomeURL.appendingPathComponent("socket.ssh").path()
|
URL.agentHomeURL.appendingPathComponent("socket.ssh").path()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
|
||||||
|
var normalizedPathAndFolder: (String, String) {
|
||||||
|
// All foundation-based normalization methods replace this with the container directly.
|
||||||
|
let processedPath = replacingOccurrences(of: "~", with: "/Users/\(NSUserName())")
|
||||||
|
let url = URL(filePath: processedPath)
|
||||||
|
let folder = url.deletingLastPathComponent().path()
|
||||||
|
return (processedPath, folder)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,22 +9,7 @@
|
|||||||
<key>Website</key>
|
<key>Website</key>
|
||||||
<string>https://github.com/maxgoedjen/secretive</string>
|
<string>https://github.com/maxgoedjen/secretive</string>
|
||||||
<key>Connections</key>
|
<key>Connections</key>
|
||||||
<array>
|
<array/>
|
||||||
<dict>
|
|
||||||
<key>IsIncoming</key>
|
|
||||||
<false/>
|
|
||||||
<key>Host</key>
|
|
||||||
<string>api.github.com</string>
|
|
||||||
<key>NetworkProtocol</key>
|
|
||||||
<string>TCP</string>
|
|
||||||
<key>Port</key>
|
|
||||||
<string>443</string>
|
|
||||||
<key>Purpose</key>
|
|
||||||
<string>Secretive checks GitHub for new versions and security updates.</string>
|
|
||||||
<key>DenyConsequences</key>
|
|
||||||
<string>If you deny these connections, you will not be notified about new versions and critical security updates.</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>Services</key>
|
<key>Services</key>
|
||||||
<array/>
|
<array/>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Brief
|
|||||||
|
|
||||||
var update: Release? = nil
|
var update: Release? = nil
|
||||||
|
|
||||||
let testBuild = false
|
let currentVersion = SemVer("0.0.0_preview")
|
||||||
|
|
||||||
init(update: Update = .none) {
|
init(update: Update = .none) {
|
||||||
switch update {
|
switch update {
|
||||||
|
|||||||
@@ -40,10 +40,7 @@ struct ConfigurationItemView<Content: View>: View {
|
|||||||
.buttonStyle(.borderless)
|
.buttonStyle(.borderless)
|
||||||
case .revealInFinder(let rawPath):
|
case .revealInFinder(let rawPath):
|
||||||
Button(.revealInFinderButton, systemImage: "folder") {
|
Button(.revealInFinderButton, systemImage: "folder") {
|
||||||
// All foundation-based normalization methods replace this with the container directly.
|
let (processedPath, folder) = rawPath.normalizedPathAndFolder
|
||||||
let processedPath = rawPath.replacingOccurrences(of: "~", with: "/Users/\(NSUserName())")
|
|
||||||
let url = URL(filePath: processedPath)
|
|
||||||
let folder = url.deletingLastPathComponent().path()
|
|
||||||
NSWorkspace.shared.selectFile(processedPath, inFileViewerRootedAtPath: folder)
|
NSWorkspace.shared.selectFile(processedPath, inFileViewerRootedAtPath: folder)
|
||||||
}
|
}
|
||||||
.labelStyle(.iconOnly)
|
.labelStyle(.iconOnly)
|
||||||
|
|||||||
@@ -21,9 +21,10 @@ struct SetupView: View {
|
|||||||
StepView(
|
StepView(
|
||||||
title: .setupAgentTitle,
|
title: .setupAgentTitle,
|
||||||
description: .setupAgentDescription,
|
description: .setupAgentDescription,
|
||||||
|
detail: .setupAgentActivityMonitorDescription,
|
||||||
systemImage: "lock.laptopcomputer",
|
systemImage: "lock.laptopcomputer",
|
||||||
) {
|
) {
|
||||||
setupButton(
|
SetupButton(
|
||||||
.setupAgentInstallButton,
|
.setupAgentInstallButton,
|
||||||
complete: installed,
|
complete: installed,
|
||||||
width: buttonWidth
|
width: buttonWidth
|
||||||
@@ -40,7 +41,7 @@ struct SetupView: View {
|
|||||||
description: .setupUpdatesDescription,
|
description: .setupUpdatesDescription,
|
||||||
systemImage: "network.badge.shield.half.filled",
|
systemImage: "network.badge.shield.half.filled",
|
||||||
) {
|
) {
|
||||||
setupButton(
|
SetupButton(
|
||||||
.setupUpdatesOkButton,
|
.setupUpdatesOkButton,
|
||||||
complete: updates,
|
complete: updates,
|
||||||
width: buttonWidth
|
width: buttonWidth
|
||||||
@@ -54,7 +55,7 @@ struct SetupView: View {
|
|||||||
description: .setupIntegrationsDescription,
|
description: .setupIntegrationsDescription,
|
||||||
systemImage: "firewall",
|
systemImage: "firewall",
|
||||||
) {
|
) {
|
||||||
setupButton(
|
SetupButton(
|
||||||
.setupIntegrationsButton,
|
.setupIntegrationsButton,
|
||||||
complete: integrations,
|
complete: integrations,
|
||||||
width: buttonWidth
|
width: buttonWidth
|
||||||
@@ -63,7 +64,7 @@ struct SetupView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onPreferenceChange(setupButton.WidthKey.self) { width in
|
.onPreferenceChange(SetupButton.WidthKey.self) { width in
|
||||||
buttonWidth = width
|
buttonWidth = width
|
||||||
}
|
}
|
||||||
.background(.white.opacity(0.1), in: RoundedRectangle(cornerRadius: 10))
|
.background(.white.opacity(0.1), in: RoundedRectangle(cornerRadius: 10))
|
||||||
@@ -88,7 +89,7 @@ struct SetupView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct setupButton: View {
|
struct SetupButton: View {
|
||||||
|
|
||||||
struct WidthKey: @MainActor PreferenceKey {
|
struct WidthKey: @MainActor PreferenceKey {
|
||||||
@MainActor static var defaultValue: CGFloat? = nil
|
@MainActor static var defaultValue: CGFloat? = nil
|
||||||
@@ -144,12 +145,20 @@ struct StepView<Content: View>: View {
|
|||||||
let title: LocalizedStringResource
|
let title: LocalizedStringResource
|
||||||
let icon: Image
|
let icon: Image
|
||||||
let description: LocalizedStringResource
|
let description: LocalizedStringResource
|
||||||
|
let detail: LocalizedStringResource?
|
||||||
let actions: Content
|
let actions: Content
|
||||||
|
|
||||||
init(title: LocalizedStringResource, description: LocalizedStringResource, systemImage: String, actions: () -> Content) {
|
init(
|
||||||
|
title: LocalizedStringResource,
|
||||||
|
description: LocalizedStringResource,
|
||||||
|
detail: LocalizedStringResource? = nil,
|
||||||
|
systemImage: String,
|
||||||
|
actions: () -> Content
|
||||||
|
) {
|
||||||
self.title = title
|
self.title = title
|
||||||
self.icon = Image(systemName: systemImage)
|
self.icon = Image(systemName: systemImage)
|
||||||
self.description = description
|
self.description = description
|
||||||
|
self.detail = detail
|
||||||
self.actions = actions()
|
self.actions = actions()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +174,11 @@ struct StepView<Content: View>: View {
|
|||||||
Text(title)
|
Text(title)
|
||||||
.bold()
|
.bold()
|
||||||
Text(description)
|
Text(description)
|
||||||
|
if let detail {
|
||||||
|
Text(detail)
|
||||||
|
.font(.callout)
|
||||||
|
.italic()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Spacer(minLength: 20)
|
Spacer(minLength: 20)
|
||||||
actions
|
actions
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
Section {
|
Section {
|
||||||
TextField(String(localized: .createSecretNameLabel), text: $name, prompt: Text(.createSecretNamePlaceholder))
|
TextField(String(localized: .createSecretNameLabel), text: $name, prompt: Text(.createSecretNamePlaceholder))
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
Picker(.createSecretRequireAuthenticationTitle, selection: $authenticationRequirement) {
|
Picker(.createSecretProtectionLevelTitle, selection: $authenticationRequirement) {
|
||||||
ForEach(authenticationOptions) { option in
|
ForEach(authenticationOptions) { option in
|
||||||
HStack {
|
HStack {
|
||||||
switch option {
|
switch option {
|
||||||
@@ -66,7 +66,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
Text(.createSecretBiometryCurrentWarning)
|
Text(.createSecretBiometryCurrentWarning)
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
.padding(.vertical, 3)
|
.padding(.vertical, 3)
|
||||||
.background(.red.opacity(0.5), in: RoundedRectangle(cornerRadius: 5))
|
.boxBackground(color: .red)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
Text(.createSecretMldsaWarning)
|
Text(.createSecretMldsaWarning)
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
.padding(.vertical, 3)
|
.padding(.vertical, 3)
|
||||||
.background(.red.opacity(0.5), in: RoundedRectangle(cornerRadius: 5))
|
.boxBackground(color: .orange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ struct EditSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
|
|
||||||
let store: StoreType
|
let store: StoreType
|
||||||
let secret: StoreType.SecretType
|
let secret: StoreType.SecretType
|
||||||
let dismissalBlock: (_ renamed: Bool) -> ()
|
|
||||||
|
|
||||||
@State private var name: String
|
@State private var name: String
|
||||||
@State private var publicKeyAttribution: String
|
@State private var publicKeyAttribution: String
|
||||||
@State var errorText: String?
|
@State var errorText: String?
|
||||||
|
|
||||||
init(store: StoreType, secret: StoreType.SecretType, dismissalBlock: @escaping (Bool) -> ()) {
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
|
init(store: StoreType, secret: StoreType.SecretType) {
|
||||||
self.store = store
|
self.store = store
|
||||||
self.secret = secret
|
self.secret = secret
|
||||||
self.dismissalBlock = dismissalBlock
|
|
||||||
name = secret.name
|
name = secret.name
|
||||||
publicKeyAttribution = secret.publicKeyAttribution ?? ""
|
publicKeyAttribution = secret.publicKeyAttribution ?? ""
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ struct EditSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Button(.editCancelButton) {
|
Button(.editCancelButton) {
|
||||||
dismissalBlock(false)
|
dismiss()
|
||||||
}
|
}
|
||||||
.keyboardShortcut(.cancelAction)
|
.keyboardShortcut(.cancelAction)
|
||||||
Button(.editSaveButton, action: rename)
|
Button(.editSaveButton, action: rename)
|
||||||
@@ -58,7 +58,7 @@ struct EditSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
try await store.update(secret: secret, name: name, attributes: attributes)
|
try await store.update(secret: secret, name: name, attributes: attributes)
|
||||||
dismissalBlock(true)
|
dismiss()
|
||||||
} catch {
|
} catch {
|
||||||
errorText = error.localizedDescription
|
errorText = error.localizedDescription
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ struct EmptyStoreImmutableView: View {
|
|||||||
|
|
||||||
struct EmptyStoreModifiableView: View {
|
struct EmptyStoreModifiableView: View {
|
||||||
|
|
||||||
|
@Environment(\.justUpdatedChecker) var justUpdatedChecker
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { windowGeometry in
|
GeometryReader { windowGeometry in
|
||||||
VStack {
|
VStack {
|
||||||
@@ -51,6 +53,21 @@ struct EmptyStoreModifiableView: View {
|
|||||||
}.frame(height: (windowGeometry.size.height/2) - 20).padding()
|
}.frame(height: (windowGeometry.size.height/2) - 20).padding()
|
||||||
Text(.emptyStoreModifiableClickHereTitle).bold()
|
Text(.emptyStoreModifiableClickHereTitle).bold()
|
||||||
Text(.emptyStoreModifiableClickHereDescription)
|
Text(.emptyStoreModifiableClickHereDescription)
|
||||||
|
if justUpdatedChecker.justUpdatedOS {
|
||||||
|
Spacer()
|
||||||
|
.frame(height: 20)
|
||||||
|
VStack(spacing: 10) {
|
||||||
|
Text(.emptyStoreModifiableEmptyOsWarningTitle)
|
||||||
|
.font(.title2)
|
||||||
|
.bold()
|
||||||
|
Text(.emptyStoreModifiableEmptyOsWarningDescription)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
.bold()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.boxBackground(color: .orange)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}.frame(maxWidth: .infinity, maxHeight: .infinity)
|
}.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
}
|
}
|
||||||
@@ -61,6 +78,10 @@ struct EmptyStoreModifiableView: View {
|
|||||||
#Preview {
|
#Preview {
|
||||||
EmptyStoreImmutableView()
|
EmptyStoreImmutableView()
|
||||||
}
|
}
|
||||||
|
#Preview {
|
||||||
|
EmptyStoreImmutableView()
|
||||||
|
// .environment(\.justUpdatedChecker, <#T##value: V##V#>)
|
||||||
|
}
|
||||||
#Preview {
|
#Preview {
|
||||||
EmptyStoreModifiableView()
|
EmptyStoreModifiableView()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ struct SecretDetailView<SecretType: Secret>: View {
|
|||||||
CopyableView(title: .secretDetailPublicKeyLabel, image: Image(systemName: "key"), text: keyString)
|
CopyableView(title: .secretDetailPublicKeyLabel, image: Image(systemName: "key"), text: keyString)
|
||||||
Spacer()
|
Spacer()
|
||||||
.frame(height: 20)
|
.frame(height: 20)
|
||||||
CopyableView(title: .secretDetailPublicKeyPathLabel, image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.publicKeyPath(for: secret))
|
CopyableView(title: .secretDetailPublicKeyPathLabel, image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.publicKeyPath(for: secret), showRevealInFinder: true)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,15 +41,12 @@ struct SecretListItemView: View {
|
|||||||
deletedSecret(secret)
|
deletedSecret(secret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $isRenaming) {
|
.sheet(isPresented: $isRenaming, onDismiss: {
|
||||||
if let modifiable = store as? AnySecretStoreModifiable {
|
|
||||||
EditSecretView(store: modifiable, secret: secret) { renamed in
|
|
||||||
isRenaming = false
|
|
||||||
if renamed {
|
|
||||||
renamedSecret(secret)
|
renamedSecret(secret)
|
||||||
|
}, content: {
|
||||||
|
if let modifiable = store as? AnySecretStoreModifiable {
|
||||||
|
EditSecretView(store: modifiable, secret: secret)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ struct StoreListView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func secretRenamed(secret: AnySecret) {
|
private func secretRenamed(secret: AnySecret) {
|
||||||
// Toggle so name updates in list.
|
// Pull new version from store, so we get all updated attributes
|
||||||
activeSecret = nil
|
activeSecret = nil
|
||||||
activeSecret = secret
|
activeSecret = storeList.allSecrets.first(where: { $0.id == secret.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -28,7 +28,7 @@ struct StoreListView: View {
|
|||||||
store: store,
|
store: store,
|
||||||
secret: secret,
|
secret: secret,
|
||||||
deletedSecret: secretDeleted,
|
deletedSecret: secretDeleted,
|
||||||
renamedSecret: secretRenamed
|
renamedSecret: secretRenamed,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,11 @@ struct StoreListView: View {
|
|||||||
// Do this to avoid a blip.
|
// Do this to avoid a blip.
|
||||||
SecretDetailView(secret: nextDefaultSecret)
|
SecretDetailView(secret: nextDefaultSecret)
|
||||||
} else {
|
} else {
|
||||||
EmptyStoreView(store: storeList.modifiableStore ?? storeList.stores.first)
|
if let modifiable = storeList.modifiableStore, modifiable.isAvailable {
|
||||||
|
EmptyStoreView(store: modifiable)
|
||||||
|
} else {
|
||||||
|
EmptyStoreView(store: storeList.stores.first(where: \.isAvailable))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationSplitViewStyle(.balanced)
|
.navigationSplitViewStyle(.balanced)
|
||||||
|
|||||||
32
Sources/Secretive/Views/Styles/BoxBackgroundStyle.swift
Normal file
32
Sources/Secretive/Views/Styles/BoxBackgroundStyle.swift
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct BoxBackgroundModifier: ViewModifier {
|
||||||
|
|
||||||
|
let color: Color
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.background {
|
||||||
|
RoundedRectangle(cornerRadius: 5)
|
||||||
|
.fill(color.opacity(0.3))
|
||||||
|
.stroke(color, lineWidth: 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
|
||||||
|
func boxBackground(color: Color) -> some View {
|
||||||
|
modifier(BoxBackgroundModifier(color: color))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
Text("Hello")
|
||||||
|
.boxBackground(color: .red)
|
||||||
|
.padding()
|
||||||
|
Text("Hello")
|
||||||
|
.boxBackground(color: .orange)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
@@ -55,27 +55,19 @@ extension ContentView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var needsSetup: Bool {
|
|
||||||
runningSetup || !hasRunSetup
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Item either showing a "everything's good, here's more info" or "something's wrong, re-run setup" message
|
/// Item either showing a "everything's good, here's more info" or "something's wrong, re-run setup" message
|
||||||
/// These two are mutually exclusive
|
/// These two are mutually exclusive
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var runningOrRunSetupView: some View {
|
var runningOrRunSetupView: some View {
|
||||||
if needsSetup {
|
|
||||||
setupNoticeView
|
|
||||||
} else {
|
|
||||||
agentStatusToolbarView
|
agentStatusToolbarView
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var updateNoticeContent: (LocalizedStringResource, Color)? {
|
var updateNoticeContent: (LocalizedStringResource, Color)? {
|
||||||
guard let update = updater.update else { return nil }
|
guard let update = updater.update else { return nil }
|
||||||
if update.critical {
|
if update.critical {
|
||||||
return (.updateCriticalNoticeTitle, .red)
|
return (.updateCriticalNoticeTitle, .red)
|
||||||
} else {
|
} else {
|
||||||
if updater.testBuild {
|
if updater.currentVersion.isTestBuild {
|
||||||
return (.updateTestNoticeTitle, .blue)
|
return (.updateTestNoticeTitle, .blue)
|
||||||
} else {
|
} else {
|
||||||
return (.updateNormalNoticeTitle, .orange)
|
return (.updateNormalNoticeTitle, .orange)
|
||||||
@@ -95,10 +87,25 @@ extension ContentView {
|
|||||||
})
|
})
|
||||||
.buttonStyle(ToolbarButtonStyle(color: color))
|
.buttonStyle(ToolbarButtonStyle(color: color))
|
||||||
.sheet(item: $selectedUpdate) { update in
|
.sheet(item: $selectedUpdate) { update in
|
||||||
|
VStack {
|
||||||
|
if updater.currentVersion.isTestBuild {
|
||||||
|
VStack {
|
||||||
|
if let description = updater.currentVersion.previewDescription {
|
||||||
|
Text(description)
|
||||||
|
}
|
||||||
|
Link(destination: URL(string: "https://github.com/maxgoedjen/secretive/actions/workflows/nightly.yml")!) {
|
||||||
|
Button(.updaterDownloadLatestNightlyButton) {}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.primaryButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
UpdateDetailView(update: update)
|
UpdateDetailView(update: update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var newItemView: some View {
|
var newItemView: some View {
|
||||||
@@ -119,19 +126,6 @@ extension ContentView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
var setupNoticeView: some View {
|
|
||||||
Button(action: {
|
|
||||||
runningSetup = true
|
|
||||||
}, label: {
|
|
||||||
if !hasRunSetup {
|
|
||||||
Text(.agentSetupNoticeTitle)
|
|
||||||
.font(.headline)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.buttonStyle(ToolbarButtonStyle(color: .orange))
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var agentStatusToolbarView: some View {
|
var agentStatusToolbarView: some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ struct CopyableView: View {
|
|||||||
var title: LocalizedStringResource
|
var title: LocalizedStringResource
|
||||||
var image: Image
|
var image: Image
|
||||||
var text: String
|
var text: String
|
||||||
|
var showRevealInFinder = false
|
||||||
|
|
||||||
@State private var interactionState: InteractionState = .normal
|
@State private var interactionState: InteractionState = .normal
|
||||||
|
|
||||||
@@ -21,9 +22,12 @@ struct CopyableView: View {
|
|||||||
.foregroundColor(primaryTextColor)
|
.foregroundColor(primaryTextColor)
|
||||||
Spacer()
|
Spacer()
|
||||||
if interactionState != .normal {
|
if interactionState != .normal {
|
||||||
hoverIcon
|
HStack {
|
||||||
.bold()
|
if showRevealInFinder {
|
||||||
.textCase(.uppercase)
|
revealInFinderButton
|
||||||
|
}
|
||||||
|
copyButton
|
||||||
|
}
|
||||||
.foregroundColor(secondaryTextColor)
|
.foregroundColor(secondaryTextColor)
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
}
|
}
|
||||||
@@ -72,11 +76,18 @@ struct CopyableView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var hoverIcon: some View {
|
var copyButton: some View {
|
||||||
switch interactionState {
|
switch interactionState {
|
||||||
case .hovering:
|
case .hovering:
|
||||||
Image(systemName: "document.on.document")
|
Button(.copyableClickToCopyButton, systemImage: "document.on.document") {
|
||||||
.accessibilityLabel(String(localized: .copyableClickToCopyButton))
|
withAnimation {
|
||||||
|
// Button will eat the click, so we set interaction state manually.
|
||||||
|
interactionState = .clicking
|
||||||
|
}
|
||||||
|
copy()
|
||||||
|
}
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.buttonStyle(.borderless)
|
||||||
case .clicking:
|
case .clicking:
|
||||||
Image(systemName: "checkmark.circle.fill")
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.accessibilityLabel(String(localized: .copyableCopied))
|
.accessibilityLabel(String(localized: .copyableCopied))
|
||||||
@@ -85,6 +96,15 @@ struct CopyableView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var revealInFinderButton: some View {
|
||||||
|
Button(.revealInFinderButton, systemImage: "folder") {
|
||||||
|
let (processedPath, folder) = text.normalizedPathAndFolder
|
||||||
|
NSWorkspace.shared.selectFile(processedPath, inFileViewerRootedAtPath: folder)
|
||||||
|
}
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.buttonStyle(.borderless)
|
||||||
|
}
|
||||||
|
|
||||||
var primaryTextColor: Color {
|
var primaryTextColor: Color {
|
||||||
switch interactionState {
|
switch interactionState {
|
||||||
case .normal, .hovering, .dragging:
|
case .normal, .hovering, .dragging:
|
||||||
|
|||||||
11
Sources/SecretiveUpdater/Info.plist
Normal file
11
Sources/SecretiveUpdater/Info.plist
Normal file
@@ -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>
|
||||||
31
Sources/SecretiveUpdater/InternetAccessPolicy.plist
Normal file
31
Sources/SecretiveUpdater/InternetAccessPolicy.plist
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?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>ApplicationDescription</key>
|
||||||
|
<string>Secretive is an app for storing and managing SSH keys in the Secure Enclave</string>
|
||||||
|
<key>DeveloperName</key>
|
||||||
|
<string>Max Goedjen</string>
|
||||||
|
<key>Website</key>
|
||||||
|
<string>https://github.com/maxgoedjen/secretive</string>
|
||||||
|
<key>Connections</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>IsIncoming</key>
|
||||||
|
<false/>
|
||||||
|
<key>Host</key>
|
||||||
|
<string>api.github.com</string>
|
||||||
|
<key>NetworkProtocol</key>
|
||||||
|
<string>TCP</string>
|
||||||
|
<key>Port</key>
|
||||||
|
<string>443</string>
|
||||||
|
<key>Purpose</key>
|
||||||
|
<string>Secretive checks GitHub for new versions and security updates.</string>
|
||||||
|
<key>DenyConsequences</key>
|
||||||
|
<string>If you deny these connections, you will not be notified about new versions and critical security updates.</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>Services</key>
|
||||||
|
<array/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
17
Sources/SecretiveUpdater/SecretiveUpdater.swift
Normal file
17
Sources/SecretiveUpdater/SecretiveUpdater.swift
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import Foundation
|
||||||
|
import OSLog
|
||||||
|
import XPCWrappers
|
||||||
|
import Brief
|
||||||
|
|
||||||
|
final class SecretiveUpdater: NSObject, XPCProtocol {
|
||||||
|
|
||||||
|
enum Constants {
|
||||||
|
static let updateURL = URL(string: "https://api.github.com/repos/maxgoedjen/secretive/releases")!
|
||||||
|
}
|
||||||
|
|
||||||
|
func process(_: Data) async throws -> [Release] {
|
||||||
|
let (data, _) = try await URLSession.shared.data(from: Constants.updateURL)
|
||||||
|
return try JSONDecoder().decode([Release].self, from: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
7
Sources/SecretiveUpdater/main.swift
Normal file
7
Sources/SecretiveUpdater/main.swift
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import Foundation
|
||||||
|
import XPCWrappers
|
||||||
|
|
||||||
|
let delegate = XPCServiceDelegate(exportedObject: SecretiveUpdater())
|
||||||
|
let listener = NSXPCListener.service()
|
||||||
|
listener.delegate = delegate
|
||||||
|
listener.resume()
|
||||||
Reference in New Issue
Block a user