Compare commits

...

21 Commits

Author SHA1 Message Date
Max Goedjen
0e0f6b2924 Merge 2025-12-14 11:17:28 -08:00
Max Goedjen
845b1ec313 Reorder modifiers to fix context menus on macOS 14 (#774) 2025-12-14 04:29:06 +00:00
Max Goedjen
595de41f03 Update runners to 26.2 (#773) 2025-12-13 23:29:26 +00:00
Jamie
d82f404166 [fix]: eliminate race condition in SocketController (#769)
* [fix]: eliminate race condition in SocketController

* [refactor]: use sequence- rather than iterator-based iteration

* [fix]: remove now-superfluous await

---------

Co-authored-by: Max Goedjen <max.goedjen@gmail.com>
2025-12-06 15:27:45 -08:00
Max Goedjen
a3bfcb316c Add support for one-off builds instead of using nightly workflow (#768) 2025-11-29 21:40:29 +00:00
Max Goedjen
bba4fb9e7c Update runners to use Xcode 26.1 (#767)
* Update images to 26.1 builders

* Try running tests via spm again

* Revert "Try running tests via spm again"

This reverts commit ec9cb609dc.
2025-11-29 21:36:41 +00:00
Max Goedjen
32a1a0bca9 Use separate socket for debug builds (#766) 2025-11-29 21:32:34 +00:00
Max Goedjen
bb0b6d8dc3 Run filehandle listening methods in main actor directly vs jumping (#765) 2025-11-27 20:00:53 +00:00
Max Goedjen
c63d87cbec Fix bug where connection would be closed without returning unhandled response for unknown messages (#747) 2025-10-25 18:28:50 +00:00
Max Goedjen
65bc6c1a69 Fix allowedsigners formatting (#744) 2025-10-08 04:25:55 +00:00
Max Goedjen
275b6ef9bb Fix bug where agent could relaunch after being disabled (#743) 2025-10-07 20:54:38 -07:00
Max Goedjen
f13bc23991 Pull in updated localizations (#742) 2025-10-07 20:52:35 -07:00
Max Goedjen
3a67d59519 Fix key copying. (#738) 2025-10-01 07:00:51 +00:00
Max Goedjen
d9a3f0c813 Change how agent launch/relaunch is performed (#737) 2025-10-01 06:57:06 +00:00
Max Goedjen
84d5a56fb0 Zip parent directory to prevent double-zip/confusing attestation (#732)
* .

* Test

* Release

* Release

* Release

* -r

* ls

* Fix yml

* Path

* Path

* Path

* List

* Zip direct

* Zip direct

* Sha

* sha

* Zip direct

* Auth.

* .

* .

* .

* .

* .

* .
2025-09-28 01:02:45 +00:00
Max Goedjen
fd95771fed Dump 2025-09-26 00:19:32 -07:00
Daniel Néri
3bb0cc4a0e Fix attestation of release zip file (#731) 2025-09-26 07:15:11 +00:00
Max Goedjen
516e37fdde Fix primitive button style trigger (#727) 2025-09-23 05:27:45 +00:00
Max Goedjen
267fe4bf27 WIP 2025-09-20 17:43:51 -07:00
Max Goedjen
a0cf74f9dc WIP 2025-09-20 17:43:43 -07:00
Max Goedjen
940b6b1b86 Sketching out. 2025-09-19 20:47:58 -07:00
51 changed files with 1852 additions and 1244 deletions

View File

@@ -37,7 +37,7 @@ jobs:
build-mode: ${{ matrix.build-mode }}
- if: matrix.build-mode == 'manual'
name: "Select Xcode"
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
run: sudo xcrun xcode-select -s /Applications/Xcode_26.2.app
- if: matrix.build-mode == 'manual'
name: "Build"
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO

View File

@@ -3,7 +3,6 @@ name: Nightly
on:
schedule:
- cron: "0 8 * * *"
workflow_dispatch:
jobs:
build:
@@ -12,6 +11,7 @@ jobs:
id-token: write
contents: write
attestations: write
actions: read
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
@@ -25,7 +25,7 @@ jobs:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
run: ./.github/scripts/signing.sh
- name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
run: sudo xcrun xcode-select -s /Applications/Xcode_26.2.app
- name: Update Build Number
env:
RUN_ID: ${{ github.run_id }}
@@ -36,20 +36,27 @@ jobs:
sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Config/Config.xcconfig
- name: Build
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
- name: Create ZIP
- name: Move to Artifact Folder
run: mkdir Artifact; cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact
- name: Upload App to Artifacts
id: upload
uses: actions/upload-artifact@v4
with:
name: Secretive
path: Artifact
- name: Download Zipped Artifact
id: download
env:
ZIP_ID: ${{ steps.upload.outputs.artifact-id }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ditto -c -k --sequesterRsrc --keepParent Archive.xcarchive/Products/Applications/Secretive.app ./Secretive.zip
curl -L -H "Authorization: Bearer $GITHUB_TOKEN" -L \
https://api.github.com/repos/maxgoedjen/secretive/actions/artifacts/$ZIP_ID/zip > Secretive.zip
- name: Notarize
env:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
run: xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
- name: Upload App to Artifacts
id: upload
uses: actions/upload-artifact@v4
with:
name: Secretive.zip
path: Secretive.zip
- name: Attest
id: attest
uses: actions/attest-build-provenance@v2

64
.github/workflows/oneoff.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: One-Off Build
on:
workflow_dispatch:
jobs:
build:
runs-on: macos-26
permissions:
id-token: write
contents: write
attestations: write
actions: read
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
- name: Setup Signing
env:
SIGNING_DATA: ${{ secrets.SIGNING_DATA }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
HOST_PROFILE_DATA: ${{ secrets.HOST_PROFILE_DATA }}
AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }}
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
run: ./.github/scripts/signing.sh
- name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26.2.app
- name: Update Build Number
env:
RUN_ID: ${{ github.run_id }}
run: |
DATE=$(date "+%Y-%m-%d")
sed -i '' -e "s/GITHUB_CI_VERSION/0.0.0_oneoff-$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_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Config/Config.xcconfig
- name: Build
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
- name: Move to Artifact Folder
run: mkdir Artifact; cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact
- name: Upload App to Artifacts
id: upload
uses: actions/upload-artifact@v4
with:
name: Secretive
path: Artifact
- name: Download Zipped Artifact
id: download
env:
ZIP_ID: ${{ steps.upload.outputs.artifact-id }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
curl -L -H "Authorization: Bearer $GITHUB_TOKEN" -L \
https://api.github.com/repos/maxgoedjen/secretive/actions/artifacts/$ZIP_ID/zip > Secretive.zip
- name: Notarize
env:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
run: xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
- name: Attest
id: attest
uses: actions/attest-build-provenance@v2
with:
subject-name: "Secretive.zip"
subject-digest: sha256:${{ steps.upload.outputs.artifact-digest }}

View File

@@ -22,7 +22,7 @@ jobs:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
run: ./.github/scripts/signing.sh
- name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
run: sudo xcrun xcode-select -s /Applications/Xcode_26.2.app
- name: Test
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test
# SPM doesn't seem to pick up on the tests currently?
@@ -32,6 +32,7 @@ jobs:
id-token: write
contents: write
attestations: write
actions: read
runs-on: macos-26
timeout-minutes: 10
steps:
@@ -46,7 +47,7 @@ jobs:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
run: ./.github/scripts/signing.sh
- name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
run: sudo xcrun xcode-select -s /Applications/Xcode_26.2.app
- name: Update Build Number
env:
TAG_NAME: ${{ github.ref }}
@@ -58,34 +59,40 @@ jobs:
sed -i '' -e "s/GITHUB_BUILD_URL/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Config/Config.xcconfig
- name: Build
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
- name: Create ZIP
run: |
ditto -c -k --sequesterRsrc --keepParent Archive.xcarchive/Products/Applications/Secretive.app ./Secretive.zip
- name: Notarize
env:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
run: xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
- name: Move to Artifact Folder
run: mkdir Artifact; cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact
- name: Upload App to Artifacts
id: upload
uses: actions/upload-artifact@v4
with:
name: Secretive.zip
path: Secretive.zip
path: Artifact
- name: Download Zipped Artifact
id: download
env:
ZIP_ID: ${{ steps.upload.outputs.artifact-id }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
curl -L -H "Authorization: Bearer $GITHUB_TOKEN" -L \
https://api.github.com/repos/maxgoedjen/secretive/actions/artifacts/$ZIP_ID/zip > Secretive.zip
- name: Notarize
env:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
run: xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
- name: Attest
id: attest
uses: actions/attest-build-provenance@v2
with:
subject-name: "Secretive.zip"
subject-digest: sha256:${{ steps.upload.outputs.artifact-digest }}
subject-path: "Secretive.zip"
- name: Create Release
run: |
sed -i.tmp "s/RUN_ID/$RUN_ID/g" .github/templates/release.md
sed -i.tmp "s/ATTESTATION_ID/$ATTESTATION_ID/g" .github/templates/release.md
gh release create $TAG_NAME -d -F .github/templates/release.md
gh release upload $TAG_NAME Secretive.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ github.ref }}
RUN_ID: ${{ github.run_id }}
ATTESTATION_ID: ${{ steps.attest.outputs.attestation-id }}
run: |
sed -i.tmp "s/RUN_ID/$RUN_ID/g" .github/templates/release.md
sed -i.tmp "s/ATTESTATION_ID/$ATTESTATION_ID/g" .github/templates/release.md
gh release create $TAG_NAME -d -F .github/templates/release.md
gh release upload $TAG_NAME Secretive.zip

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- uses: actions/checkout@v5
- name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
run: sudo xcrun xcode-select -s /Applications/Xcode_26.2.app
- name: Test Main Packages
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test
# SPM doesn't seem to pick up on the tests currently?

View File

@@ -19,15 +19,24 @@ let package = Package(
.library(
name: "SmartCardSecretKit",
targets: ["SmartCardSecretKit"]),
.library(
name: "CertificateKit",
targets: ["CertificateKit"]),
.library(
name: "SecretAgentKit",
targets: ["SecretAgentKit", "XPCWrappers"]),
targets: ["SecretAgentKit"]),
.library(
name: "Common",
targets: ["Common"]),
.library(
name: "Brief",
targets: ["Brief"]),
.library(
name: "XPCWrappers",
targets: ["XPCWrappers"]),
.library(
name: "SSHProtocolKit",
targets: ["SSHProtocolKit"]),
],
dependencies: [
],
@@ -55,9 +64,15 @@ let package = Package(
resources: [localization],
swiftSettings: swiftSettings,
),
.target(
name: "CertificateKit",
dependencies: ["SecretKit", "SSHProtocolKit"],
resources: [localization],
// swiftSettings: swiftSettings,
),
.target(
name: "SecretAgentKit",
dependencies: ["SecretKit"],
dependencies: ["SecretKit", "SSHProtocolKit", "CertificateKit"],
resources: [localization],
swiftSettings: swiftSettings,
),
@@ -65,9 +80,25 @@ let package = Package(
name: "SecretAgentKitTests",
dependencies: ["SecretAgentKit"],
),
.target(
name: "SSHProtocolKit",
dependencies: ["SecretKit"],
resources: [localization],
// swiftSettings: swiftSettings,
),
.testTarget(
name: "SSHProtocolKitTests",
dependencies: ["SSHProtocolKit"],
),
.target(
name: "Common",
dependencies: [],
resources: [localization],
swiftSettings: swiftSettings,
),
.target(
name: "Brief",
dependencies: ["XPCWrappers"],
dependencies: ["XPCWrappers", "SSHProtocolKit"],
resources: [localization],
swiftSettings: swiftSettings,
),

File diff suppressed because it is too large Load Diff

View File

@@ -75,14 +75,14 @@ extension Updater {
.reversed()
.filter({ !$0.prerelease })
.first(where: { $0.minimumOSVersion <= osVersion }) else { return }
guard !userIgnored(release: release) else { return }
guard !release.prerelease else { return }
let latestVersion = SemVer(release.name)
if latestVersion > currentVersion {
// guard !userIgnored(release: release) else { return }
// guard !release.prerelease else { return }
// let latestVersion = SemVer(release.name)
// if latestVersion > currentVersion {
await MainActor.run {
state.update = release
}
}
// }
}
/// Checks whether the user has ignored a release.

View File

@@ -0,0 +1,15 @@
import Foundation
//import SecretKit
//extension SecureEnclave {
public struct Certificate: Sendable, Codable, Equatable, Hashable, Identifiable {
public var id: Int { hashValue }
public var type: String
public let name: String?
public let data: Data
}
//}

View File

@@ -0,0 +1,91 @@
import Foundation
import Security
import CryptoTokenKit
import CryptoKit
import os
import SSHProtocolKit
public struct CertificateKitMigrator {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.migration", category: "CertificateKitMigrator")
let directory: URL
/// Initializes a PublicKeyFileStoreController.
public init(homeDirectory: URL) {
directory = homeDirectory.appending(component: "PublicKeys")
}
@MainActor public func migrate() throws {
let fileCerts = try FileManager.default
.contentsOfDirectory(atPath: directory.path())
.filter { $0.hasSuffix("-cert.pub") }
Task {
for path in fileCerts {
let url = directory.appending(component: path)
let data = try! Data(contentsOf: url)
// let parser = try! await XPCCertificateParser()
let parser = OpenSSHCertificateParser()
let cert = try! await parser.parse(data: data)
print(cert)
// let secret = storeList.allSecrets.first { secret in
// secret.name == cert.name
// }
// guard let secret = secret ?? storeList.allSecrets.first else { return }
// print(cert.data.formatted(.hex()))
// certificateStore.saveCertificate(cert.data, for: secret)
print(cert)
}
}
// let privateAttributes = KeychainDictionary([
// kSecClass: kSecClassKey,
// kSecAttrKeyType: Constants.oldKeyType,
// kSecAttrApplicationTag: SecureEnclave.Store.Constants.keyTag,
// kSecAttrKeyClass: kSecAttrKeyClassPrivate,
// kSecReturnRef: true,
// kSecMatchLimit: kSecMatchLimitAll,
// kSecReturnAttributes: true
// ])
// var privateUntyped: CFTypeRef?
// unsafe SecItemCopyMatching(privateAttributes, &privateUntyped)
// guard let privateTyped = privateUntyped as? [[CFString: Any]] else { return }
// let migratedPublicKeys = Set(store.secrets.map(\.publicKey))
// var migratedAny = false
// for key in privateTyped {
// let name = key[kSecAttrLabel] as? String ?? String(localized: .unnamedSecret)
// let id = key[kSecAttrApplicationLabel] as! Data
// guard !id.contains(Constants.migrationMagicNumber) else {
// logger.log("Skipping \(name), already migrated.")
// continue
// }
// let ref = key[kSecValueRef] as! SecKey
// let attributes = SecKeyCopyAttributes(ref) as! [CFString: Any]
// let tokenObjectID = unsafe attributes[Constants.tokenObjectID] as! Data
// let accessControl = attributes[kSecAttrAccessControl] as! SecAccessControl
// // Best guess.
// let auth: AuthenticationRequirement = String(describing: accessControl)
// .contains("DeviceOwnerAuthentication") ? .presenceRequired : .unknown
// do {
// let parsed = try CryptoKit.SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: tokenObjectID)
// let secret = Secret(id: UUID().uuidString, name: name, publicKey: parsed.publicKey.x963Representation, attributes: Attributes(keyType: .init(algorithm: .ecdsa, size: 256), authentication: auth))
// guard !migratedPublicKeys.contains(parsed.publicKey.x963Representation) else {
// logger.log("Skipping \(name), public key already present. Marking as migrated.")
// markMigrated(secret: secret, oldID: id)
// continue
// }
// logger.log("Migrating \(name).")
// try store.saveKey(tokenObjectID, name: name, attributes: secret.attributes)
// logger.log("Migrated \(name).")
// markMigrated(secret: secret, oldID: id)
// migratedAny = true
// } catch {
// logger.error("Failed to migrate \(name): \(error.localizedDescription).")
// }
// }
// if migratedAny {
// store.reloadSecrets()
// }
}
}

View File

@@ -0,0 +1,134 @@
import Foundation
import Observation
import Security
import os
import SecretKit
@Observable public final class CertificateStore {
@MainActor private var certificates: [Certificate] = []
/// Initializes a Store.
@MainActor public init() {
loadCertificates()
Task {
// for await note in DistributedNotificationCenter.default().notifications(named: .certificateStoreUpdated) {
// guard Constants.notificationToken != (note.object as? String) else {
// // Don't reload if we're the ones triggering this by reloading.
// continue
// }
// loadCertificates()
// }
}
}
@MainActor public func reloadCertificates() {
let before = certificates
certificates.removeAll()
loadCertificates()
if certificates != before {
// NotificationCenter.default.post(name: .certificateStoreReloaded, object: self)
// DistributedNotificationCenter.default().postNotificationName(.certificateStoreUpdated, object: Constants.notificationToken, deliverImmediately: true)
}
}
@MainActor public func saveCertificate(_ data: Data, for secret: any Secret) {
let certificate = SecCertificateCreateWithData(nil, data as CFData)
print(certificate as Any)
}
@MainActor public func certificates(for secret: any Secret) -> [Certificate] {
[]
}
}
extension CertificateStore {
/// Loads all certificates from the store.
@MainActor private func loadCertificates() {
// let queryAttributes = KeychainDictionary([
// kSecClass: Constants.keyClass,
// kSecAttrService: Constants.keyTag,
// kSecUseDataProtectionKeychain: true,
// kSecReturnData: true,
// kSecMatchLimit: kSecMatchLimitAll,
// kSecReturnAttributes: true
// ])
// var untyped: CFTypeRef?
// unsafe SecItemCopyMatching(queryAttributes, &untyped)
// guard let typed = untyped as? [[CFString: Any]] else { return }
// let wrapped: [SecureEnclave.Certificates] = typed.compactMap {
// do {
// let name = $0[kSecAttrLabel] as? String ?? String(localized: "unnamed_certificate")
// guard let attributesData = $0[kSecAttrGeneric] as? Data,
// let id = $0[kSecAttrAccount] as? String else {
// throw MissingAttributesError()
// }
// let attributes = try JSONDecoder().decode(Attributes.self, from: attributesData)
// let keyData = $0[kSecValueData] as! Data
// let publicKey: Data
// switch attributes.keyType {
// case .ecdsa256:
// let key = try CryptoKit.SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: keyData)
// publicKey = key.publicKey.x963Representation
// case .mldsa65:
// guard #available(macOS 26.0, *) else { throw UnsupportedAlgorithmError() }
// let key = try CryptoKit.SecureEnclave.MLDSA65.PrivateKey(dataRepresentation: keyData)
// publicKey = key.publicKey.rawRepresentation
// case .mldsa87:
// guard #available(macOS 26.0, *) else { throw UnsupportedAlgorithmError() }
// let key = try CryptoKit.SecureEnclave.MLDSA87.PrivateKey(dataRepresentation: keyData)
// publicKey = key.publicKey.rawRepresentation
// default:
// throw UnsupportedAlgorithmError()
// }
// return SecureEnclave.Certificates(id: id, name: name, publicKey: publicKey, attributes: attributes)
// } catch {
// return nil
// }
// }
// certificates.append(contentsOf: wrapped)
}
/// Saves a public key.
/// - Parameters:
/// - key: The data representation key to save.
/// - name: A user-facing name for the key.
/// - attributes: Attributes of the key.
/// - Note: Despite the name, the "Data" of the key is _not_ actual key material. This is an opaque data representation that the SEP can manipulate.
// @discardableResult
// func saveKey(_ key: Data, name: String, attributes: Attributes) throws -> String {
// let attributes = try JSONEncoder().encode(attributes)
// let id = UUID().uuidString
// let keychainAttributes = KeychainDictionary([
// kSecClass: Constants.keyClass,
// kSecAttrService: Constants.keyTag,
// kSecUseDataProtectionKeychain: true,
// kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
// kSecAttrAccount: id,
// kSecValueData: key,
// kSecAttrLabel: name,
// kSecAttrGeneric: attributes
// ])
// let status = SecItemAdd(keychainAttributes, nil)
// if status != errSecSuccess {
// throw KeychainError(statusCode: status)
// }
// return id
// }
}
extension CertificateStore {
enum Constants {
static let keyClass = kSecClassCertificate as String
static let keyTag = Data("com.maxgoedjen.certificateive.certificate".utf8)
static let notificationToken = UUID().uuidString
}
struct UnsupportedAlgorithmError: Error {}
}

View File

@@ -2,19 +2,23 @@ import Foundation
extension URL {
static var agentHomeURL: URL {
public static var agentHomeURL: URL {
URL(fileURLWithPath: URL.homeDirectory.path().replacingOccurrences(of: Bundle.hostBundleID, with: Bundle.agentBundleID))
}
static var socketPath: String {
public static var socketPath: String {
#if DEBUG
URL.agentHomeURL.appendingPathComponent("socket-debug.ssh").path()
#else
URL.agentHomeURL.appendingPathComponent("socket.ssh").path()
#endif
}
}
extension String {
var normalizedPathAndFolder: (String, String) {
public 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)

View File

@@ -0,0 +1,37 @@
import Foundation
import CryptoKit
public struct HexDataStyle<SequenceType: Sequence>: Hashable, Codable {
let separator: String
public init(separator: String) {
self.separator = separator
}
}
extension HexDataStyle: FormatStyle where SequenceType.Element == UInt8 {
public func format(_ value: SequenceType) -> String {
value
.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }
.joined(separator: separator)
}
}
extension FormatStyle where Self == HexDataStyle<Data> {
public static func hex(separator: String = "") -> HexDataStyle<Data> {
HexDataStyle(separator: separator)
}
}
extension FormatStyle where Self == HexDataStyle<Insecure.MD5Digest> {
public static func hex(separator: String = ":") -> HexDataStyle<Insecure.MD5Digest> {
HexDataStyle(separator: separator)
}
}

View File

@@ -0,0 +1,102 @@
import Foundation
import OSLog
import CryptoKit
public struct OpenSSHCertificate: Sendable, Codable, Equatable, Hashable, Identifiable, CustomDebugStringConvertible {
public var id: Int { hashValue }
public var type: CertificateType
public let name: String?
public let data: Data
public var publicKey: Data
public var principals: [String]
public var keyID: String
public var serial: UInt64
public var validityRange: Range<Date>?
public var debugDescription: String {
"OpenSSH Certificate \(name, default: "Unnamed"): \(data.formatted(.hex()))"
}
}
extension OpenSSHCertificate {
public enum CertificateType: String, Sendable, Codable {
case ecdsa256 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
case ecdsa384 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
case nistp521 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
var keyIdentifier: String {
rawValue.replacingOccurrences(of: "-cert-v01@openssh.com", with: "")
}
}
}
public protocol OpenSSHCertificateParserProtocol {
func parse(data: Data) async throws -> OpenSSHCertificate
}
public struct OpenSSHCertificateParser: OpenSSHCertificateParserProtocol, Sendable {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive", category: "OpenSSHCertificateParser")
public init() {
}
public func parse(data: Data) throws(OpenSSHCertificateError) -> OpenSSHCertificate {
let string = String(decoding: data, as: UTF8.self)
var elements = string
.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: " ")
guard elements.count >= 2 else {
throw OpenSSHCertificateError.parsingFailed
}
let typeString = elements.removeFirst()
guard let type = OpenSSHCertificate.CertificateType(rawValue: typeString) else { throw .unsupportedType }
let encodedKey = elements.removeFirst()
guard let decoded = Data(base64Encoded: encodedKey) else {
throw OpenSSHCertificateError.parsingFailed
}
let name = elements.first
do {
let dataParser = OpenSSHReader(data: decoded)
_ = try dataParser.readNextChunkAsString() // Redundant key type
_ = try dataParser.readNextChunk() // Nonce
_ = try dataParser.readNextChunkAsString() // curve
let publicKey = try dataParser.readNextChunk()
let serialNumber = try dataParser.readNextBytes(as: UInt64.self, convertEndianness: true)
let role = try dataParser.readNextBytes(as: UInt32.self, convertEndianness: true)
let keyIdentifier = try dataParser.readNextChunkAsString()
let principalsReader = try dataParser.readNextChunkAsSubReader()
var principals: [String] = []
while !principalsReader.done {
try principals.append(principalsReader.readNextChunkAsString())
}
let validAfter = try dataParser.readNextBytes(as: UInt64.self, convertEndianness: true)
let validBefore = try dataParser.readNextBytes(as: UInt64.self, convertEndianness: true)
let validityRange = Date(timeIntervalSince1970: TimeInterval(validAfter))..<Date(timeIntervalSince1970: TimeInterval(validBefore))
return OpenSSHCertificate(
type: type,
name: name,
data: data,
publicKey: publicKey,
principals: principals,
keyID: keyIdentifier,
serial: serialNumber,
validityRange: validityRange
)
} catch {
throw .parsingFailed
}
}
}
public enum OpenSSHCertificateError: Error, Codable {
case unsupportedType
case parsingFailed
}

View File

@@ -1,5 +1,6 @@
import Foundation
import CryptoKit
import SecretKit
/// Generates OpenSSH representations of the public key sof secrets.
public struct OpenSSHPublicKeyWriter: Sendable {
@@ -49,9 +50,7 @@ public struct OpenSSHPublicKeyWriter: Sendable {
/// Generates an OpenSSH MD5 fingerprint string.
/// - Returns: OpenSSH MD5 fingerprint string.
public func openSSHMD5Fingerprint<SecretType: Secret>(secret: SecretType) -> String {
Insecure.MD5.hash(data: data(secret: secret))
.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }
.joined(separator: ":")
Insecure.MD5.hash(data: data(secret: secret)).formatted(.hex(separator: ":"))
}
public func comment<SecretType: Secret>(secret: SecretType) -> String {

View File

@@ -4,6 +4,7 @@ import Foundation
final class OpenSSHReader {
var remaining: Data
var done = false
/// Initialize the reader with an OpenSSH data payload.
/// - Parameter data: The data to read.
@@ -14,22 +15,28 @@ final class OpenSSHReader {
/// 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)
let length = try readNextBytes(as: UInt32.self, convertEndianness: convertEndianness)
guard remaining.count >= length else { throw .beyondBounds }
let dataRange = 0..<length
let dataRange = 0..<Int(length)
let ret = Data(remaining[dataRange])
remaining.removeSubrange(dataRange)
if remaining.isEmpty {
done = true
}
return ret
}
func readNextBytes<T>(as: T.Type) throws(OpenSSHReaderError) -> T {
func readNextBytes<T: FixedWidthInteger>(as: T.Type, convertEndianness: Bool = true) 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)
if remaining.isEmpty {
done = true
}
let value = unsafe lengthChunk.bytes.unsafeLoad(as: T.self)
return convertEndianness ? T(value.bigEndian) : T(value)
}
func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String {

View File

@@ -1,5 +1,6 @@
import Foundation
import CryptoKit
import SecretKit
/// Generates OpenSSH representations of Secrets.
public struct OpenSSHSignatureWriter: Sendable {

View File

@@ -1,5 +1,6 @@
import Foundation
import OSLog
import SecretKit
/// Controller responsible for writing public keys to disk, so that they're easily accessible by scripts.
public final class PublicKeyFileStoreController: Sendable {

View File

@@ -74,21 +74,16 @@ extension SSHAgentInputParser {
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 +
let certType = try reader.readNextChunkAsString()
guard let certType = OpenSSHCertificate.CertificateType(rawValue: certType) else { return nil }
_ = try reader.readNextChunk() // nonce
let curveIdentifier = try reader.readNextChunk()
let publicKey = try reader.readNextChunk()
let openSSHIdentifier = certType.keyIdentifier
return openSSHIdentifier.lengthAndData +
curveIdentifier.lengthAndData +
publicKey.lengthAndData
default:
return nil
}
} catch {
return nil
}

View File

@@ -3,6 +3,7 @@ import CryptoKit
import OSLog
import SecretKit
import AppKit
import SSHProtocolKit
/// The `Agent` is an implementation of an SSH agent. It manages coordination and access between a socket, traces requests, notifies witnesses and passes requests to stores.
public final class Agent: Sendable {
@@ -11,7 +12,7 @@ public final class Agent: Sendable {
private let witness: SigningWitness?
private let publicKeyWriter = OpenSSHPublicKeyWriter()
private let signatureWriter = OpenSSHSignatureWriter()
private let certificateHandler = OpenSSHCertificateHandler()
// private let certificateHandler = OpenSSHCertificateHandler()
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "Agent")
/// Initializes an agent with a store list and a witness.
@@ -23,7 +24,7 @@ public final class Agent: Sendable {
self.storeList = storeList
self.witness = witness
Task { @MainActor in
await certificateHandler.reloadCertificates(for: storeList.allSecrets)
// await certificateHandler.reloadCertificates(for: storeList.allSecrets)
}
}
@@ -47,6 +48,7 @@ extension Agent {
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
case .unknown(let value):
logger.error("Agent received unknown request of type \(value).")
throw UnhandledRequestError()
default:
logger.debug("Agent received valid request of type \(request.debugDescription), but not currently supported.")
throw UnhandledRequestError()
@@ -66,7 +68,7 @@ extension Agent {
/// - Returns: An OpenSSH formatted Data payload listing the identities available for signing operations.
func identities() async -> Data {
let secrets = await storeList.allSecrets
await certificateHandler.reloadCertificates(for: secrets)
// await certificateHandler.reloadCertificates(for: secrets)
var count = 0
var keyData = Data()
@@ -75,12 +77,12 @@ extension Agent {
keyData.append(keyBlob.lengthAndData)
keyData.append(publicKeyWriter.comment(secret: secret).lengthAndData)
count += 1
if let (certificateData, name) = try? await certificateHandler.keyBlobAndName(for: secret) {
keyData.append(certificateData.lengthAndData)
keyData.append(name.lengthAndData)
count += 1
}
// if let (certificateData, name) = try? await certificateHandler.keyBlobAndName(for: secret) {
// keyData.append(certificateData.lengthAndData)
// keyData.append(name.lengthAndData)
// count += 1
// }
}
logger.log("Agent enumerated \(count) identities")
var countBigEndian = UInt32(count).bigEndian
@@ -95,7 +97,7 @@ extension Agent {
/// - Returns: An OpenSSH formatted Data payload containing the signed data response.
func sign(data: Data, keyBlob: Data, provenance: SigningRequestProvenance) async throws -> Data {
guard let (secret, store) = await secret(matching: keyBlob) else {
let keyBlobHex = keyBlob.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }.joined()
let keyBlobHex = keyBlob.formatted(.hex())
logger.debug("Agent did not have a key matching \(keyBlobHex)")
throw NoMatchingKeyError()
}

View File

@@ -1,6 +1,7 @@
import Foundation
import OSLog
import SecretKit
import SSHProtocolKit
/// Manages storage and lookup for OpenSSH certificates.
public actor OpenSSHCertificateHandler: Sendable {
@@ -21,9 +22,6 @@ public actor OpenSSHCertificateHandler: Sendable {
logger.log("No certificates, short circuiting")
return
}
keyBlobsAndNames = secrets.reduce(into: [:]) { partialResult, next in
partialResult[next] = try? loadKeyblobAndName(for: next)
}
}
/// Attempts to find an OpenSSH Certificate that corresponds to a ``Secret``
@@ -32,57 +30,6 @@ public actor OpenSSHCertificateHandler: Sendable {
public func keyBlobAndName<SecretType: Secret>(for secret: SecretType) throws -> (Data, Data)? {
keyBlobsAndNames[AnySecret(secret)]
}
/// Attempts to find an OpenSSH Certificate that corresponds to a ``Secret``
/// - Parameter secret: The secret to search for a certificate with
/// - Returns: A (``Data``, ``Data``) tuple containing the certificate and certificate name, respectively.
private func loadKeyblobAndName<SecretType: Secret>(for secret: SecretType) throws -> (Data, Data)? {
let certificatePath = publicKeyFileStoreController.sshCertificatePath(for: secret)
guard FileManager.default.fileExists(atPath: certificatePath) else {
return nil
}
logger.debug("Found certificate for \(secret.name)")
let certContent = try String(contentsOfFile:certificatePath, encoding: .utf8)
let certElements = certContent.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: " ")
guard certElements.count >= 2 else {
logger.warning("Certificate found for \(secret.name) but failed to load")
throw OpenSSHCertificateError.parsingFailed
}
guard let certDecoded = Data(base64Encoded: certElements[1] as String) else {
logger.warning("Certificate found for \(secret.name) but failed to decode base64 key")
throw OpenSSHCertificateError.parsingFailed
}
if certElements.count >= 3 {
let certName = Data(certElements[2].utf8)
return (certDecoded, certName)
}
let certName = Data(secret.name.utf8)
logger.info("Certificate for \(secret.name) does not have a name tag, using secret name instead")
return (certDecoded, certName)
}
}
extension OpenSSHCertificateHandler {
enum OpenSSHCertificateError: LocalizedError {
case unsupportedType
case parsingFailed
case doesNotExist
public var errorDescription: String? {
switch self {
case .unsupportedType:
return "The key type was unsupported"
case .parsingFailed:
return "Failed to properly parse the SSH certificate"
case .doesNotExist:
return "Certificate does not exist"
}
}
}
}

View File

@@ -36,7 +36,7 @@ extension SigningRequestTracer {
/// - Parameter pid: The process ID to look up.
/// - Returns: A ``SecretKit.SigningRequestProvenance.Process`` describing the process.
func process(from pid: Int32) -> SigningRequestProvenance.Process {
var pidAndNameInfo = self.pidAndNameInfo(from: pid)
var pidAndNameInfo = unsafe self.pidAndNameInfo(from: pid)
let ppid = unsafe pidAndNameInfo.kp_eproc.e_ppid != 0 ? pidAndNameInfo.kp_eproc.e_ppid : nil
let procName = unsafe withUnsafeMutablePointer(to: &pidAndNameInfo.kp_proc.p_comm.0) { pointer in
unsafe String(cString: pointer)

View File

@@ -36,16 +36,21 @@ public struct SocketController {
logger.debug("Socket controller path is clear")
port = SocketPort(path: path)
fileHandle = FileHandle(fileDescriptor: port.socket, closeOnDealloc: true)
Task { [fileHandle, sessionsContinuation, logger] in
for await notification in NotificationCenter.default.notifications(named: .NSFileHandleConnectionAccepted) {
Task { @MainActor [fileHandle, sessionsContinuation, logger] in
// Create the sequence before triggering the notification to
// ensure it will not be missed.
let connectionAcceptedNotifications = NotificationCenter.default.notifications(named: .NSFileHandleConnectionAccepted)
fileHandle.acceptConnectionInBackgroundAndNotify()
for await notification in connectionAcceptedNotifications {
logger.debug("Socket controller accepted connection")
guard let new = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { continue }
let session = Session(fileHandle: new)
sessionsContinuation.yield(session)
await fileHandle.acceptConnectionInBackgroundAndNotifyOnMainActor()
fileHandle.acceptConnectionInBackgroundAndNotify()
}
}
fileHandle.acceptConnectionInBackgroundAndNotify(forModes: [RunLoop.Mode.common])
logger.debug("Socket listening at \(path)")
}
@@ -77,8 +82,14 @@ extension SocketController {
self.fileHandle = fileHandle
provenance = SigningRequestTracer().provenance(from: fileHandle)
(messages, messagesContinuation) = AsyncStream.makeStream()
Task { [messagesContinuation, logger] in
for await _ in NotificationCenter.default.notifications(named: .NSFileHandleDataAvailable, object: fileHandle) {
Task { @MainActor [messagesContinuation, logger] in
// Create the sequence before triggering the notification to
// ensure it will not be missed.
let dataAvailableNotifications = NotificationCenter.default.notifications(named: .NSFileHandleDataAvailable, object: fileHandle)
fileHandle.waitForDataInBackgroundAndNotify()
for await _ in dataAvailableNotifications {
let data = fileHandle.availableData
guard !data.isEmpty else {
logger.debug("Socket controller received empty data, ending continuation.")
@@ -90,16 +101,13 @@ extension SocketController {
logger.debug("Socket controller yielded data.")
}
}
Task {
await fileHandle.waitForDataInBackgroundAndNotifyOnMainActor()
}
}
/// Writes new data to the socket.
/// - Parameter data: The data to write.
public func write(_ data: Data) async throws {
try fileHandle.write(contentsOf: data)
await fileHandle.waitForDataInBackgroundAndNotifyOnMainActor()
@MainActor public func write(_ data: Data) throws {
try fileHandle.write(contentsOf: data)
fileHandle.waitForDataInBackgroundAndNotify()
}
/// Closes the socket and cleans up resources.
@@ -113,22 +121,6 @@ extension SocketController {
}
private extension FileHandle {
/// Ensures waitForDataInBackgroundAndNotify will be called on the main actor.
@MainActor func waitForDataInBackgroundAndNotifyOnMainActor() {
waitForDataInBackgroundAndNotify()
}
/// Ensures acceptConnectionInBackgroundAndNotify will be called on the main actor.
/// - Parameter modes: the runloop modes to use.
@MainActor func acceptConnectionInBackgroundAndNotifyOnMainActor(forModes modes: [RunLoop.Mode]? = [RunLoop.Mode.common]) {
acceptConnectionInBackgroundAndNotify(forModes: modes)
}
}
private extension SocketPort {
convenience init(path: String) {

View File

@@ -6,6 +6,9 @@ import SmartCardSecretKit
import SecretAgentKit
import Brief
import Observation
import SSHProtocolKit
import CertificateKit
import Common
@main
class AppDelegate: NSObject, NSApplicationDelegate {
@@ -15,6 +18,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let cryptoKit = SecureEnclave.Store()
let migrator = SecureEnclave.CryptoKitMigrator()
try? migrator.migrate(to: cryptoKit)
let certsMigrator = CertificateKitMigrator(homeDirectory: URL.homeDirectory)
try? certsMigrator.migrate()
list.add(store: cryptoKit)
list.add(store: SmartCard.Store())
return list
@@ -26,7 +31,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
Agent(storeList: storeList, witness: notifier)
}()
private lazy var socketController: SocketController = {
let path = (NSHomeDirectory() as NSString).appendingPathComponent("socket.ssh") as String
let path = URL.socketPath as String
return SocketController(path: path)
}()
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "AppDelegate")
@@ -41,7 +46,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
for await message in session.messages {
let request = try await inputParser.parse(data: message)
let agentResponse = await agent.handle(request: request, provenance: session.provenance)
try await session.write(agentResponse)
try session.write(agentResponse)
}
} catch {
try session.close()

View File

@@ -2,6 +2,16 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.hardened-process</key>
<true/>
<key>com.apple.security.hardened-process.dyld-ro</key>
<true/>
<key>com.apple.security.hardened-process.enhanced-security-version</key>
<integer>1</integer>
<key>com.apple.security.hardened-process.hardened-heap</key>
<true/>
<key>com.apple.security.hardened-process.platform-restrictions</key>
<integer>2</integer>
<key>com.apple.security.smartcard</key>
<true/>
<key>keychain-access-groups</key>

View File

@@ -1,8 +1,8 @@
import Foundation
import SecretAgentKit
import OSLog
import SSHProtocolKit
import Brief
import XPCWrappers
import OSLog
/// Delegates all agent input parsing to an XPC service which wraps OpenSSH
public final class XPCAgentInputParser: SSHAgentInputParserProtocol {

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.hardened-process</key>
<true/>
<key>com.apple.security.hardened-process.checked-allocations</key>
<true/>
<key>com.apple.security.hardened-process.checked-allocations.enable-pure-data</key>
<true/>
<key>com.apple.security.hardened-process.checked-allocations.no-tagged-receive</key>
<true/>
<key>com.apple.security.hardened-process.dyld-ro</key>
<true/>
<key>com.apple.security.hardened-process.enhanced-security-version</key>
<integer>1</integer>
<key>com.apple.security.hardened-process.hardened-heap</key>
<true/>
<key>com.apple.security.hardened-process.platform-restrictions</key>
<integer>2</integer>
</dict>
</plist>

View File

@@ -1,7 +1,7 @@
import Foundation
import OSLog
import XPCWrappers
import SecretAgentKit
import SSHProtocolKit
final class SecretAgentInputParser: NSObject, XPCProtocol {

View File

@@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */; };
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
50033AC327813F1700253856 /* BundleIDs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50033AC227813F1700253856 /* BundleIDs.swift */; };
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3A278005E800DF2006 /* SecretKit */; };
5003EF3D278005F300DF2006 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3C278005F300DF2006 /* Brief */; };
5003EF3F278005F300DF2006 /* SecretAgentKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3E278005F300DF2006 /* SecretAgentKit */; };
@@ -26,13 +25,13 @@
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 */; };
504788EC2E680DC800B4556F /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788EB2E680DC400B4556F /* URLs.swift */; };
504788F22E681F3A00B4556F /* Instructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F12E681F3A00B4556F /* Instructions.swift */; };
504788F42E681F6900B4556F /* ToolConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F32E681F6900B4556F /* ToolConfigurationView.swift */; };
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; };
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */; };
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; };
505993512E7E59FB0092CFFA /* XPCCertificateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505993502E7E59F70092CFFA /* XPCCertificateParser.swift */; };
505993532E7E70C90092CFFA /* CertificateKit in Frameworks */ = {isa = PBXBuildFile; productRef = 505993522E7E70C90092CFFA /* CertificateKit */; };
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; };
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; };
@@ -49,7 +48,6 @@
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 */; };
@@ -70,10 +68,19 @@
50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; };
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; };
50E0145C2EDB9CDF00B121F1 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 50E0145B2EDB9CDF00B121F1 /* Common */; };
50E0145E2EDB9CE400B121F1 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 50E0145D2EDB9CE400B121F1 /* Common */; };
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */; };
50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4C22E7765DF00C73783 /* AboutView.swift */; };
50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; };
50E4C4C92E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; };
50E4C4D92E77C4B300C73783 /* SecretiveCertificateParser.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50E4C4CE2E77C4B300C73783 /* SecretiveCertificateParser.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
50E4C4E82E77C53000C73783 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4E32E77C53000C73783 /* main.swift */; };
50E4C4E92E77C53000C73783 /* SecretiveCertificateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4E52E77C53000C73783 /* SecretiveCertificateParser.swift */; };
50E4C4ED2E77C55E00C73783 /* XPCWrappers in Frameworks */ = {isa = PBXBuildFile; productRef = 50E4C4EC2E77C55E00C73783 /* XPCWrappers */; };
50E4C4EF2E77C8FC00C73783 /* SSHProtocolKit in Frameworks */ = {isa = PBXBuildFile; productRef = 50E4C4EE2E77C8FC00C73783 /* SSHProtocolKit */; };
50E4C4F12E77C90300C73783 /* SSHProtocolKit in Frameworks */ = {isa = PBXBuildFile; productRef = 50E4C4F02E77C90300C73783 /* SSHProtocolKit */; };
50E4C4F32E77CAC600C73783 /* XPCWrappers in Frameworks */ = {isa = PBXBuildFile; productRef = 50E4C4F22E77CAC600C73783 /* XPCWrappers */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -119,6 +126,13 @@
remoteGlobalIDString = 50692E4F2E6FF9D20043C7BB;
remoteInfo = SecretAgentInputParser;
};
50E4C4D72E77C4B300C73783 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 50E4C4CD2E77C4B300C73783;
remoteInfo = SecretiveCertificateParser;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -129,6 +143,7 @@
dstSubfolderSpec = 16;
files = (
50692D1D2E6FDB880043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */,
50E4C4D92E77C4B300C73783 /* SecretiveCertificateParser.xpc in Embed XPC Services */,
50692E5B2E6FF9D20043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */,
);
name = "Embed XPC Services";
@@ -181,21 +196,19 @@
/* Begin PBXFileReference section */
2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSecretView.swift; sourceTree = "<group>"; };
50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
50033AC227813F1700253856 /* BundleIDs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleIDs.swift; sourceTree = "<group>"; };
5003EF39278005C800DF2006 /* Packages */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Packages; sourceTree = "<group>"; };
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>"; };
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>"; };
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>"; };
504788F32E681F6900B4556F /* ToolConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolConfigurationView.swift; sourceTree = "<group>"; };
504788F52E68206F00B4556F /* GettingStartedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GettingStartedView.swift; sourceTree = "<group>"; };
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxBackgroundStyle.swift; sourceTree = "<group>"; };
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>"; };
5059933F2E7A3B5B0092CFFA /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; sourceTree = "<group>"; };
505993502E7E59F70092CFFA /* XPCCertificateParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCCertificateParser.swift; sourceTree = "<group>"; };
50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; };
50617D8223FCE48E0099B055 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
50617D8423FCE48E0099B055 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@@ -241,6 +254,13 @@
50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowBackgroundStyle.swift; sourceTree = "<group>"; };
50E4C4C22E7765DF00C73783 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
50E4C4C72E777E4200C73783 /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = "<group>"; };
50E4C4CE2E77C4B300C73783 /* SecretiveCertificateParser.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretiveCertificateParser.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
50E4C4E02E77C4EE00C73783 /* SecretAgentInputParser.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretAgentInputParser.entitlements; sourceTree = "<group>"; };
50E4C4E12E77C4FA00C73783 /* SecretiveUpdater.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretiveUpdater.entitlements; sourceTree = "<group>"; };
50E4C4E22E77C53000C73783 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
50E4C4E32E77C53000C73783 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
50E4C4E42E77C53000C73783 /* SecretiveCertificateParser.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretiveCertificateParser.entitlements; sourceTree = "<group>"; };
50E4C4E52E77C53000C73783 /* SecretiveCertificateParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretiveCertificateParser.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -248,8 +268,10 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
50E0145C2EDB9CDF00B121F1 /* Common in Frameworks */,
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */,
501421622781262300BBAA70 /* Brief in Frameworks */,
505993532E7E70C90092CFFA /* CertificateKit in Frameworks */,
5003EF5F2780081600DF2006 /* SecureEnclaveSecretKit in Frameworks */,
5003EF612780081600DF2006 /* SmartCardSecretKit in Frameworks */,
);
@@ -268,7 +290,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
50692E6C2E6FFA510043C7BB /* SecretAgentKit in Frameworks */,
50E4C4F32E77CAC600C73783 /* XPCWrappers in Frameworks */,
50E4C4F12E77C90300C73783 /* SSHProtocolKit in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -281,20 +304,22 @@
5003EF652780081B00DF2006 /* SmartCardSecretKit in Frameworks */,
5003EF3F278005F300DF2006 /* SecretAgentKit in Frameworks */,
5003EF41278005FA00DF2006 /* SecretKit in Frameworks */,
50E0145E2EDB9CE400B121F1 /* Common in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
50E4C4CB2E77C4B300C73783 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
50E4C4EF2E77C8FC00C73783 /* SSHProtocolKit in Frameworks */,
50E4C4ED2E77C55E00C73783 /* XPCWrappers in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
50033AC427813F1C00253856 /* Helpers */ = {
isa = PBXGroup;
children = (
50033AC227813F1700253856 /* BundleIDs.swift */,
);
path = Helpers;
sourceTree = "<group>";
};
504788ED2E681EB200B4556F /* Modifiers */ = {
isa = PBXGroup;
children = (
@@ -356,6 +381,7 @@
508A58AF241E144C0069DC07 /* Config */,
50692D272E6FDB8D0043C7BB /* SecretiveUpdater */,
50692E662E6FF9E20043C7BB /* SecretAgentInputParser */,
50E4C4E72E77C53000C73783 /* SecretiveCertificateParser */,
50617D8023FCE48E0099B055 /* Products */,
5099A08B240243730062B6F2 /* Frameworks */,
);
@@ -368,6 +394,7 @@
50A3B78A24026B7500D209EA /* SecretAgent.app */,
50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */,
50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */,
50E4C4CE2E77C4B300C73783 /* SecretiveCertificateParser.xpc */,
);
name = Products;
sourceTree = "<group>";
@@ -378,7 +405,6 @@
50617D8223FCE48E0099B055 /* App.swift */,
508A58B0241ED1C40069DC07 /* Views */,
508A58B1241ED1EA0069DC07 /* Controllers */,
50033AC427813F1C00253856 /* Helpers */,
50617D8E23FCE48E0099B055 /* Info.plist */,
508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */,
50E4C4C72E777E4200C73783 /* AppIcon.icon */,
@@ -403,6 +429,7 @@
50692D272E6FDB8D0043C7BB /* SecretiveUpdater */ = {
isa = PBXGroup;
children = (
50E4C4E12E77C4FA00C73783 /* SecretiveUpdater.entitlements */,
50692D232E6FDB8D0043C7BB /* Info.plist */,
50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */,
50692D242E6FDB8D0043C7BB /* main.swift */,
@@ -414,6 +441,7 @@
50692E662E6FF9E20043C7BB /* SecretAgentInputParser */ = {
isa = PBXGroup;
children = (
50E4C4E02E77C4EE00C73783 /* SecretAgentInputParser.entitlements */,
50692E622E6FF9E20043C7BB /* Info.plist */,
50692E632E6FF9E20043C7BB /* main.swift */,
50692E642E6FF9E20043C7BB /* SecretAgentInputParser.swift */,
@@ -444,11 +472,10 @@
508A58B1241ED1EA0069DC07 /* Controllers */ = {
isa = PBXGroup;
children = (
504788EB2E680DC400B4556F /* URLs.swift */,
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */,
5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */,
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */,
50571E0424393D1500F76F6C /* LaunchAgentController.swift */,
505993502E7E59F70092CFFA /* XPCCertificateParser.swift */,
);
path = Controllers;
sourceTree = "<group>";
@@ -483,6 +510,17 @@
path = "Preview Content";
sourceTree = "<group>";
};
50E4C4E72E77C53000C73783 /* SecretiveCertificateParser */ = {
isa = PBXGroup;
children = (
50E4C4E42E77C53000C73783 /* SecretiveCertificateParser.entitlements */,
50E4C4E22E77C53000C73783 /* Info.plist */,
50E4C4E32E77C53000C73783 /* main.swift */,
50E4C4E52E77C53000C73783 /* SecretiveCertificateParser.swift */,
);
path = SecretiveCertificateParser;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -503,6 +541,7 @@
50142167278126B500BBAA70 /* PBXTargetDependency */,
50692D1C2E6FDB880043C7BB /* PBXTargetDependency */,
50692E5A2E6FF9D20043C7BB /* PBXTargetDependency */,
50E4C4D82E77C4B300C73783 /* PBXTargetDependency */,
);
name = Secretive;
packageProductDependencies = (
@@ -510,6 +549,8 @@
5003EF5E2780081600DF2006 /* SecureEnclaveSecretKit */,
5003EF602780081600DF2006 /* SmartCardSecretKit */,
501421612781262300BBAA70 /* Brief */,
505993522E7E70C90092CFFA /* CertificateKit */,
50E0145B2EDB9CDF00B121F1 /* Common */,
);
productName = Secretive;
productReference = 50617D7F23FCE48E0099B055 /* Secretive.app */;
@@ -550,7 +591,8 @@
);
name = SecretAgentInputParser;
packageProductDependencies = (
50692E6B2E6FFA510043C7BB /* SecretAgentKit */,
50E4C4F02E77C90300C73783 /* SSHProtocolKit */,
50E4C4F22E77CAC600C73783 /* XPCWrappers */,
);
productName = SecretAgentInputParser;
productReference = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */;
@@ -580,11 +622,33 @@
5003EF40278005FA00DF2006 /* SecretKit */,
5003EF622780081B00DF2006 /* SecureEnclaveSecretKit */,
5003EF642780081B00DF2006 /* SmartCardSecretKit */,
50E0145D2EDB9CE400B121F1 /* Common */,
);
productName = SecretAgent;
productReference = 50A3B78A24026B7500D209EA /* SecretAgent.app */;
productType = "com.apple.product-type.application";
};
50E4C4CD2E77C4B300C73783 /* SecretiveCertificateParser */ = {
isa = PBXNativeTarget;
buildConfigurationList = 50E4C4DB2E77C4B300C73783 /* Build configuration list for PBXNativeTarget "SecretiveCertificateParser" */;
buildPhases = (
50E4C4CA2E77C4B300C73783 /* Sources */,
50E4C4CB2E77C4B300C73783 /* Frameworks */,
50E4C4CC2E77C4B300C73783 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = SecretiveCertificateParser;
packageProductDependencies = (
50E4C4EC2E77C55E00C73783 /* XPCWrappers */,
50E4C4EE2E77C8FC00C73783 /* SSHProtocolKit */,
);
productName = SecretiveCertificateParser;
productReference = 50E4C4CE2E77C4B300C73783 /* SecretiveCertificateParser.xpc */;
productType = "com.apple.product-type.xpc-service";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -608,6 +672,9 @@
50A3B78924026B7500D209EA = {
CreatedOnToolsVersion = 11.4;
};
50E4C4CD2E77C4B300C73783 = {
CreatedOnToolsVersion = 26.0;
};
};
};
buildConfigurationList = 50617D7A23FCE48D0099B055 /* Build configuration list for PBXProject "Secretive" */;
@@ -635,6 +702,7 @@
50A3B78924026B7500D209EA /* SecretAgent */,
50692D112E6FDB880043C7BB /* SecretiveUpdater */,
50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */,
50E4C4CD2E77C4B300C73783 /* SecretiveCertificateParser */,
);
};
/* End PBXProject section */
@@ -677,6 +745,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
50E4C4CC2E77C4B300C73783 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -690,24 +765,22 @@
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */,
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
504788EC2E680DC800B4556F /* URLs.swift in Sources */,
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */,
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */,
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */,
505993512E7E59FB0092CFFA /* XPCCertificateParser.swift in Sources */,
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */,
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */,
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */,
50033AC327813F1700253856 /* BundleIDs.swift in Sources */,
50BDCB722E63BAF20072D2E7 /* AgentStatusView.swift in Sources */,
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */,
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */,
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
50AE97002E5C1A420018C710 /* IntegrationsView.swift in Sources */,
50153E20250AFCB200525160 /* UpdateView.swift in Sources */,
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */,
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */,
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */,
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
@@ -749,6 +822,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
50E4C4CA2E77C4B300C73783 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
50E4C4E82E77C53000C73783 /* main.swift in Sources */,
50E4C4E92E77C53000C73783 /* SecretiveCertificateParser.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -781,6 +863,11 @@
target = 50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */;
targetProxy = 50692E712E6FFA6E0043C7BB /* PBXContainerItemProxy */;
};
50E4C4D82E77C4B300C73783 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 50E4C4CD2E77C4B300C73783 /* SecretiveCertificateParser */;
targetProxy = 50E4C4D72E77C4B300C73783 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -1024,6 +1111,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = SecretiveUpdater/SecretiveUpdater.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
@@ -1031,9 +1119,11 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_POINTER_AUTHENTICATION = YES;
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
@@ -1063,49 +1153,11 @@
};
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 = 14.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_ENTITLEMENTS = SecretiveUpdater/SecretiveUpdater.entitlements;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
@@ -1113,9 +1165,11 @@
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_POINTER_AUTHENTICATION = YES;
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
@@ -1149,13 +1203,16 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = SecretAgentInputParser/SecretAgentInputParser.entitlements;
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_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_POINTER_AUTHENTICATION = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SecretAgentInputParser/Info.plist;
@@ -1177,39 +1234,11 @@
};
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 = 14.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_ENTITLEMENTS = SecretAgentInputParser/SecretAgentInputParser.entitlements;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
@@ -1217,7 +1246,9 @@
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_POINTER_AUTHENTICATION = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SecretAgentInputParser/Info.plist;
@@ -1239,152 +1270,6 @@
};
name = Release;
};
508A5914241EF1A00069DC07 /* Test */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 508A58AB241E121B0069DC07 /* Config.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_POINTER_AUTHENTICATION = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "";
SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
STRIP_INSTALLED_PRODUCT = NO;
STRIP_SWIFT_SYMBOLS = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_MEMORY_SAFETY = YES;
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
SWIFT_VERSION = 6.0;
};
name = Test;
};
508A5915241EF1A00069DC07 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = Secretive/Secretive.entitlements;
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = NO;
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
ENABLE_POINTER_AUTHENTICATION = YES;
ENABLE_PREVIEWS = YES;
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
ENABLE_RESOURCE_ACCESS_USB = NO;
INFOPLIST_FILE = Secretive/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
};
508A5917241EF1A00069DC07 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
ENABLE_PREVIEWS = YES;
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
ENABLE_RESOURCE_ACCESS_LOCATION = NO;
ENABLE_RESOURCE_ACCESS_PRINTING = NO;
ENABLE_RESOURCE_ACCESS_USB = NO;
INFOPLIST_FILE = SecretAgent/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
};
50A3B79B24026B7600D209EA /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -1396,9 +1281,11 @@
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
DEVELOPMENT_TEAM = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
ENABLE_POINTER_AUTHENTICATION = YES;
ENABLE_PREVIEWS = YES;
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
@@ -1432,9 +1319,11 @@
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
DEVELOPMENT_TEAM = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
ENABLE_POINTER_AUTHENTICATION = YES;
ENABLE_PREVIEWS = YES;
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
@@ -1457,6 +1346,76 @@
};
name = Release;
};
50E4C4DC2E77C4B300C73783 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = SecretiveCertificateParser/SecretiveCertificateParser.entitlements;
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_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_POINTER_AUTHENTICATION = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SecretiveCertificateParser/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SecretiveCertificateParser;
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.SecretiveCertificateParser;
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;
};
50E4C4DE2E77C4B300C73783 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = SecretiveCertificateParser/SecretiveCertificateParser.entitlements;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_POINTER_AUTHENTICATION = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SecretiveCertificateParser/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SecretiveCertificateParser;
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.SecretiveCertificateParser;
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;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -1464,7 +1423,6 @@
isa = XCConfigurationList;
buildConfigurations = (
50617D9B23FCE48E0099B055 /* Debug */,
508A5914241EF1A00069DC07 /* Test */,
50617D9C23FCE48E0099B055 /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -1474,7 +1432,6 @@
isa = XCConfigurationList;
buildConfigurations = (
50617D9E23FCE48E0099B055 /* Debug */,
508A5915241EF1A00069DC07 /* Test */,
50617D9F23FCE48E0099B055 /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -1484,7 +1441,6 @@
isa = XCConfigurationList;
buildConfigurations = (
50692D202E6FDB880043C7BB /* Debug */,
50692D212E6FDB880043C7BB /* Test */,
50692D222E6FDB880043C7BB /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -1494,7 +1450,6 @@
isa = XCConfigurationList;
buildConfigurations = (
50692E5E2E6FF9D20043C7BB /* Debug */,
50692E5F2E6FF9D20043C7BB /* Test */,
50692E602E6FF9D20043C7BB /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -1504,12 +1459,20 @@
isa = XCConfigurationList;
buildConfigurations = (
50A3B79B24026B7600D209EA /* Debug */,
508A5917241EF1A00069DC07 /* Test */,
50A3B79C24026B7600D209EA /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
50E4C4DB2E77C4B300C73783 /* Build configuration list for PBXNativeTarget "SecretiveCertificateParser" */ = {
isa = XCConfigurationList;
buildConfigurations = (
50E4C4DC2E77C4B300C73783 /* Debug */,
50E4C4DE2E77C4B300C73783 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCSwiftPackageProductDependency section */
@@ -1549,6 +1512,10 @@
isa = XCSwiftPackageProductDependency;
productName = Brief;
};
505993522E7E70C90092CFFA /* CertificateKit */ = {
isa = XCSwiftPackageProductDependency;
productName = CertificateKit;
};
50692D2C2E6FDC000043C7BB /* XPCWrappers */ = {
isa = XCSwiftPackageProductDependency;
productName = XPCWrappers;
@@ -1557,9 +1524,29 @@
isa = XCSwiftPackageProductDependency;
productName = Brief;
};
50692E6B2E6FFA510043C7BB /* SecretAgentKit */ = {
50E0145B2EDB9CDF00B121F1 /* Common */ = {
isa = XCSwiftPackageProductDependency;
productName = SecretAgentKit;
productName = Common;
};
50E0145D2EDB9CE400B121F1 /* Common */ = {
isa = XCSwiftPackageProductDependency;
productName = Common;
};
50E4C4EC2E77C55E00C73783 /* XPCWrappers */ = {
isa = XCSwiftPackageProductDependency;
productName = XPCWrappers;
};
50E4C4EE2E77C8FC00C73783 /* SSHProtocolKit */ = {
isa = XCSwiftPackageProductDependency;
productName = SSHProtocolKit;
};
50E4C4F02E77C90300C73783 /* SSHProtocolKit */ = {
isa = XCSwiftPackageProductDependency;
productName = SSHProtocolKit;
};
50E4C4F22E77CAC600C73783 /* XPCWrappers */ = {
isa = XCSwiftPackageProductDependency;
productName = XPCWrappers;
};
/* End XCSwiftPackageProductDependency section */
};

View File

@@ -23,7 +23,7 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Test"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">

View File

@@ -3,26 +3,30 @@ import SecretKit
import SecureEnclaveSecretKit
import SmartCardSecretKit
import Brief
import CertificateKit
@main
struct Secretive: App {
@Environment(\.agentStatusChecker) var agentStatusChecker
@Environment(\.agentLaunchController) var agentLaunchController
@Environment(\.justUpdatedChecker) var justUpdatedChecker
@SceneBuilder var body: some Scene {
WindowGroup {
ContentView()
.environment(EnvironmentValues._secretStoreList)
.environment(EnvironmentValues._certificateStore)
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
guard hasRunSetup else { return }
agentStatusChecker.check()
if agentStatusChecker.running && justUpdatedChecker.justUpdatedBuild {
// Relaunch the agent, since it'll be running from earlier update still
reinstallAgent()
} else if !agentStatusChecker.running && !agentStatusChecker.developmentBuild {
forceLaunchAgent()
Task {
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
@AppStorage("explicitlyDisabled") var explicitlyDisabled = false
guard hasRunSetup && !explicitlyDisabled else { return }
agentLaunchController.check()
guard !agentLaunchController.developmentBuild else { return }
if justUpdatedChecker.justUpdatedBuild || !agentLaunchController.running {
// Relaunch the agent, since it'll be running from earlier update still
try await agentLaunchController.forceLaunch()
}
}
}
}
@@ -79,30 +83,6 @@ extension Secretive {
}
extension Secretive {
private func reinstallAgent() {
Task {
_ = await LaunchAgentController().install()
try? await Task.sleep(for: .seconds(1))
agentStatusChecker.check()
if !agentStatusChecker.running {
forceLaunchAgent()
}
}
}
private func forceLaunchAgent() {
// We've run setup, we didn't just update, launchd is just not doing it's thing.
// Force a launch directly.
Task {
_ = await LaunchAgentController().forceLaunch()
agentStatusChecker.check()
}
}
}
private enum Constants {
static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")!
}
@@ -114,15 +94,18 @@ extension EnvironmentValues {
@MainActor fileprivate static let _secretStoreList: SecretStoreList = {
let list = SecretStoreList()
let cryptoKit = SecureEnclave.Store()
let migrator = SecureEnclave.CryptoKitMigrator()
try? migrator.migrate(to: cryptoKit)
let cryptoKitMigrator = SecureEnclave.CryptoKitMigrator()
try? cryptoKitMigrator.migrate(to: cryptoKit)
list.add(store: cryptoKit)
list.add(store: SmartCard.Store())
return list
}()
private static let _agentStatusChecker = AgentStatusChecker()
@Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker
@MainActor fileprivate static let _certificateStore: CertificateStore = CertificateStore()
private static let _agentLaunchController = AgentLaunchController()
@Entry var agentLaunchController: any AgentLaunchControllerProtocol = _agentLaunchController
private static let _updater: any UpdaterProtocol = {
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
return Updater(checkOnLaunch: hasRunSetup)
@@ -135,6 +118,10 @@ extension EnvironmentValues {
@MainActor var secretStoreList: SecretStoreList {
EnvironmentValues._secretStoreList
}
@MainActor var certificateStore: CertificateStore {
EnvironmentValues._certificateStore
}
}
extension FocusedValues {

View File

@@ -2,18 +2,26 @@ import Foundation
import AppKit
import SecretKit
import Observation
import OSLog
import ServiceManagement
import Common
@MainActor protocol AgentStatusCheckerProtocol: Observable, Sendable {
@MainActor protocol AgentLaunchControllerProtocol: Observable, Sendable {
var running: Bool { get }
var developmentBuild: Bool { get }
var process: NSRunningApplication? { get }
func check()
func install() async throws
func uninstall() async throws
func forceLaunch() async throws
}
@Observable @MainActor final class AgentStatusChecker: AgentStatusCheckerProtocol {
@Observable @MainActor final class AgentLaunchController: AgentLaunchControllerProtocol {
var running: Bool = false
var process: NSRunningApplication? = nil
private let logger = Logger(subsystem: "com.maxgoedjen.secretive", category: "LaunchAgentController")
private let service = SMAppService.loginItem(identifier: Bundle.agentBundleID)
nonisolated init() {
Task { @MainActor in
@@ -33,7 +41,7 @@ import Observation
// The process corresponding to this instance of Secretive
var instanceSecretAgentProcess: NSRunningApplication? {
// FIXME: CHECK VERSION
// TODO: CHECK VERSION
let agents = allSecretAgentProcesses
for agent in agents {
guard let url = agent.bundleURL else { continue }
@@ -49,6 +57,47 @@ import Observation
Bundle.main.bundleURL.isXcodeURL
}
func install() async throws {
logger.debug("Installing agent")
try? await service.unregister()
// This is definitely a bit of a "seems to work better" thing but:
// Seems to more reliably hit if these are on separate runloops, otherwise it seems like it sometimes doesn't kill old
// and start new?
try await Task.sleep(for: .seconds(1))
try service.register()
try await Task.sleep(for: .seconds(1))
check()
}
func uninstall() async throws {
logger.debug("Uninstalling agent")
try await Task.sleep(for: .seconds(1))
try await service.unregister()
try await Task.sleep(for: .seconds(1))
check()
}
func forceLaunch() async throws {
logger.debug("Agent is not running, attempting to force launch by reinstalling")
try await install()
if running {
logger.debug("Agent successfully force launched by reinstalling")
return
}
logger.debug("Agent is not running, attempting to force launch by launching directly")
let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LoginItems/SecretAgent.app")
let config = NSWorkspace.OpenConfiguration()
config.activates = false
do {
try await NSWorkspace.shared.openApplication(at: url, configuration: config)
logger.debug("Agent force launched")
try await Task.sleep(for: .seconds(1))
} catch {
logger.error("Error force launching \(error.localizedDescription)")
}
check()
}
}
extension URL {

View File

@@ -1,65 +0,0 @@
import Foundation
import ServiceManagement
import AppKit
import OSLog
import SecretKit
struct LaunchAgentController {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive", category: "LaunchAgentController")
func install() async -> Bool {
logger.debug("Installing agent")
_ = setEnabled(false)
// This is definitely a bit of a "seems to work better" thing but:
// Seems to more reliably hit if these are on separate runloops, otherwise it seems like it sometimes doesn't kill old
// and start new?
try? await Task.sleep(for: .seconds(1))
let result = await MainActor.run {
setEnabled(true)
}
try? await Task.sleep(for: .seconds(1))
return result
}
func uninstall() async -> Bool {
logger.debug("Uninstalling agent")
try? await Task.sleep(for: .seconds(1))
let result = await MainActor.run {
setEnabled(false)
}
try? await Task.sleep(for: .seconds(1))
return result
}
func forceLaunch() async -> Bool {
logger.debug("Agent is not running, attempting to force launch")
let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LoginItems/SecretAgent.app")
let config = NSWorkspace.OpenConfiguration()
config.activates = false
do {
try await NSWorkspace.shared.openApplication(at: url, configuration: config)
logger.debug("Agent force launched")
try? await Task.sleep(for: .seconds(1))
return true
} catch {
logger.error("Error force launching \(error.localizedDescription)")
return false
}
}
private func setEnabled(_ enabled: Bool) -> Bool {
let service = SMAppService.loginItem(identifier: Bundle.agentBundleID)
do {
if enabled {
try service.register()
} else {
try service.unregister()
}
return true
} catch {
return false
}
}
}

View File

@@ -0,0 +1,29 @@
import Foundation
import OSLog
import SSHProtocolKit
import Brief
import XPCWrappers
/// Delegates all agent input parsing to an XPC service which wraps OpenSSH
public final class XPCCertificateParser: OpenSSHCertificateParserProtocol {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive", category: "XPCCertificateParser")
private let session: XPCTypedSession<OpenSSHCertificate, OpenSSHCertificateError>
public init() async throws {
logger.debug("Creating XPCCertificateParser")
session = try await XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretiveCertificateParser", warmup: true)
logger.debug("XPCCertificateParser is warmed up.")
}
public func parse(data: Data) async throws -> OpenSSHCertificate {
logger.debug("Parsing input")
defer { logger.debug("Parsed input") }
return try await session.send(data)
}
deinit {
session.complete()
}
}

View File

@@ -1,7 +1,7 @@
import Foundation
import AppKit
class PreviewAgentStatusChecker: AgentStatusCheckerProtocol {
class PreviewAgentLaunchController: AgentLaunchControllerProtocol {
let running: Bool
let process: NSRunningApplication?
@@ -15,4 +15,13 @@ class PreviewAgentStatusChecker: AgentStatusCheckerProtocol {
func check() {
}
func install() async throws {
}
func uninstall() async throws {
}
func forceLaunch() async throws {
}
}

View File

@@ -3,6 +3,7 @@ import SwiftUI
struct SetupView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.agentLaunchController) private var agentLaunchController
@Binding var setupComplete: Bool
@State var showingIntegrations = false
@@ -31,7 +32,7 @@ struct SetupView: View {
) {
installed = true
Task {
await LaunchAgentController().install()
try? await agentLaunchController.install()
}
}
}

View File

@@ -1,5 +1,6 @@
import SwiftUI
import SecretKit
import SSHProtocolKit
struct ToolConfigurationView: View {
@@ -10,6 +11,7 @@ struct ToolConfigurationView: View {
@State var creating = false
@State var selectedSecret: AnySecret?
@State var email = ""
init(selectedInstruction: ConfigurationFileInstructions) {
self.selectedInstruction = selectedInstruction
@@ -48,6 +50,12 @@ struct ToolConfigurationView: View {
.tag(secret)
}
}
TextField(text: $email, prompt: Text(.integrationsConfigureUsingEmailPlaceholder)) {
Text(.integrationsConfigureUsingEmailTitle)
Text(.integrationsConfigureUsingEmailSubtitle)
.font(.subheadline)
.foregroundStyle(.secondary)
}
} header: {
Text(.integrationsConfigureUsingSecretHeader)
}
@@ -60,7 +68,7 @@ struct ToolConfigurationView: View {
Section {
ConfigurationItemView(title: .integrationsPathTitle, value: stepGroup.path, action: .revealInFinder(stepGroup.path))
ForEach(stepGroup.steps, id: \.self.key) { step in
ConfigurationItemView(title: .integrationsAddThisTitle, action: .copy(String(localized: step))) {
ConfigurationItemView(title: .integrationsAddThisTitle, action: .copy(placeholdersReplaced(text: String(localized: step)))) {
HStack {
Text(placeholdersReplaced(text: String(localized: step)))
.padding(8)
@@ -102,9 +110,11 @@ struct ToolConfigurationView: View {
func placeholdersReplaced(text: String) -> String {
guard let selectedSecret else { return text }
let writer = OpenSSHPublicKeyWriter()
let gitAllowedSignersString = [email.isEmpty ? String(localized: .integrationsConfigureUsingEmailPlaceholder) : email, writer.openSSHString(secret: selectedSecret)]
.joined(separator: " ")
let fileController = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL)
return text
.replacingOccurrences(of: Instructions.Constants.publicKeyPlaceholder, with: writer.openSSHString(secret: selectedSecret))
.replacingOccurrences(of: Instructions.Constants.publicKeyPlaceholder, with: gitAllowedSignersString)
.replacingOccurrences(of: Instructions.Constants.publicKeyPathPlaceholder, with: fileController.publicKeyPath(for: selectedSecret))
}

View File

@@ -68,6 +68,9 @@ struct ToolbarButtonStyle: PrimitiveButtonStyle {
.padding(.vertical, 10)
.padding(.horizontal, 12)
.glassEffect(.regular.interactive().tint(tint))
.onTapGesture {
configuration.trigger()
}
} else {
BorderedButtonStyle().makeBody(configuration: configuration)
.padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8))

View File

@@ -1,5 +1,6 @@
import SwiftUI
import SecretKit
import SSHProtocolKit
struct SecretDetailView<SecretType: Secret>: View {

View File

@@ -24,6 +24,18 @@ struct SecretListItemView: View {
Text(secret.name)
}
}
.sheet(isPresented: $isRenaming, onDismiss: {
renamedSecret(secret)
}, content: {
if let modifiable = store as? AnySecretStoreModifiable {
EditSecretView(store: modifiable, secret: secret)
}
})
.showingDeleteConfirmation(isPresented: $isDeleting, secret, store as? AnySecretStoreModifiable) { deleted in
if deleted {
deletedSecret(secret)
}
}
.contextMenu {
if store is AnySecretStoreModifiable {
Button(action: { isRenaming = true }) {
@@ -36,17 +48,5 @@ struct SecretListItemView: View {
}
}
}
.showingDeleteConfirmation(isPresented: $isDeleting, secret, store as? AnySecretStoreModifiable) { deleted in
if deleted {
deletedSecret(secret)
}
}
.sheet(isPresented: $isRenaming, onDismiss: {
renamedSecret(secret)
}, content: {
if let modifiable = store as? AnySecretStoreModifiable {
EditSecretView(store: modifiable, secret: secret)
}
})
}
}

View File

@@ -2,10 +2,10 @@ import SwiftUI
struct AgentStatusView: View {
@Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol
@Environment(\.agentLaunchController) private var agentLaunchController: any AgentLaunchControllerProtocol
var body: some View {
if agentStatusChecker.running {
if agentLaunchController.running {
AgentRunningView()
} else {
AgentNotRunningView()
@@ -14,12 +14,13 @@ struct AgentStatusView: View {
}
struct AgentRunningView: View {
@Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol
@Environment(\.agentLaunchController) private var agentLaunchController: any AgentLaunchControllerProtocol
@AppStorage("explicitlyDisabled") var explicitlyDisabled = false
var body: some View {
Form {
Section {
if let process = agentStatusChecker.process {
if let process = agentLaunchController.process {
ConfigurationItemView(
title: .agentDetailsLocationTitle,
value: process.bundleURL!.path(),
@@ -53,19 +54,14 @@ struct AgentRunningView: View {
Menu(.agentDetailsRestartAgentButton) {
Button(.agentDetailsDisableAgentButton) {
Task {
_ = await LaunchAgentController()
explicitlyDisabled = true
try? await agentLaunchController
.uninstall()
agentStatusChecker.check()
}
}
} primaryAction: {
Task {
let controller = LaunchAgentController()
let installed = await controller.install()
if !installed {
_ = await controller.forceLaunch()
}
agentStatusChecker.check()
try? await agentLaunchController.forceLaunch()
}
}
}
@@ -82,9 +78,10 @@ struct AgentRunningView: View {
struct AgentNotRunningView: View {
@Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol
@Environment(\.agentLaunchController) private var agentLaunchController
@State var triedRestart = false
@State var loading = false
@AppStorage("explicitlyDisabled") var explicitlyDisabled = false
var body: some View {
Form {
@@ -100,18 +97,14 @@ struct AgentNotRunningView: View {
if !triedRestart {
Spacer()
Button {
explicitlyDisabled = false
guard !loading else { return }
loading = true
Task {
let controller = LaunchAgentController()
let installed = await controller.install()
if !installed {
_ = await controller.forceLaunch()
}
agentStatusChecker.check()
try await agentLaunchController.forceLaunch()
loading = false
if !agentStatusChecker.running {
if !agentLaunchController.running {
triedRestart = true
}
}
@@ -145,9 +138,9 @@ struct AgentNotRunningView: View {
//#Preview {
// AgentStatusView()
// .environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: false))
// .environment(\.agentLaunchController, PreviewAgentLaunchController(running: false))
//}
//#Preview {
// AgentStatusView()
// .environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: true, process: .current))
// .environment(\.agentLaunchController, PreviewAgentLaunchController(running: true, process: .current))
//}

View File

@@ -3,6 +3,7 @@ import SecretKit
import SecureEnclaveSecretKit
import SmartCardSecretKit
import Brief
import SSHProtocolKit
struct ContentView: View {
@@ -13,8 +14,9 @@ struct ContentView: View {
@Environment(\.colorScheme) private var colorScheme
@Environment(\.openWindow) private var openWindow
@Environment(\.secretStoreList) private var storeList
@Environment(\.certificateStore) private var certificateStore
@Environment(\.updater) private var updater
@Environment(\.agentStatusChecker) private var agentStatusChecker
@Environment(\.agentLaunchController) private var agentLaunchController
@AppStorage("defaultsHasRunSetup") private var hasRunSetup = false
@State private var showingCreation = false
@@ -42,6 +44,22 @@ struct ContentView: View {
runningSetup = true
}
}
.dropDestination(for: URL.self) { items, location in
guard let url = items.first, url.pathExtension == "pub" else { return false }
Task {
let data = try! Data(contentsOf: url)
let parser = try! await XPCCertificateParser()
let cert = try! await parser.parse(data: data)
let secret = storeList.allSecrets.first { secret in
secret.name == cert.name
}
guard let secret = secret ?? storeList.allSecrets.first else { return }
print(cert.data.formatted(.hex()))
certificateStore.saveCertificate(cert.data, for: secret)
print(cert)
}
return true
} isTargeted: { _ in }
.focusedSceneValue(\.showCreateSecret, .init(isEnabled: !runningSetup) {
showingCreation = true
})
@@ -127,7 +145,7 @@ extension ContentView {
showingAgentInfo = true
}, label: {
HStack {
if agentStatusChecker.running {
if agentLaunchController.running {
Text(.agentRunningNoticeTitle)
.font(.headline)
.foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white)
@@ -145,8 +163,8 @@ extension ContentView {
})
.buttonStyle(
ToolbarStatusButtonStyle(
lightColor: agentStatusChecker.running ? .black.opacity(0.05) : .red.opacity(0.75),
darkColor: agentStatusChecker.running ? .white.opacity(0.05) : .red.opacity(0.5),
lightColor: agentLaunchController.running ? .black.opacity(0.05) : .red.opacity(0.75),
darkColor: agentLaunchController.running ? .white.opacity(0.05) : .red.opacity(0.5),
)
)
.popover(isPresented: $showingAgentInfo, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) {

View 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>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.hardened-process</key>
<true/>
<key>com.apple.security.hardened-process.checked-allocations</key>
<true/>
<key>com.apple.security.hardened-process.checked-allocations.enable-pure-data</key>
<true/>
<key>com.apple.security.hardened-process.checked-allocations.no-tagged-receive</key>
<true/>
<key>com.apple.security.hardened-process.dyld-ro</key>
<true/>
<key>com.apple.security.hardened-process.enhanced-security-version</key>
<integer>1</integer>
<key>com.apple.security.hardened-process.hardened-heap</key>
<true/>
<key>com.apple.security.hardened-process.platform-restrictions</key>
<integer>2</integer>
</dict>
</plist>

View File

@@ -0,0 +1,17 @@
import Foundation
import OSLog
import XPCWrappers
import SSHProtocolKit
final class SecretiveCertificateParser: NSObject, XPCProtocol {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.SecretiveCertificateParser", category: "SecretiveCertificateParser")
func process(_ data: Data) async throws -> OpenSSHCertificate {
let parser = OpenSSHCertificateParser()
let result = try parser.parse(data: data)
logger.log("Parser parsed certificate \(result.debugDescription)")
return result
}
}

View File

@@ -0,0 +1,7 @@
import Foundation
import XPCWrappers
let delegate = XPCServiceDelegate(exportedObject: SecretiveCertificateParser())
let listener = NSXPCListener.service()
listener.delegate = delegate
listener.resume()

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.hardened-process</key>
<true/>
<key>com.apple.security.hardened-process.checked-allocations</key>
<true/>
<key>com.apple.security.hardened-process.checked-allocations.enable-pure-data</key>
<true/>
<key>com.apple.security.hardened-process.checked-allocations.no-tagged-receive</key>
<true/>
<key>com.apple.security.hardened-process.dyld-ro</key>
<true/>
<key>com.apple.security.hardened-process.enhanced-security-version</key>
<integer>1</integer>
<key>com.apple.security.hardened-process.hardened-heap</key>
<true/>
<key>com.apple.security.hardened-process.platform-restrictions</key>
<integer>2</integer>
</dict>
</plist>