Compare commits

..

10 Commits

Author SHA1 Message Date
Max Goedjen
ece3865d9a Merge branch 'main' into sshextensions 2026-03-25 15:07:22 -07:00
Max Goedjen
4033a5b947 Xcode 26.4 Updates (#793)
* Bump CI to 26.4

* Update entitlements

* Move inputParser inside task scope to avoid races.

* Update project settings.
2026-03-12 19:48:12 +00:00
Max Goedjen
6b1f5bbb7c WIP 2026-03-12 12:46:45 -07:00
Max Goedjen
2cc0157290 Fix internet access policy linking (#794) 2026-03-11 22:16:06 +00:00
Max Goedjen
f848eb659e Messy WIP for agent extensions 2026-03-11 14:57:38 -07:00
Guilherme Rambo
faa622e379 Project setup to facilitate external contributions (#783) 2026-01-06 16:35:04 +00:00
Max Goedjen
9f2c6d9e84 Add hardware sec flags (#781)
* Add hardware sec flags

* Add hardware sec flags to xpc too
2025-12-30 21:18:22 +00:00
Thijs Mergaert
afb48529c7 Implement LAContext persistence for SmartCardStore (signature fixed) (#760)
* Implement LAContext persistence for SmartCardStore

* Consolidating persistence

---------

Co-authored-by: Max Goedjen <max.goedjen@gmail.com>
2025-12-14 21:13:57 +00:00
Sergei Razmetov
6c56039ece Fix SSH ECDSA signature mpint encoding (#772)
* Fix SSH ECDSA signature mpint encoding

OpenSSHSignatureWriter was emitting non-canonical mpints (keeping
fixed-width leading 0x00 bytes), which breaks strict parsers.
Canonicalize r/s mpints and add a regression test.

* Cleanup and consolidation

---------

Co-authored-by: Max Goedjen <max.goedjen@gmail.com>
2025-12-14 20:00:34 +00:00
Max Goedjen
2b712864d6 Pulling out a bunch of openssh stuff to dedicated package. (#775) 2025-12-14 19:54:56 +00:00
59 changed files with 1193 additions and 986 deletions

View File

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

View File

@@ -25,7 +25,7 @@ jobs:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
run: ./.github/scripts/signing.sh run: ./.github/scripts/signing.sh
- name: Set Environment - name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26.2.app run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
- name: Update Build Number - name: Update Build Number
env: env:
RUN_ID: ${{ github.run_id }} RUN_ID: ${{ github.run_id }}

View File

@@ -24,7 +24,7 @@ jobs:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
run: ./.github/scripts/signing.sh run: ./.github/scripts/signing.sh
- name: Set Environment - name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26.2.app run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
- name: Update Build Number - name: Update Build Number
env: env:
RUN_ID: ${{ github.run_id }} RUN_ID: ${{ github.run_id }}

View File

@@ -22,7 +22,7 @@ jobs:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
run: ./.github/scripts/signing.sh run: ./.github/scripts/signing.sh
- name: Set Environment - name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26.2.app run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
- name: Test - name: Test
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test
# SPM doesn't seem to pick up on the tests currently? # SPM doesn't seem to pick up on the tests currently?
@@ -47,7 +47,7 @@ jobs:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
run: ./.github/scripts/signing.sh run: ./.github/scripts/signing.sh
- name: Set Environment - name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26.2.app run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
- name: Update Build Number - name: Update Build Number
env: env:
TAG_NAME: ${{ github.ref }} TAG_NAME: ${{ github.ref }}

View File

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

4
.gitignore vendored
View File

@@ -93,3 +93,7 @@ iOSInjectionProject/
Archive.xcarchive Archive.xcarchive
.DS_Store .DS_Store
contents.xcworkspacedata contents.xcworkspacedata
# Per-User Configs
Sources/Config/OpenSource.xcconfig

View File

@@ -22,6 +22,9 @@ let package = Package(
.library( .library(
name: "SmartCardSecretKit", name: "SmartCardSecretKit",
targets: ["SmartCardSecretKit"]), targets: ["SmartCardSecretKit"]),
.library(
name: "SSHProtocolKit",
targets: ["SSHProtocolKit"]),
], ],
dependencies: [ dependencies: [
], ],
@@ -53,6 +56,19 @@ let package = Package(
resources: [localization], resources: [localization],
swiftSettings: swiftSettings swiftSettings: swiftSettings
), ),
.target(
name: "SSHProtocolKit",
dependencies: ["SecretKit"],
path: "Sources/Packages/Sources/SSHProtocolKit",
resources: [localization],
swiftSettings: swiftSettings,
),
.testTarget(
name: "SSHProtocolKitTests",
dependencies: ["SSHProtocolKit"],
path: "Sources/Packages/Tests/SSHProtocolKitTests",
swiftSettings: swiftSettings,
),
] ]
) )

View File

@@ -1,3 +1,8 @@
CI_VERSION = GITHUB_CI_VERSION CI_VERSION = GITHUB_CI_VERSION
CI_BUILD_NUMBER = GITHUB_BUILD_NUMBER CI_BUILD_NUMBER = GITHUB_BUILD_NUMBER
CI_BUILD_LINK = GITHUB_BUILD_URL CI_BUILD_LINK = GITHUB_BUILD_URL
#include? "OpenSource.xcconfig"
SECRETIVE_BASE_BUNDLE_ID = $(SECRETIVE_BASE_BUNDLE_ID_OSS:default=com.maxgoedjen.Secretive)
SECRETIVE_DEVELOPMENT_TEAM = $(SECRETIVE_DEVELOPMENT_TEAM_OSS:default=Z72PRUAWF6)

View File

@@ -19,9 +19,6 @@ let package = Package(
.library( .library(
name: "SmartCardSecretKit", name: "SmartCardSecretKit",
targets: ["SmartCardSecretKit"]), targets: ["SmartCardSecretKit"]),
.library(
name: "CertificateKit",
targets: ["CertificateKit"]),
.library( .library(
name: "SecretAgentKit", name: "SecretAgentKit",
targets: ["SecretAgentKit"]), targets: ["SecretAgentKit"]),
@@ -49,7 +46,7 @@ let package = Package(
), ),
.testTarget( .testTarget(
name: "SecretKitTests", name: "SecretKitTests",
dependencies: ["SecretKit", "SecureEnclaveSecretKit", "SmartCardSecretKit"], dependencies: ["SecretKit", "SecretAgentKit", "SecureEnclaveSecretKit", "SmartCardSecretKit"],
swiftSettings: swiftSettings, swiftSettings: swiftSettings,
), ),
.target( .target(
@@ -64,15 +61,9 @@ let package = Package(
resources: [localization], resources: [localization],
swiftSettings: swiftSettings, swiftSettings: swiftSettings,
), ),
.target(
name: "CertificateKit",
dependencies: ["SecretKit", "SSHProtocolKit"],
resources: [localization],
// swiftSettings: swiftSettings,
),
.target( .target(
name: "SecretAgentKit", name: "SecretAgentKit",
dependencies: ["SecretKit", "SSHProtocolKit", "CertificateKit"], dependencies: ["SecretKit", "SSHProtocolKit", "Common"],
resources: [localization], resources: [localization],
swiftSettings: swiftSettings, swiftSettings: swiftSettings,
), ),
@@ -84,15 +75,16 @@ let package = Package(
name: "SSHProtocolKit", name: "SSHProtocolKit",
dependencies: ["SecretKit"], dependencies: ["SecretKit"],
resources: [localization], resources: [localization],
// swiftSettings: swiftSettings, swiftSettings: swiftSettings,
), ),
.testTarget( .testTarget(
name: "SSHProtocolKitTests", name: "SSHProtocolKitTests",
dependencies: ["SSHProtocolKit"], dependencies: ["SSHProtocolKit"],
swiftSettings: swiftSettings,
), ),
.target( .target(
name: "Common", name: "Common",
dependencies: [], dependencies: ["SSHProtocolKit", "SecretKit"],
resources: [localization], resources: [localization],
swiftSettings: swiftSettings, swiftSettings: swiftSettings,
), ),

View File

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

View File

@@ -1,15 +0,0 @@
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

@@ -1,91 +0,0 @@
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

@@ -1,134 +0,0 @@
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

@@ -1,4 +1,6 @@
import Foundation import Foundation
import SSHProtocolKit
import SecretKit
extension URL { extension URL {
@@ -14,6 +16,20 @@ extension URL {
#endif #endif
} }
public static var publicKeyDirectory: URL {
agentHomeURL.appending(component: "PublicKeys")
}
/// The path for a Secret's public key.
/// - Parameter secret: The Secret to return the path for.
/// - Returns: The path to the Secret's public key.
/// - Warning: This method returning a path does not imply that a key has been written to disk already. This method only describes where it will be written to.
public static func publicKeyPath<SecretType: Secret>(for secret: SecretType, in directory: URL) -> String {
let keyWriter = OpenSSHPublicKeyWriter()
let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "")
return directory.appending(component: "\(minimalHex).pub").path()
}
} }
extension String { extension String {
@@ -27,3 +43,4 @@ extension String {
} }
} }

View File

@@ -1,102 +0,0 @@
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

@@ -19,7 +19,7 @@ public struct OpenSSHPublicKeyWriter: Sendable {
("nistp" + String(describing: secret.keyType.size)).lengthAndData + ("nistp" + String(describing: secret.keyType.size)).lengthAndData +
secret.publicKey.lengthAndData secret.publicKey.lengthAndData
case .mldsa: case .mldsa:
// https://www.ietf.org/archive/id/draft-sfluhrer-ssh-mldsa-04.txt // https://datatracker.ietf.org/doc/html/draft-sfluhrer-ssh-mldsa-05
openSSHIdentifier(for: secret.keyType).lengthAndData + openSSHIdentifier(for: secret.keyType).lengthAndData +
secret.publicKey.lengthAndData secret.publicKey.lengthAndData
case .rsa: case .rsa:

View File

@@ -1,20 +1,20 @@
import Foundation import Foundation
/// Reads OpenSSH protocol data. /// Reads OpenSSH protocol data.
final class OpenSSHReader { public final class OpenSSHReader {
var remaining: Data var remaining: Data
var done = false var done = false
/// Initialize the reader with an OpenSSH data payload. /// Initialize the reader with an OpenSSH data payload.
/// - Parameter data: The data to read. /// - Parameter data: The data to read.
init(data: Data) { public init(data: Data) {
remaining = Data(data) remaining = Data(data)
} }
/// Reads the next chunk of data from the playload. /// Reads the next chunk of data from the playload.
/// - Returns: The next chunk of data. /// - Returns: The next chunk of data.
func readNextChunk(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> Data { public func readNextChunk(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> Data {
let length = try readNextBytes(as: UInt32.self, convertEndianness: convertEndianness) let length = try readNextBytes(as: UInt32.self, convertEndianness: convertEndianness)
guard remaining.count >= length else { throw .beyondBounds } guard remaining.count >= length else { throw .beyondBounds }
let dataRange = 0..<Int(length) let dataRange = 0..<Int(length)
@@ -26,7 +26,7 @@ final class OpenSSHReader {
return ret return ret
} }
func readNextBytes<T: FixedWidthInteger>(as: T.Type, convertEndianness: Bool = true) throws(OpenSSHReaderError) -> T { public func readNextBytes<T: FixedWidthInteger>(as: T.Type, convertEndianness: Bool = true) throws(OpenSSHReaderError) -> T {
let size = MemoryLayout<T>.size let size = MemoryLayout<T>.size
guard remaining.count >= size else { throw .beyondBounds } guard remaining.count >= size else { throw .beyondBounds }
let lengthRange = 0..<size let lengthRange = 0..<size
@@ -39,16 +39,29 @@ final class OpenSSHReader {
return convertEndianness ? T(value.bigEndian) : T(value) return convertEndianness ? T(value.bigEndian) : T(value)
} }
func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String { public func readNextByteAsBool() throws(OpenSSHReaderError) -> Bool {
let size = MemoryLayout<Bool>.size
guard remaining.count >= size else { throw .beyondBounds }
let lengthRange = 0..<size
let lengthChunk = remaining[lengthRange]
remaining.removeSubrange(lengthRange)
if remaining.isEmpty {
done = true
}
return unsafe lengthChunk.bytes.unsafeLoad(as: Bool.self)
}
public func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String {
try String(decoding: readNextChunk(convertEndianness: convertEndianness), as: UTF8.self) try String(decoding: readNextChunk(convertEndianness: convertEndianness), as: UTF8.self)
} }
func readNextChunkAsSubReader(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> OpenSSHReader { public func readNextChunkAsSubReader(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> OpenSSHReader {
OpenSSHReader(data: try readNextChunk(convertEndianness: convertEndianness)) OpenSSHReader(data: try readNextChunk(convertEndianness: convertEndianness))
} }
} }
public enum OpenSSHReaderError: Error, Codable { public enum OpenSSHReaderError: Error, Codable {
case incorrectFormat
case beyondBounds case beyondBounds
} }

View File

@@ -17,7 +17,7 @@ public struct OpenSSHSignatureWriter: Sendable {
// https://datatracker.ietf.org/doc/html/rfc5656#section-3.1 // https://datatracker.ietf.org/doc/html/rfc5656#section-3.1
ecdsaSignature(signature, keyType: secret.keyType) ecdsaSignature(signature, keyType: secret.keyType)
case .mldsa: case .mldsa:
// https://datatracker.ietf.org/doc/html/draft-sfluhrer-ssh-mldsa-00#name-public-key-algorithms // https://datatracker.ietf.org/doc/html/draft-sfluhrer-ssh-mldsa-05
mldsaSignature(signature, keyType: secret.keyType) mldsaSignature(signature, keyType: secret.keyType)
case .rsa: case .rsa:
// https://datatracker.ietf.org/doc/html/rfc4253#section-6.6 // https://datatracker.ietf.org/doc/html/rfc4253#section-6.6
@@ -30,19 +30,28 @@ public struct OpenSSHSignatureWriter: Sendable {
extension OpenSSHSignatureWriter { extension OpenSSHSignatureWriter {
/// Converts a fixed-width big-endian integer (e.g. r/s from CryptoKit rawRepresentation) into an SSH mpint.
/// Strips unnecessary leading zeros and prefixes `0x00` if needed to keep the value positive.
private func mpint(fromFixedWidthPositiveBytes bytes: Data) -> Data {
// mpint zero is encoded as a string with zero bytes of data.
guard let firstNonZeroIndex = bytes.firstIndex(where: { $0 != 0x00 }) else {
return Data()
}
let trimmed = Data(bytes[firstNonZeroIndex...])
if let first = trimmed.first, first >= 0x80 {
var prefixed = Data([0x00])
prefixed.append(trimmed)
return prefixed
}
return trimmed
}
func ecdsaSignature(_ rawRepresentation: Data, keyType: KeyType) -> Data { func ecdsaSignature(_ rawRepresentation: Data, keyType: KeyType) -> Data {
let rawLength = rawRepresentation.count/2 let rawLength = rawRepresentation.count/2
// Check if we need to pad with 0x00 to prevent certain let r = mpint(fromFixedWidthPositiveBytes: Data(rawRepresentation[0..<rawLength]))
// ssh servers from thinking r or s is negative let s = mpint(fromFixedWidthPositiveBytes: Data(rawRepresentation[rawLength...]))
let paddingRange: ClosedRange<UInt8> = 0x80...0xFF
var r = Data(rawRepresentation[0..<rawLength])
if paddingRange ~= r.first! {
r.insert(0x00, at: 0)
}
var s = Data(rawRepresentation[rawLength...])
if paddingRange ~= s.first! {
s.insert(0x00, at: 0)
}
var signatureChunk = Data() var signatureChunk = Data()
signatureChunk.append(r.lengthAndData) signatureChunk.append(r.lengthAndData)

View File

@@ -1,104 +0,0 @@
import Foundation
import OSLog
import SecretKit
public protocol SSHAgentInputParserProtocol {
func parse(data: Data) async throws -> SSHAgent.Request
}
public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "InputParser")
public init() {
}
public func parse(data: Data) throws(AgentParsingError) -> SSHAgent.Request {
logger.debug("Parsing new data")
guard data.count > 4 else {
throw .invalidData
}
let specifiedLength = unsafe (data[0..<4].bytes.unsafeLoad(as: UInt32.self).bigEndian) + 4
let rawRequestInt = data[4]
let remainingDataRange = 5..<min(Int(specifiedLength), data.count)
lazy var body: Data = { Data(data[remainingDataRange]) }()
switch rawRequestInt {
case SSHAgent.Request.requestIdentities.protocolID:
return .requestIdentities
case SSHAgent.Request.signRequest(.empty).protocolID:
do {
return .signRequest(try signatureRequestContext(from: body))
} catch {
throw .openSSHReader(error)
}
case SSHAgent.Request.addIdentity.protocolID:
return .addIdentity
case SSHAgent.Request.removeIdentity.protocolID:
return .removeIdentity
case SSHAgent.Request.removeAllIdentities.protocolID:
return .removeAllIdentities
case SSHAgent.Request.addIDConstrained.protocolID:
return .addIDConstrained
case SSHAgent.Request.addSmartcardKey.protocolID:
return .addSmartcardKey
case SSHAgent.Request.removeSmartcardKey.protocolID:
return .removeSmartcardKey
case SSHAgent.Request.lock.protocolID:
return .lock
case SSHAgent.Request.unlock.protocolID:
return .unlock
case SSHAgent.Request.addSmartcardKeyConstrained.protocolID:
return .addSmartcardKeyConstrained
case SSHAgent.Request.protocolExtension.protocolID:
return .protocolExtension
default:
return .unknown(rawRequestInt)
}
}
}
extension SSHAgentInputParser {
func signatureRequestContext(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext {
let reader = OpenSSHReader(data: data)
let rawKeyBlob = try reader.readNextChunk()
let keyBlob = certificatePublicKeyBlob(from: rawKeyBlob) ?? rawKeyBlob
let dataToSign = try reader.readNextChunk()
return SSHAgent.Request.SignatureRequestContext(keyBlob: keyBlob, dataToSign: dataToSign)
}
func certificatePublicKeyBlob(from hash: Data) -> Data? {
let reader = OpenSSHReader(data: hash)
do {
let certType = 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
} catch {
return nil
}
}
}
extension SSHAgentInputParser {
public enum AgentParsingError: Error, Codable {
case unknownRequest
case unhandledRequest
case invalidData
case openSSHReader(OpenSSHReaderError)
}
}

View File

@@ -19,7 +19,7 @@ extension SSHAgent {
case lock case lock
case unlock case unlock
case addSmartcardKeyConstrained case addSmartcardKeyConstrained
case protocolExtension case protocolExtension(ProtocolExtension)
case unknown(UInt8) case unknown(UInt8)
public var protocolID: UInt8 { public var protocolID: UInt8 {
@@ -60,18 +60,82 @@ extension SSHAgent {
public struct SignatureRequestContext: Sendable, Codable { public struct SignatureRequestContext: Sendable, Codable {
public let keyBlob: Data public let keyBlob: Data
public let dataToSign: Data public let dataToSign: SignaturePayload
public init(keyBlob: Data, dataToSign: Data) { public init(keyBlob: Data, dataToSign: SignaturePayload) {
self.keyBlob = keyBlob self.keyBlob = keyBlob
self.dataToSign = dataToSign self.dataToSign = dataToSign
} }
public static var empty: SignatureRequestContext { public static var empty: SignatureRequestContext {
SignatureRequestContext(keyBlob: Data(), dataToSign: Data()) SignatureRequestContext(keyBlob: Data(), dataToSign: SignaturePayload(raw: Data(), decoded: nil))
}
public struct SignaturePayload: Sendable, Codable {
public let raw: Data
public let decoded: DecodedPayload?
public init(
raw: Data,
decoded: DecodedPayload?
) {
self.raw = raw
self.decoded = decoded
}
public enum DecodedPayload: Sendable, Codable {
case sshConnection(SSHConnectionPayload)
case sshSig(SSHSigPayload)
public struct SSHConnectionPayload: Sendable, Codable {
public let username: String
public let hasSignature: Bool
public let publicKeyAlgorithm: String
public let publicKey: Data
public let hostKey: Data
public init(
username: String,
hasSignature: Bool,
publicKeyAlgorithm: String,
publicKey: Data,
hostKey: Data
) {
self.username = username
self.hasSignature = hasSignature
self.publicKeyAlgorithm = publicKeyAlgorithm
self.publicKey = publicKey
self.hostKey = hostKey
}
}
public struct SSHSigPayload: Sendable, Codable {
public let namespace: String
public let hashAlgorithm: String
public let hash: Data
public init(
namespace: String,
hashAlgorithm: String,
hash: Data,
) {
self.namespace = namespace
self.hashAlgorithm = hashAlgorithm
self.hash = hash
}
}
}
} }
} }
} }
/// The type of the SSH Agent Response, as described in https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-5.1 /// The type of the SSH Agent Response, as described in https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-5.1
@@ -88,8 +152,8 @@ extension SSHAgent {
switch self { switch self {
case .agentFailure: "SSH_AGENT_FAILURE" case .agentFailure: "SSH_AGENT_FAILURE"
case .agentSuccess: "SSH_AGENT_SUCCESS" case .agentSuccess: "SSH_AGENT_SUCCESS"
case .agentIdentitiesAnswer: "SSH_AGENT_IDENTITIES_ANSWER" case .agentIdentitiesAnswer: "SSH2_AGENT_IDENTITIES_ANSWER"
case .agentSignResponse: "SSH_AGENT_SIGN_RESPONSE" case .agentSignResponse: "SSH2_AGENT_SIGN_RESPONSE"
case .agentExtensionFailure: "SSH_AGENT_EXTENSION_FAILURE" case .agentExtensionFailure: "SSH_AGENT_EXTENSION_FAILURE"
case .agentExtensionResponse: "SSH_AGENT_EXTENSION_RESPONSE" case .agentExtensionResponse: "SSH_AGENT_EXTENSION_RESPONSE"
} }

View File

@@ -0,0 +1,76 @@
import Foundation
// Extensions, as defined in https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.agent
extension SSHAgent {
public enum ProtocolExtension: CustomDebugStringConvertible, Codable, Sendable {
case openSSH(OpenSSHExtension)
case unknown(String)
public var debugDescription: String {
switch self {
case let .openSSH(protocolExtension):
protocolExtension.debugDescription
case .unknown(let string):
"Unknown (\(string))"
}
}
public static var empty: ProtocolExtension {
.unknown("empty")
}
private struct ProtocolExtensionParsingError: Error {}
}
}
extension SSHAgent.ProtocolExtension {
public enum OpenSSHExtension: CustomDebugStringConvertible, Codable, Sendable {
case sessionBind(SessionBindContext)
case unknown(String)
public static var domain: String {
"openssh.com"
}
public var name: String {
switch self {
case .sessionBind:
"session-bind"
case .unknown(let name):
name
}
}
public var debugDescription: String {
"\(name)@\(OpenSSHExtension.domain)"
}
}
}
extension SSHAgent.ProtocolExtension.OpenSSHExtension {
public struct SessionBindContext: Codable, Sendable {
public let hostKey: Data
public let sessionID: Data
public let signature: Data
public let forwarding: Bool
public init(hostKey: Data, sessionID: Data, signature: Data, forwarding: Bool) {
self.hostKey = hostKey
self.sessionID = sessionID
self.signature = signature
self.forwarding = forwarding
}
public static let empty = SessionBindContext(hostKey: Data(), sessionID: Data(), signature: Data(), forwarding: false)
}
}

View File

@@ -12,9 +12,11 @@ public final class Agent: Sendable {
private let witness: SigningWitness? private let witness: SigningWitness?
private let publicKeyWriter = OpenSSHPublicKeyWriter() private let publicKeyWriter = OpenSSHPublicKeyWriter()
private let signatureWriter = OpenSSHSignatureWriter() private let signatureWriter = OpenSSHSignatureWriter()
// private let certificateHandler = OpenSSHCertificateHandler() private let certificateHandler = OpenSSHCertificateHandler()
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "Agent") private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "Agent")
@MainActor private var sessionID: SSHAgent.ProtocolExtension.OpenSSHExtension.SessionBindContext?
/// Initializes an agent with a store list and a witness. /// Initializes an agent with a store list and a witness.
/// - Parameters: /// - Parameters:
/// - storeList: The `SecretStoreList` to make available. /// - storeList: The `SecretStoreList` to make available.
@@ -24,7 +26,7 @@ public final class Agent: Sendable {
self.storeList = storeList self.storeList = storeList
self.witness = witness self.witness = witness
Task { @MainActor in Task { @MainActor in
// await certificateHandler.reloadCertificates(for: storeList.allSecrets) await certificateHandler.reloadCertificates(for: storeList.allSecrets)
} }
} }
@@ -33,6 +35,7 @@ public final class Agent: Sendable {
extension Agent { extension Agent {
public func handle(request: SSHAgent.Request, provenance: SigningRequestProvenance) async -> Data { public func handle(request: SSHAgent.Request, provenance: SigningRequestProvenance) async -> Data {
logger.debug("Agent received request of type \(request.debugDescription)")
// Depending on the launch context (such as after macOS update), the agent may need to reload secrets before acting // Depending on the launch context (such as after macOS update), the agent may need to reload secrets before acting
await reloadSecretsIfNeccessary() await reloadSecretsIfNeccessary()
var response = Data() var response = Data()
@@ -43,9 +46,34 @@ extension Agent {
response.append(await identities()) response.append(await identities())
logger.debug("Agent returned \(SSHAgent.Response.agentIdentitiesAnswer.debugDescription)") logger.debug("Agent returned \(SSHAgent.Response.agentIdentitiesAnswer.debugDescription)")
case .signRequest(let context): case .signRequest(let context):
if let boundSession = await sessionID {
switch context.dataToSign.decoded {
case .sshConnection(let payload):
guard payload.hostKey == boundSession.hostKey else {
logger.error("Agent received bind request, but host key does not match signature reqeust host key.")
throw BindingFailure()
}
case .sshSig:
// SSHSIG does not have a host binding payload.
break
default:
break
}
}
response.append(SSHAgent.Response.agentSignResponse.data) response.append(SSHAgent.Response.agentSignResponse.data)
response.append(try await sign(data: context.dataToSign, keyBlob: context.keyBlob, provenance: provenance)) response.append(try await sign(data: context.dataToSign.raw, keyBlob: context.keyBlob, provenance: provenance))
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)") logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
case .protocolExtension(.openSSH(.sessionBind(let bind))):
response = try await MainActor.run {
guard sessionID == nil else {
logger.error("Agent received bind request, but already bound.")
throw BindingFailure()
}
logger.debug("Agent bound")
sessionID = bind
return SSHAgent.Response.agentSuccess.data
}
logger.debug("Agent returned \(SSHAgent.Response.agentSuccess.debugDescription)")
case .unknown(let value): case .unknown(let value):
logger.error("Agent received unknown request of type \(value).") logger.error("Agent received unknown request of type \(value).")
throw UnhandledRequestError() throw UnhandledRequestError()
@@ -68,7 +96,7 @@ extension Agent {
/// - Returns: An OpenSSH formatted Data payload listing the identities available for signing operations. /// - Returns: An OpenSSH formatted Data payload listing the identities available for signing operations.
func identities() async -> Data { func identities() async -> Data {
let secrets = await storeList.allSecrets let secrets = await storeList.allSecrets
// await certificateHandler.reloadCertificates(for: secrets) await certificateHandler.reloadCertificates(for: secrets)
var count = 0 var count = 0
var keyData = Data() var keyData = Data()
@@ -78,11 +106,11 @@ extension Agent {
keyData.append(publicKeyWriter.comment(secret: secret).lengthAndData) keyData.append(publicKeyWriter.comment(secret: secret).lengthAndData)
count += 1 count += 1
// if let (certificateData, name) = try? await certificateHandler.keyBlobAndName(for: secret) { if let (certificateData, name) = try? await certificateHandler.keyBlobAndName(for: secret) {
// keyData.append(certificateData.lengthAndData) keyData.append(certificateData.lengthAndData)
// keyData.append(name.lengthAndData) keyData.append(name.lengthAndData)
// count += 1 count += 1
// } }
} }
logger.log("Agent enumerated \(count) identities") logger.log("Agent enumerated \(count) identities")
var countBigEndian = UInt32(count).bigEndian var countBigEndian = UInt32(count).bigEndian
@@ -97,7 +125,7 @@ extension Agent {
/// - Returns: An OpenSSH formatted Data payload containing the signed data response. /// - Returns: An OpenSSH formatted Data payload containing the signed data response.
func sign(data: Data, keyBlob: Data, provenance: SigningRequestProvenance) async throws -> Data { func sign(data: Data, keyBlob: Data, provenance: SigningRequestProvenance) async throws -> Data {
guard let (secret, store) = await secret(matching: keyBlob) else { guard let (secret, store) = await secret(matching: keyBlob) else {
let keyBlobHex = keyBlob.formatted(.hex()) let keyBlobHex = keyBlob.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }.joined()
logger.debug("Agent did not have a key matching \(keyBlobHex)") logger.debug("Agent did not have a key matching \(keyBlobHex)")
throw NoMatchingKeyError() throw NoMatchingKeyError()
} }
@@ -145,6 +173,7 @@ extension Agent {
struct NoMatchingKeyError: Error {} struct NoMatchingKeyError: Error {}
struct UnhandledRequestError: Error {} struct UnhandledRequestError: Error {}
struct BindingFailure: Error {}
} }

View File

@@ -6,7 +6,7 @@ import SSHProtocolKit
/// Manages storage and lookup for OpenSSH certificates. /// Manages storage and lookup for OpenSSH certificates.
public actor OpenSSHCertificateHandler: Sendable { public actor OpenSSHCertificateHandler: Sendable {
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.homeDirectory) private let publicKeyFileStoreController = PublicKeyFileStoreController(directory: URL.publicKeyDirectory)
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "OpenSSHCertificateHandler") private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "OpenSSHCertificateHandler")
private let writer = OpenSSHPublicKeyWriter() private let writer = OpenSSHPublicKeyWriter()
private var keyBlobsAndNames: [AnySecret: (Data, Data)] = [:] private var keyBlobsAndNames: [AnySecret: (Data, Data)] = [:]
@@ -22,6 +22,9 @@ public actor OpenSSHCertificateHandler: Sendable {
logger.log("No certificates, short circuiting") logger.log("No certificates, short circuiting")
return 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`` /// Attempts to find an OpenSSH Certificate that corresponds to a ``Secret``
@@ -31,5 +34,56 @@ public actor OpenSSHCertificateHandler: Sendable {
keyBlobsAndNames[AnySecret(secret)] 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

@@ -1,6 +1,8 @@
import Foundation import Foundation
import OSLog import OSLog
import SecretKit import SecretKit
import SSHProtocolKit
import Common
/// Controller responsible for writing public keys to disk, so that they're easily accessible by scripts. /// Controller responsible for writing public keys to disk, so that they're easily accessible by scripts.
public final class PublicKeyFileStoreController: Sendable { public final class PublicKeyFileStoreController: Sendable {
@@ -10,8 +12,8 @@ public final class PublicKeyFileStoreController: Sendable {
private let keyWriter = OpenSSHPublicKeyWriter() private let keyWriter = OpenSSHPublicKeyWriter()
/// Initializes a PublicKeyFileStoreController. /// Initializes a PublicKeyFileStoreController.
public init(homeDirectory: URL) { public init(directory: URL) {
directory = homeDirectory.appending(component: "PublicKeys") self.directory = directory
} }
/// Writes out the keys specified to disk. /// Writes out the keys specified to disk.
@@ -20,7 +22,7 @@ public final class PublicKeyFileStoreController: Sendable {
public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws { public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws {
logger.log("Writing public keys to disk") logger.log("Writing public keys to disk")
if clear { if clear {
let validPaths = Set(secrets.map { publicKeyPath(for: $0) }) let validPaths = Set(secrets.map { URL.publicKeyPath(for: $0, in: directory) })
.union(Set(secrets.map { sshCertificatePath(for: $0) })) .union(Set(secrets.map { sshCertificatePath(for: $0) }))
let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: directory.path())) ?? [] let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: directory.path())) ?? []
let fullPathContents = contentsOfDirectory.map { directory.appending(path: $0).path() } let fullPathContents = contentsOfDirectory.map { directory.appending(path: $0).path() }
@@ -34,21 +36,13 @@ public final class PublicKeyFileStoreController: Sendable {
} }
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false, attributes: nil) try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false, attributes: nil)
for secret in secrets { for secret in secrets {
let path = publicKeyPath(for: secret) let path = URL.publicKeyPath(for: secret, in: directory)
let data = Data(keyWriter.openSSHString(secret: secret).utf8) let data = Data(keyWriter.openSSHString(secret: secret).utf8)
FileManager.default.createFile(atPath: path, contents: data, attributes: nil) FileManager.default.createFile(atPath: path, contents: data, attributes: nil)
} }
logger.log("Finished writing public keys") logger.log("Finished writing public keys")
} }
/// The path for a Secret's public key.
/// - Parameter secret: The Secret to return the path for.
/// - Returns: The path to the Secret's public key.
/// - Warning: This method returning a path does not imply that a key has been written to disk already. This method only describes where it will be written to.
public func publicKeyPath<SecretType: Secret>(for secret: SecretType) -> String {
let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "")
return directory.appending(component: "\(minimalHex).pub").path()
}
/// Short-circuit check to ship enumerating a bunch of paths if there's nothing in the cert directory. /// Short-circuit check to ship enumerating a bunch of paths if there's nothing in the cert directory.
public var hasAnyCertificates: Bool { public var hasAnyCertificates: Bool {

View File

@@ -0,0 +1,253 @@
import Foundation
import OSLog
import SecretKit
import SSHProtocolKit
import CryptoKit
public protocol SSHAgentInputParserProtocol {
func parse(data: Data) async throws -> SSHAgent.Request
}
public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "InputParser")
public init() {
}
public func parse(data: Data) throws(AgentParsingError) -> SSHAgent.Request {
logger.debug("Parsing new data")
guard data.count > 4 else {
throw .invalidData
}
let specifiedLength = unsafe (data[0..<4].bytes.unsafeLoad(as: UInt32.self).bigEndian) + 4
let rawRequestInt = data[4]
let remainingDataRange = 5..<min(Int(specifiedLength), data.count)
lazy var body: Data = { Data(data[remainingDataRange]) }()
switch rawRequestInt {
case SSHAgent.Request.requestIdentities.protocolID:
return .requestIdentities
case SSHAgent.Request.signRequest(.empty).protocolID:
do {
return .signRequest(try signatureRequestContext(from: body))
} catch {
throw .openSSHReader(error)
}
case SSHAgent.Request.addIdentity.protocolID:
return .addIdentity
case SSHAgent.Request.removeIdentity.protocolID:
return .removeIdentity
case SSHAgent.Request.removeAllIdentities.protocolID:
return .removeAllIdentities
case SSHAgent.Request.addIDConstrained.protocolID:
return .addIDConstrained
case SSHAgent.Request.addSmartcardKey.protocolID:
return .addSmartcardKey
case SSHAgent.Request.removeSmartcardKey.protocolID:
return .removeSmartcardKey
case SSHAgent.Request.lock.protocolID:
return .lock
case SSHAgent.Request.unlock.protocolID:
return .unlock
case SSHAgent.Request.addSmartcardKeyConstrained.protocolID:
return .addSmartcardKeyConstrained
case SSHAgent.Request.protocolExtension(.empty).protocolID:
return .protocolExtension(try protocolExtension(from: body))
default:
return .unknown(rawRequestInt)
}
}
}
extension SSHAgentInputParser {
private enum Constants {
static let userAuthMagic: UInt8 = 50 // SSH2_MSG_USERAUTH_REQUEST
static let sshSigMagic = Data("SSHSIG".utf8)
}
func signatureRequestContext(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext {
let reader = OpenSSHReader(data: data)
let rawKeyBlob = try reader.readNextChunk()
let keyBlob = certificatePublicKeyBlob(from: rawKeyBlob) ?? rawKeyBlob
let rawPayload = try reader.readNextChunk()
let payload: SSHAgent.Request.SignatureRequestContext.SignaturePayload
do {
if rawPayload.count > 6 && rawPayload[0..<6] == Constants.sshSigMagic {
payload = .init(raw: rawPayload, decoded: .sshSig(try sshSigPayload(from: rawPayload[6...])))
} else {
payload = .init(raw: rawPayload, decoded: .sshConnection(try sshConnectionPayload(from: rawPayload)))
}
} catch {
payload = .init(raw: rawPayload, decoded: nil)
}
return SSHAgent.Request.SignatureRequestContext(keyBlob: keyBlob, dataToSign: payload)
}
func sshSigPayload(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext.SignaturePayload.DecodedPayload.SSHSigPayload {
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig#L79
let payloadReader = OpenSSHReader(data: data)
let namespace = try payloadReader.readNextChunkAsString()
_ = try payloadReader.readNextChunk() // reserved
let hashAlgorithm = try payloadReader.readNextChunkAsString()
let hash = try payloadReader.readNextChunk()
return .init(
namespace: namespace,
hashAlgorithm: hashAlgorithm,
hash: hash
)
}
func sshConnectionPayload(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext.SignaturePayload.DecodedPayload.SSHConnectionPayload {
let payloadReader = OpenSSHReader(data: data)
_ = try payloadReader.readNextChunk()
let magic = try payloadReader.readNextBytes(as: UInt8.self, convertEndianness: false)
guard magic == Constants.userAuthMagic else { throw .incorrectFormat }
let username = try payloadReader.readNextChunkAsString()
_ = try payloadReader.readNextChunkAsString() // "ssh-connection"
_ = try payloadReader.readNextChunkAsString() // "publickey-hostbound-v00@openssh.com"
let hasSignature = try payloadReader.readNextByteAsBool()
let algorithm = try payloadReader.readNextChunkAsString()
let publicKeyReader = try payloadReader.readNextChunkAsSubReader()
_ = try publicKeyReader.readNextChunk()
_ = try publicKeyReader.readNextChunk()
let publicKey = try publicKeyReader.readNextChunk()
let hostKeyReader = try payloadReader.readNextChunkAsSubReader()
_ = try hostKeyReader.readNextChunk()
let hostKey = try hostKeyReader.readNextChunk()
return .init(
username: username,
hasSignature: hasSignature,
publicKeyAlgorithm: algorithm,
publicKey: publicKey,
hostKey: hostKey,
)
}
func protocolExtension(from data: Data) throws(AgentParsingError) -> SSHAgent.ProtocolExtension {
do {
let reader = OpenSSHReader(data: data)
let nameRaw = try reader.readNextChunkAsString()
let nameSplit = nameRaw.split(separator: "@")
guard nameSplit.count == 2 else {
throw AgentParsingError.invalidData
}
let (name, domain) = (nameSplit[0], nameSplit[1])
switch domain {
case SSHAgent.ProtocolExtension.OpenSSHExtension.domain:
switch name {
case SSHAgent.ProtocolExtension.OpenSSHExtension.sessionBind(.empty).name:
let hostkeyBlob = try reader.readNextChunkAsSubReader()
let hostKeyType = try hostkeyBlob.readNextChunkAsString()
let hostKeyData = try hostkeyBlob.readNextChunk()
let sessionID = try reader.readNextChunk()
let signatureBlob = try reader.readNextChunkAsSubReader()
_ = try signatureBlob.readNextChunk() // key type again
let signature = try signatureBlob.readNextChunk()
let forwarding = try reader.readNextByteAsBool()
switch hostKeyType {
// FIXME: FACTOR OUT?
case "ssh-ed25519":
let hostKey = try CryptoKit.Curve25519.Signing.PublicKey(rawRepresentation: hostKeyData)
guard hostKey.isValidSignature(signature, for: sessionID) else {
throw AgentParsingError.incorrectSignature
}
case "ecdsa-sha2-nistp256":
let hostKey = try CryptoKit.P256.Signing.PublicKey(rawRepresentation: hostKeyData)
guard hostKey.isValidSignature(try .init(rawRepresentation: signature), for: sessionID) else {
throw AgentParsingError.incorrectSignature
}
case "ecdsa-sha2-nistp384":
let hostKey = try CryptoKit.P384.Signing.PublicKey(rawRepresentation: hostKeyData)
guard hostKey.isValidSignature(try .init(rawRepresentation: signature), for: sessionID) else {
throw AgentParsingError.incorrectSignature
}
case "ssh-mldsa-65":
if #available(macOS 26.0, *) {
let hostKey = try CryptoKit.MLDSA65.PublicKey(rawRepresentation: hostKeyData)
guard hostKey.isValidSignature(signature, for: sessionID) else {
throw AgentParsingError.incorrectSignature
}
} else {
throw AgentParsingError.unhandledRequest
}
case "ssh-mldsa-87":
if #available(macOS 26.0, *) {
let hostKey = try CryptoKit.MLDSA65.PublicKey(rawRepresentation: hostKeyData)
guard hostKey.isValidSignature(signature, for: sessionID) else {
throw AgentParsingError.incorrectSignature
}
} else {
throw AgentParsingError.unhandledRequest
}
case "ssh-rsa":
// FIXME: HANDLE
throw AgentParsingError.unhandledRequest
default:
throw AgentParsingError.unhandledRequest
}
let context = SSHAgent.ProtocolExtension.OpenSSHExtension.SessionBindContext(
hostKey: hostKeyData,
sessionID: sessionID,
signature: signature,
forwarding: forwarding
)
return .openSSH(.sessionBind(context))
default:
return .openSSH(.unknown(String(name)))
}
default:
return .unknown(nameRaw)
}
} catch let error as OpenSSHReaderError {
throw .openSSHReader(error)
} catch let error as AgentParsingError {
throw error
} catch {
throw .unknownRequest
}
}
func certificatePublicKeyBlob(from hash: Data) -> Data? {
let reader = OpenSSHReader(data: hash)
do {
let certType = String(decoding: try reader.readNextChunk(), as: UTF8.self)
switch certType {
case "ecdsa-sha2-nistp256-cert-v01@openssh.com",
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
"ecdsa-sha2-nistp521-cert-v01@openssh.com":
_ = try reader.readNextChunk() // nonce
let curveIdentifier = try reader.readNextChunk()
let publicKey = try reader.readNextChunk()
let openSSHIdentifier = certType.replacingOccurrences(of: "-cert-v01@openssh.com", with: "")
return openSSHIdentifier.lengthAndData +
curveIdentifier.lengthAndData +
publicKey.lengthAndData
default:
return nil
}
} catch {
return nil
}
}
}
extension SSHAgentInputParser {
public enum AgentParsingError: Error, Codable {
case unknownRequest
case unhandledRequest
case invalidData
case incorrectSignature
case openSSHReader(OpenSSHReaderError)
}
}

View File

@@ -0,0 +1,69 @@
import LocalAuthentication
/// A context describing a persisted authentication.
package final class PersistentAuthenticationContext<SecretType: Secret>: PersistedAuthenticationContext {
/// The Secret to persist authentication for.
let secret: SecretType
/// The LAContext used to authorize the persistent context.
package nonisolated(unsafe) let context: LAContext
/// An expiration date for the context.
/// - Note - Monotonic time instead of Date() to prevent people setting the clock back.
let monotonicExpiration: UInt64
/// Initializes a context.
/// - Parameters:
/// - secret: The Secret to persist authentication for.
/// - context: The LAContext used to authorize the persistent context.
/// - duration: The duration of the authorization context, in seconds.
init(secret: SecretType, context: LAContext, duration: TimeInterval) {
self.secret = secret
unsafe self.context = context
let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value
self.monotonicExpiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds)
}
/// A boolean describing whether or not the context is still valid.
package var valid: Bool {
clock_gettime_nsec_np(CLOCK_MONOTONIC) < monotonicExpiration
}
package var expiration: Date {
let remainingNanoseconds = monotonicExpiration - clock_gettime_nsec_np(CLOCK_MONOTONIC)
let remainingInSeconds = Measurement(value: Double(remainingNanoseconds), unit: UnitDuration.nanoseconds).converted(to: .seconds).value
return Date(timeIntervalSinceNow: remainingInSeconds)
}
}
package actor PersistentAuthenticationHandler<SecretType: Secret>: Sendable {
private var persistedAuthenticationContexts: [SecretType: PersistentAuthenticationContext<SecretType>] = [:]
package init() {
}
package func existingPersistedAuthenticationContext(secret: SecretType) -> PersistentAuthenticationContext<SecretType>? {
guard let persisted = persistedAuthenticationContexts[secret], persisted.valid else { return nil }
return persisted
}
package func persistAuthentication(secret: SecretType, forDuration duration: TimeInterval) async throws {
let newContext = LAContext()
newContext.touchIDAuthenticationAllowableReuseDuration = duration
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .spellOut
formatter.allowedUnits = [.hour, .minute, .day]
let durationString = formatter.string(from: duration)!
newContext.localizedReason = String(localized: .authContextPersistForDuration(secretName: secret.name, duration: durationString))
let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason)
guard success else { return }
let context = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration)
persistedAuthenticationContexts[secret] = context
}
}

View File

@@ -1,70 +0,0 @@
import LocalAuthentication
import SecretKit
extension SecureEnclave {
/// A context describing a persisted authentication.
final class PersistentAuthenticationContext: PersistedAuthenticationContext {
/// The Secret to persist authentication for.
let secret: Secret
/// The LAContext used to authorize the persistent context.
nonisolated(unsafe) let context: LAContext
/// An expiration date for the context.
/// - Note - Monotonic time instead of Date() to prevent people setting the clock back.
let monotonicExpiration: UInt64
/// Initializes a context.
/// - Parameters:
/// - secret: The Secret to persist authentication for.
/// - context: The LAContext used to authorize the persistent context.
/// - duration: The duration of the authorization context, in seconds.
init(secret: Secret, context: LAContext, duration: TimeInterval) {
self.secret = secret
unsafe self.context = context
let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value
self.monotonicExpiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds)
}
/// A boolean describing whether or not the context is still valid.
var valid: Bool {
clock_gettime_nsec_np(CLOCK_MONOTONIC) < monotonicExpiration
}
var expiration: Date {
let remainingNanoseconds = monotonicExpiration - clock_gettime_nsec_np(CLOCK_MONOTONIC)
let remainingInSeconds = Measurement(value: Double(remainingNanoseconds), unit: UnitDuration.nanoseconds).converted(to: .seconds).value
return Date(timeIntervalSinceNow: remainingInSeconds)
}
}
actor PersistentAuthenticationHandler: Sendable {
private var persistedAuthenticationContexts: [Secret: PersistentAuthenticationContext] = [:]
func existingPersistedAuthenticationContext(secret: Secret) -> PersistentAuthenticationContext? {
guard let persisted = persistedAuthenticationContexts[secret], persisted.valid else { return nil }
return persisted
}
func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
let newContext = LAContext()
newContext.touchIDAuthenticationAllowableReuseDuration = duration
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .spellOut
formatter.allowedUnits = [.hour, .minute, .day]
let durationString = formatter.string(from: duration)!
newContext.localizedReason = String(localized: .authContextPersistForDuration(secretName: secret.name, duration: durationString))
let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason)
guard success else { return }
let context = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration)
persistedAuthenticationContexts[secret] = context
}
}
}

View File

@@ -17,7 +17,7 @@ extension SecureEnclave {
} }
public let id = UUID() public let id = UUID()
public let name = String(localized: .secureEnclave) public let name = String(localized: .secureEnclave)
private let persistentAuthenticationHandler = PersistentAuthenticationHandler() private let persistentAuthenticationHandler = PersistentAuthenticationHandler<Secret>()
/// Initializes a Store. /// Initializes a Store.
@MainActor public init() { @MainActor public init() {

View File

@@ -34,6 +34,7 @@ extension SmartCard {
public var secrets: [Secret] { public var secrets: [Secret] {
state.secrets state.secrets
} }
private let persistentAuthenticationHandler = PersistentAuthenticationHandler<Secret>()
/// Initializes a Store. /// Initializes a Store.
public init() { public init() {
@@ -58,9 +59,15 @@ extension SmartCard {
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data { public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
guard let tokenID = await state.tokenID else { fatalError() } guard let tokenID = await state.tokenID else { fatalError() }
let context = LAContext() var context: LAContext
context.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name)) if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) {
context.localizedCancelTitle = String(localized: .authContextRequestDenyButton) context = unsafe existing.context
} else {
let newContext = LAContext()
newContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
context = newContext
}
let attributes = KeychainDictionary([ let attributes = KeychainDictionary([
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeyClass: kSecAttrKeyClassPrivate,
@@ -86,11 +93,12 @@ extension SmartCard {
return signature as Data return signature as Data
} }
public func existingPersistedAuthenticationContext(secret: Secret) -> PersistedAuthenticationContext? { public func existingPersistedAuthenticationContext(secret: Secret) async -> PersistedAuthenticationContext? {
nil await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret)
} }
public func persistAuthentication(secret: Secret, forDuration: TimeInterval) throws { public func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
try await persistentAuthenticationHandler.persistAuthentication(secret: secret, forDuration: duration)
} }
/// Reloads all secrets from the store. /// Reloads all secrets from the store.
@@ -163,7 +171,7 @@ extension SmartCard.Store {
let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)! let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)!
let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any] let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any]
let publicKey = publicKeyAttributes[kSecValueData] as! Data let publicKey = publicKeyAttributes[kSecValueData] as! Data
let attributes = Attributes(keyType: KeyType(secAttr: algorithmSecAttr, size: keySize)!, authentication: .unknown) let attributes = Attributes(keyType: KeyType(secAttr: algorithmSecAttr, size: keySize)!, authentication: .presenceRequired)
let secret = SmartCard.Secret(id: tokenID, name: name, publicKey: publicKey, attributes: attributes) let secret = SmartCard.Secret(id: tokenID, name: name, publicKey: publicKey, attributes: attributes)
guard signatureAlgorithm(for: secret) != nil else { return nil } guard signatureAlgorithm(for: secret) != nil else { return nil }
return secret return secret

View File

@@ -0,0 +1,26 @@
import Foundation
extension ProcessInfo {
private static let fallbackTeamID = "Z72PRUAWF6"
private static let teamID: String = {
#if DEBUG
guard let task = SecTaskCreateFromSelf(nil) else {
assertionFailure("SecTaskCreateFromSelf failed")
return fallbackTeamID
}
guard let value = SecTaskCopyValueForEntitlement(task, "com.apple.developer.team-identifier" as CFString, nil) as? String else {
assertionFailure("SecTaskCopyValueForEntitlement(com.apple.developer.team-identifier) failed")
return fallbackTeamID
}
return value
#else
/// Always use hardcoded team ID for release builds, just in case.
return fallbackTeamID
#endif
}()
public var teamID: String { Self.teamID }
}

View File

@@ -12,7 +12,7 @@ public final class XPCServiceDelegate: NSObject, NSXPCListenerDelegate {
newConnection.exportedInterface = NSXPCInterface(with: (any _XPCProtocol).self) newConnection.exportedInterface = NSXPCInterface(with: (any _XPCProtocol).self)
let exportedObject = exportedObject let exportedObject = exportedObject
newConnection.exportedObject = exportedObject newConnection.exportedObject = exportedObject
newConnection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = Z72PRUAWF6") newConnection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"\(ProcessInfo.processInfo.teamID)\"")
newConnection.resume() newConnection.resume()
return true return true
} }

View File

@@ -8,7 +8,7 @@ public struct XPCTypedSession<ResponseType: Codable & Sendable, ErrorType: Error
public init(serviceName: String, warmup: Bool = false) async throws { public init(serviceName: String, warmup: Bool = false) async throws {
let connection = NSXPCConnection(serviceName: serviceName) let connection = NSXPCConnection(serviceName: serviceName)
connection.remoteObjectInterface = NSXPCInterface(with: (any _XPCProtocol).self) connection.remoteObjectInterface = NSXPCInterface(with: (any _XPCProtocol).self)
connection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = Z72PRUAWF6") connection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"\(ProcessInfo.processInfo.teamID)\"")
connection.resume() connection.resume()
guard let proxy = connection.remoteObjectProxy as? _XPCProtocol else { fatalError() } guard let proxy = connection.remoteObjectProxy as? _XPCProtocol else { fatalError() }
self.connection = connection self.connection = connection

View File

@@ -1,8 +1,7 @@
import Foundation import Foundation
import Testing import Testing
@testable import SecretKit @testable import SecretKit
@testable import SecureEnclaveSecretKit import SSHProtocolKit
@testable import SmartCardSecretKit
@Suite struct OpenSSHPublicKeyWriterTests { @Suite struct OpenSSHPublicKeyWriterTests {
@@ -47,8 +46,8 @@ import Testing
extension OpenSSHPublicKeyWriterTests { extension OpenSSHPublicKeyWriterTests {
enum Constants { enum Constants {
static let ecdsa256Secret = SmartCard.Secret(id: Data(), name: "Test Key (ECDSA 256)", publicKey: Data(base64Encoded: "BOVEjgAA5PHqRgwykjN5qM21uWCHFSY/Sqo5gkHAkn+e1MMQKHOLga7ucB9b3mif33MBid59GRK9GEPVlMiSQwo=")!, attributes: Attributes(keyType: KeyType(algorithm: .ecdsa, size: 256), authentication: .notRequired, publicKeyAttribution: "test@example.com")) static let ecdsa256Secret = TestSecret(id: Data(), name: "Test Key (ECDSA 256)", publicKey: Data(base64Encoded: "BOVEjgAA5PHqRgwykjN5qM21uWCHFSY/Sqo5gkHAkn+e1MMQKHOLga7ucB9b3mif33MBid59GRK9GEPVlMiSQwo=")!, attributes: Attributes(keyType: KeyType(algorithm: .ecdsa, size: 256), authentication: .notRequired, publicKeyAttribution: "test@example.com"))
static let ecdsa384Secret = SmartCard.Secret(id: Data(), name: "Test Key (ECDSA 384)", publicKey: Data(base64Encoded: "BG2MNc/C5OTHFE2tBvbZCVcpOGa8vBMquiTLkH4lwkeqOPxhi+PyYUfQZMTRJNPiTyWPoMBqNiCIFRVv60yPN/AHufHaOgbdTP42EgMlMMImkAjYUEv9DESHTVIs2PW1yQ==")!, attributes: Attributes(keyType: KeyType(algorithm: .ecdsa, size: 384), authentication: .notRequired, publicKeyAttribution: "test@example.com")) static let ecdsa384Secret = TestSecret(id: Data(), name: "Test Key (ECDSA 384)", publicKey: Data(base64Encoded: "BG2MNc/C5OTHFE2tBvbZCVcpOGa8vBMquiTLkH4lwkeqOPxhi+PyYUfQZMTRJNPiTyWPoMBqNiCIFRVv60yPN/AHufHaOgbdTP42EgMlMMImkAjYUEv9DESHTVIs2PW1yQ==")!, attributes: Attributes(keyType: KeyType(algorithm: .ecdsa, size: 384), authentication: .notRequired, publicKeyAttribution: "test@example.com"))
} }

View File

@@ -1,8 +1,6 @@
import Foundation import Foundation
import Testing import Testing
@testable import SecretAgentKit import SSHProtocolKit
@testable import SecureEnclaveSecretKit
@testable import SmartCardSecretKit
@Suite struct OpenSSHReaderTests { @Suite struct OpenSSHReaderTests {

View File

@@ -0,0 +1,83 @@
import Foundation
import Testing
import SSHProtocolKit
@testable import SecretKit
@Suite struct OpenSSHSignatureWriterTests {
private let writer = OpenSSHSignatureWriter()
@Test func ecdsaMpintStripsUnnecessaryLeadingZeros() throws {
let secret = Constants.ecdsa256Secret
// r has a leading 0x00 followed by 0x01 (< 0x80): the mpint must not keep the leading zero.
let rBytes: [UInt8] = [0x00] + (1...31).map { UInt8($0) }
let r = Data(rBytes)
// s has two leading 0x00 bytes followed by 0x7f (< 0x80): the mpint must not keep the leading zeros.
let sBytes: [UInt8] = [0x00, 0x00, 0x7f] + Array(repeating: UInt8(0x01), count: 29)
let s = Data(sBytes)
let rawRepresentation = r + s
let response = writer.data(secret: secret, signature: rawRepresentation)
let (parsedR, parsedS) = try parseEcdsaSignatureMpints(from: response)
#expect(parsedR == Data((1...31).map { UInt8($0) }))
#expect(parsedS == Data([0x7f] + Array(repeating: UInt8(0x01), count: 29)))
}
@Test func ecdsaMpintPrefixesZeroWhenHighBitSet() throws {
let secret = Constants.ecdsa256Secret
// r starts with 0x80 (high bit set): mpint must be prefixed with 0x00.
let r = Data([UInt8(0x80)] + Array(repeating: UInt8(0x01), count: 31))
let s = Data([UInt8(0x01)] + Array(repeating: UInt8(0x02), count: 31))
let rawRepresentation = r + s
let response = writer.data(secret: secret, signature: rawRepresentation)
let (parsedR, parsedS) = try parseEcdsaSignatureMpints(from: response)
#expect(parsedR == Data([0x00, 0x80] + Array(repeating: UInt8(0x01), count: 31)))
#expect(parsedS == Data([0x01] + Array(repeating: UInt8(0x02), count: 31)))
}
}
private extension OpenSSHSignatureWriterTests {
enum Constants {
static let ecdsa256Secret = TestSecret(
id: Data(),
name: "Test Key (ECDSA 256)",
publicKey: Data(repeating: 0x01, count: 65),
attributes: Attributes(
keyType: KeyType(algorithm: .ecdsa, size: 256),
authentication: .notRequired,
publicKeyAttribution: "test@example.com"
)
)
}
enum ParseError: Error {
case eof
case invalidAlgorithm
}
func parseEcdsaSignatureMpints(from openSSHSignedData: Data) throws -> (r: Data, s: Data) {
let reader = OpenSSHReader(data: openSSHSignedData)
// Prefix
_ = try reader.readNextBytes(as: UInt32.self)
let algorithm = try reader.readNextChunkAsString()
guard algorithm == "ecdsa-sha2-nistp256" else {
throw ParseError.invalidAlgorithm
}
let sigReader = try reader.readNextChunkAsSubReader()
let r = try sigReader.readNextChunk()
let s = try sigReader.readNextChunk()
return (r, s)
}
}

View File

@@ -0,0 +1,11 @@
import Foundation
import SecretKit
public struct TestSecret: SecretKit.Secret {
public let id: Data
public let name: String
public let publicKey: Data
public var attributes: Attributes
}

View File

@@ -1,6 +1,7 @@
import Foundation import Foundation
import Testing import Testing
import CryptoKit import CryptoKit
@testable import SSHProtocolKit
@testable import SecretKit @testable import SecretKit
@testable import SecretAgentKit @testable import SecretAgentKit
@@ -44,8 +45,8 @@ import CryptoKit
let agent = Agent(storeList: list) let agent = Agent(storeList: list)
let response = await agent.handle(request: request, provenance: .test) let response = await agent.handle(request: request, provenance: .test)
let responseReader = OpenSSHReader(data: response) let responseReader = OpenSSHReader(data: response)
let length = try responseReader.readNextBytes(as: UInt32.self).bigEndian let length = try responseReader.readNextBytes(as: UInt32.self)
let type = try responseReader.readNextBytes(as: UInt8.self).bigEndian let type = try responseReader.readNextBytes(as: UInt8.self)
#expect(length == response.count - MemoryLayout<UInt32>.size) #expect(length == response.count - MemoryLayout<UInt32>.size)
#expect(type == SSHAgent.Response.agentSignResponse.rawValue) #expect(type == SSHAgent.Response.agentSignResponse.rawValue)
let outer = OpenSSHReader(data: responseReader.remaining) let outer = OpenSSHReader(data: responseReader.remaining)

View File

@@ -1,6 +1,7 @@
import Foundation import Foundation
import SecretKit import SecretKit
import CryptoKit import CryptoKit
import SSHProtocolKit
struct Stub {} struct Stub {}

View File

@@ -6,8 +6,6 @@ import SmartCardSecretKit
import SecretAgentKit import SecretAgentKit
import Brief import Brief
import Observation import Observation
import SSHProtocolKit
import CertificateKit
import Common import Common
@main @main
@@ -18,15 +16,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let cryptoKit = SecureEnclave.Store() let cryptoKit = SecureEnclave.Store()
let migrator = SecureEnclave.CryptoKitMigrator() let migrator = SecureEnclave.CryptoKitMigrator()
try? migrator.migrate(to: cryptoKit) try? migrator.migrate(to: cryptoKit)
let certsMigrator = CertificateKitMigrator(homeDirectory: URL.homeDirectory)
try? certsMigrator.migrate()
list.add(store: cryptoKit) list.add(store: cryptoKit)
list.add(store: SmartCard.Store()) list.add(store: SmartCard.Store())
return list return list
}() }()
private let updater = Updater(checkOnLaunch: true) private let updater = Updater(checkOnLaunch: true)
private let notifier = Notifier() private let notifier = Notifier()
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.homeDirectory) private let publicKeyFileStoreController = PublicKeyFileStoreController(directory: URL.publicKeyDirectory)
private lazy var agent: Agent = { private lazy var agent: Agent = {
Agent(storeList: storeList, witness: notifier) Agent(storeList: storeList, witness: notifier)
}() }()
@@ -39,9 +35,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
logger.debug("SecretAgent finished launching") logger.debug("SecretAgent finished launching")
Task { Task {
let inputParser = try await XPCAgentInputParser()
for await session in socketController.sessions { for await session in socketController.sessions {
Task { Task {
let inputParser = try await XPCAgentInputParser()
do { do {
for await message in session.messages { for await message in session.messages {
let request = try await inputParser.parse(data: message) let request = try await inputParser.parse(data: message)

View File

@@ -4,16 +4,22 @@
<dict> <dict>
<key>com.apple.security.hardened-process</key> <key>com.apple.security.hardened-process</key>
<true/> <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> <key>com.apple.security.hardened-process.dyld-ro</key>
<true/> <true/>
<key>com.apple.security.hardened-process.enhanced-security-version</key> <key>com.apple.security.hardened-process.enhanced-security-version-string</key>
<integer>1</integer> <string>1</string>
<key>com.apple.security.hardened-process.hardened-heap</key> <key>com.apple.security.hardened-process.hardened-heap</key>
<true/> <true/>
<key>com.apple.security.hardened-process.platform-restrictions</key>
<integer>2</integer>
<key>com.apple.security.smartcard</key> <key>com.apple.security.smartcard</key>
<true/> <true/>
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
<string>2</string>
<key>keychain-access-groups</key> <key>keychain-access-groups</key>
<array> <array>
<string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string> <string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string>

View File

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

View File

@@ -12,11 +12,11 @@
<true/> <true/>
<key>com.apple.security.hardened-process.dyld-ro</key> <key>com.apple.security.hardened-process.dyld-ro</key>
<true/> <true/>
<key>com.apple.security.hardened-process.enhanced-security-version</key>
<integer>1</integer>
<key>com.apple.security.hardened-process.hardened-heap</key> <key>com.apple.security.hardened-process.hardened-heap</key>
<true/> <true/>
<key>com.apple.security.hardened-process.platform-restrictions</key> <key>com.apple.security.hardened-process.enhanced-security-version-string</key>
<integer>2</integer> <string>1</string>
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
<string>2</string>
</dict> </dict>
</plist> </plist>

View File

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

View File

@@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */; }; 2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */; };
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; }; 50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
5002C3AB2EEF483300FFAD22 /* XPCWrappers in Frameworks */ = {isa = PBXBuildFile; productRef = 5002C3AA2EEF483300FFAD22 /* XPCWrappers */; };
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3A278005E800DF2006 /* SecretKit */; }; 5003EF3B278005E800DF2006 /* SecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3A278005E800DF2006 /* SecretKit */; };
5003EF3D278005F300DF2006 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3C278005F300DF2006 /* Brief */; }; 5003EF3D278005F300DF2006 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3C278005F300DF2006 /* Brief */; };
5003EF3F278005F300DF2006 /* SecretAgentKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3E278005F300DF2006 /* SecretAgentKit */; }; 5003EF3F278005F300DF2006 /* SecretAgentKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3E278005F300DF2006 /* SecretAgentKit */; };
@@ -30,8 +31,6 @@
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; }; 504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; };
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */; }; 504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */; };
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; }; 50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.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 */; }; 50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.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 */; }; 50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; };
@@ -48,6 +47,7 @@
50692E5B2E6FF9D20043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 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 */; }; 50692E682E6FF9E20043C7BB /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50692E632E6FF9E20043C7BB /* main.swift */; };
50692E692E6FF9E20043C7BB /* SecretAgentInputParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50692E642E6FF9E20043C7BB /* SecretAgentInputParser.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, ); }; }; 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, ); }; }; 50692E702E6FFA6E0043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5079BA0E250F29BF00EA86F4 /* StoreListView.swift */; }; 5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5079BA0E250F29BF00EA86F4 /* StoreListView.swift */; };
@@ -61,6 +61,7 @@
50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79324026B7600D209EA /* Preview Assets.xcassets */; }; 50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79324026B7600D209EA /* Preview Assets.xcassets */; };
50A3B79724026B7600D209EA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79524026B7600D209EA /* Main.storyboard */; }; 50A3B79724026B7600D209EA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79524026B7600D209EA /* Main.storyboard */; };
50AE97002E5C1A420018C710 /* IntegrationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */; }; 50AE97002E5C1A420018C710 /* IntegrationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */; };
50B832C02F62202A00D2FCB8 /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = 50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */; };
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8550C24138C4F009958AC /* DeleteSecretView.swift */; }; 50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8550C24138C4F009958AC /* DeleteSecretView.swift */; };
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; }; 50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; };
50BDCB722E63BAF20072D2E7 /* AgentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */; }; 50BDCB722E63BAF20072D2E7 /* AgentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */; };
@@ -74,13 +75,6 @@
50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4C22E7765DF00C73783 /* AboutView.swift */; }; 50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4C22E7765DF00C73783 /* AboutView.swift */; };
50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; }; 50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; };
50E4C4C92E777E4200C73783 /* 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 */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -126,13 +120,6 @@
remoteGlobalIDString = 50692E4F2E6FF9D20043C7BB; remoteGlobalIDString = 50692E4F2E6FF9D20043C7BB;
remoteInfo = SecretAgentInputParser; remoteInfo = SecretAgentInputParser;
}; };
50E4C4D72E77C4B300C73783 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 50E4C4CD2E77C4B300C73783;
remoteInfo = SecretiveCertificateParser;
};
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
@@ -143,7 +130,6 @@
dstSubfolderSpec = 16; dstSubfolderSpec = 16;
files = ( files = (
50692D1D2E6FDB880043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */, 50692D1D2E6FDB880043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */,
50E4C4D92E77C4B300C73783 /* SecretiveCertificateParser.xpc in Embed XPC Services */,
50692E5B2E6FF9D20043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */, 50692E5B2E6FF9D20043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */,
); );
name = "Embed XPC Services"; name = "Embed XPC Services";
@@ -197,6 +183,8 @@
2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSecretView.swift; sourceTree = "<group>"; }; 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>"; }; 50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
5003EF39278005C800DF2006 /* Packages */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Packages; sourceTree = "<group>"; }; 5003EF39278005C800DF2006 /* Packages */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Packages; sourceTree = "<group>"; };
500666D02F04786900328939 /* SecretiveUpdater.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretiveUpdater.entitlements; sourceTree = "<group>"; };
500666D12F04787200328939 /* SecretAgentInputParser.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretAgentInputParser.entitlements; sourceTree = "<group>"; };
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Packages/Resources/Localizable.xcstrings; sourceTree = SOURCE_ROOT; }; 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Packages/Resources/Localizable.xcstrings; sourceTree = SOURCE_ROOT; };
50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; }; 50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; };
50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = "<group>"; }; 50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = "<group>"; };
@@ -208,7 +196,6 @@
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxBackgroundStyle.swift; sourceTree = "<group>"; }; 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxBackgroundStyle.swift; sourceTree = "<group>"; };
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; }; 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; };
5059933F2E7A3B5B0092CFFA /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; sourceTree = "<group>"; }; 5059933F2E7A3B5B0092CFFA /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; sourceTree = "<group>"; };
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; }; 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>"; }; 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>"; }; 50617D8423FCE48E0099B055 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@@ -254,13 +241,7 @@
50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowBackgroundStyle.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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; }; F418C9A82F0C57F000E9ADF8 /* OpenSource.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = OpenSource.xcconfig; sourceTree = "<group>"; };
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 */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -271,7 +252,6 @@
50E0145C2EDB9CDF00B121F1 /* Common in Frameworks */, 50E0145C2EDB9CDF00B121F1 /* Common in Frameworks */,
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */, 5003EF3B278005E800DF2006 /* SecretKit in Frameworks */,
501421622781262300BBAA70 /* Brief in Frameworks */, 501421622781262300BBAA70 /* Brief in Frameworks */,
505993532E7E70C90092CFFA /* CertificateKit in Frameworks */,
5003EF5F2780081600DF2006 /* SecureEnclaveSecretKit in Frameworks */, 5003EF5F2780081600DF2006 /* SecureEnclaveSecretKit in Frameworks */,
5003EF612780081600DF2006 /* SmartCardSecretKit in Frameworks */, 5003EF612780081600DF2006 /* SmartCardSecretKit in Frameworks */,
); );
@@ -290,8 +270,8 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
50E4C4F32E77CAC600C73783 /* XPCWrappers in Frameworks */, 5002C3AB2EEF483300FFAD22 /* XPCWrappers in Frameworks */,
50E4C4F12E77C90300C73783 /* SSHProtocolKit in Frameworks */, 50692E6C2E6FFA510043C7BB /* SecretAgentKit in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -308,15 +288,6 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
50E4C4CB2E77C4B300C73783 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
50E4C4EF2E77C8FC00C73783 /* SSHProtocolKit in Frameworks */,
50E4C4ED2E77C55E00C73783 /* XPCWrappers in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
@@ -381,7 +352,6 @@
508A58AF241E144C0069DC07 /* Config */, 508A58AF241E144C0069DC07 /* Config */,
50692D272E6FDB8D0043C7BB /* SecretiveUpdater */, 50692D272E6FDB8D0043C7BB /* SecretiveUpdater */,
50692E662E6FF9E20043C7BB /* SecretAgentInputParser */, 50692E662E6FF9E20043C7BB /* SecretAgentInputParser */,
50E4C4E72E77C53000C73783 /* SecretiveCertificateParser */,
50617D8023FCE48E0099B055 /* Products */, 50617D8023FCE48E0099B055 /* Products */,
5099A08B240243730062B6F2 /* Frameworks */, 5099A08B240243730062B6F2 /* Frameworks */,
); );
@@ -394,7 +364,6 @@
50A3B78A24026B7500D209EA /* SecretAgent.app */, 50A3B78A24026B7500D209EA /* SecretAgent.app */,
50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */, 50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */,
50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */, 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */,
50E4C4CE2E77C4B300C73783 /* SecretiveCertificateParser.xpc */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -429,7 +398,7 @@
50692D272E6FDB8D0043C7BB /* SecretiveUpdater */ = { 50692D272E6FDB8D0043C7BB /* SecretiveUpdater */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
50E4C4E12E77C4FA00C73783 /* SecretiveUpdater.entitlements */, 500666D02F04786900328939 /* SecretiveUpdater.entitlements */,
50692D232E6FDB8D0043C7BB /* Info.plist */, 50692D232E6FDB8D0043C7BB /* Info.plist */,
50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */, 50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */,
50692D242E6FDB8D0043C7BB /* main.swift */, 50692D242E6FDB8D0043C7BB /* main.swift */,
@@ -441,7 +410,7 @@
50692E662E6FF9E20043C7BB /* SecretAgentInputParser */ = { 50692E662E6FF9E20043C7BB /* SecretAgentInputParser */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
50E4C4E02E77C4EE00C73783 /* SecretAgentInputParser.entitlements */, 500666D12F04787200328939 /* SecretAgentInputParser.entitlements */,
50692E622E6FF9E20043C7BB /* Info.plist */, 50692E622E6FF9E20043C7BB /* Info.plist */,
50692E632E6FF9E20043C7BB /* main.swift */, 50692E632E6FF9E20043C7BB /* main.swift */,
50692E642E6FF9E20043C7BB /* SecretAgentInputParser.swift */, 50692E642E6FF9E20043C7BB /* SecretAgentInputParser.swift */,
@@ -454,6 +423,7 @@
children = ( children = (
508A590F241EEF6D0069DC07 /* Secretive.xctestplan */, 508A590F241EEF6D0069DC07 /* Secretive.xctestplan */,
508A58AB241E121B0069DC07 /* Config.xcconfig */, 508A58AB241E121B0069DC07 /* Config.xcconfig */,
F418C9A82F0C57F000E9ADF8 /* OpenSource.xcconfig */,
); );
path = Config; path = Config;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -475,7 +445,6 @@
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */, 508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */,
5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */, 5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */,
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */, 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */,
505993502E7E59F70092CFFA /* XPCCertificateParser.swift */,
); );
path = Controllers; path = Controllers;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -510,17 +479,6 @@
path = "Preview Content"; path = "Preview Content";
sourceTree = "<group>"; 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 */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@@ -541,7 +499,6 @@
50142167278126B500BBAA70 /* PBXTargetDependency */, 50142167278126B500BBAA70 /* PBXTargetDependency */,
50692D1C2E6FDB880043C7BB /* PBXTargetDependency */, 50692D1C2E6FDB880043C7BB /* PBXTargetDependency */,
50692E5A2E6FF9D20043C7BB /* PBXTargetDependency */, 50692E5A2E6FF9D20043C7BB /* PBXTargetDependency */,
50E4C4D82E77C4B300C73783 /* PBXTargetDependency */,
); );
name = Secretive; name = Secretive;
packageProductDependencies = ( packageProductDependencies = (
@@ -549,7 +506,6 @@
5003EF5E2780081600DF2006 /* SecureEnclaveSecretKit */, 5003EF5E2780081600DF2006 /* SecureEnclaveSecretKit */,
5003EF602780081600DF2006 /* SmartCardSecretKit */, 5003EF602780081600DF2006 /* SmartCardSecretKit */,
501421612781262300BBAA70 /* Brief */, 501421612781262300BBAA70 /* Brief */,
505993522E7E70C90092CFFA /* CertificateKit */,
50E0145B2EDB9CDF00B121F1 /* Common */, 50E0145B2EDB9CDF00B121F1 /* Common */,
); );
productName = Secretive; productName = Secretive;
@@ -591,8 +547,8 @@
); );
name = SecretAgentInputParser; name = SecretAgentInputParser;
packageProductDependencies = ( packageProductDependencies = (
50E4C4F02E77C90300C73783 /* SSHProtocolKit */, 50692E6B2E6FFA510043C7BB /* SecretAgentKit */,
50E4C4F22E77CAC600C73783 /* XPCWrappers */, 5002C3AA2EEF483300FFAD22 /* XPCWrappers */,
); );
productName = SecretAgentInputParser; productName = SecretAgentInputParser;
productReference = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */; productReference = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */;
@@ -628,27 +584,6 @@
productReference = 50A3B78A24026B7500D209EA /* SecretAgent.app */; productReference = 50A3B78A24026B7500D209EA /* SecretAgent.app */;
productType = "com.apple.product-type.application"; 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 */ /* End PBXNativeTarget section */
/* Begin PBXProject section */ /* Begin PBXProject section */
@@ -657,7 +592,7 @@
attributes = { attributes = {
BuildIndependentTargetsInParallel = YES; BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 2600; LastSwiftUpdateCheck = 2600;
LastUpgradeCheck = 2600; LastUpgradeCheck = 2640;
ORGANIZATIONNAME = "Max Goedjen"; ORGANIZATIONNAME = "Max Goedjen";
TargetAttributes = { TargetAttributes = {
50617D7E23FCE48D0099B055 = { 50617D7E23FCE48D0099B055 = {
@@ -672,9 +607,6 @@
50A3B78924026B7500D209EA = { 50A3B78924026B7500D209EA = {
CreatedOnToolsVersion = 11.4; CreatedOnToolsVersion = 11.4;
}; };
50E4C4CD2E77C4B300C73783 = {
CreatedOnToolsVersion = 26.0;
};
}; };
}; };
buildConfigurationList = 50617D7A23FCE48D0099B055 /* Build configuration list for PBXProject "Secretive" */; buildConfigurationList = 50617D7A23FCE48D0099B055 /* Build configuration list for PBXProject "Secretive" */;
@@ -702,7 +634,6 @@
50A3B78924026B7500D209EA /* SecretAgent */, 50A3B78924026B7500D209EA /* SecretAgent */,
50692D112E6FDB880043C7BB /* SecretiveUpdater */, 50692D112E6FDB880043C7BB /* SecretiveUpdater */,
50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */, 50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */,
50E4C4CD2E77C4B300C73783 /* SecretiveCertificateParser */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@@ -723,6 +654,7 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
50B832C02F62202A00D2FCB8 /* InternetAccessPolicy.plist in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -745,13 +677,6 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
50E4C4CC2E77C4B300C73783 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
@@ -771,7 +696,6 @@
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */, 50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */, 504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */,
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */, 50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */,
505993512E7E59FB0092CFFA /* XPCCertificateParser.swift in Sources */,
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */, 50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */,
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */, 5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */,
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */, 50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */,
@@ -822,15 +746,6 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
50E4C4CA2E77C4B300C73783 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
50E4C4E82E77C53000C73783 /* main.swift in Sources */,
50E4C4E92E77C53000C73783 /* SecretiveCertificateParser.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */ /* Begin PBXTargetDependency section */
@@ -863,11 +778,6 @@
target = 50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */; target = 50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */;
targetProxy = 50692E712E6FFA6E0043C7BB /* PBXContainerItemProxy */; targetProxy = 50692E712E6FFA6E0043C7BB /* PBXContainerItemProxy */;
}; };
50E4C4D82E77C4B300C73783 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 50E4C4CD2E77C4B300C73783 /* SecretiveCertificateParser */;
targetProxy = 50E4C4D72E77C4B300C73783 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */ /* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
@@ -1038,7 +948,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
DEVELOPMENT_TEAM = Z72PRUAWF6; DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES; ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -1061,7 +971,7 @@
); );
MACOSX_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1; MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host; PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).Host";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
}; };
@@ -1078,7 +988,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
DEVELOPMENT_TEAM = Z72PRUAWF6; DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES; ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -1101,7 +1011,7 @@
); );
MACOSX_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1; MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host; PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).Host";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Host"; PROVISIONING_PROFILE_SPECIFIER = "Secretive - Host";
}; };
@@ -1117,7 +1027,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = Z72PRUAWF6; DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES; ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -1140,7 +1050,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater; PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretiveUpdater";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
@@ -1153,17 +1063,14 @@
}; };
name = Debug; name = Debug;
}; };
50692D222E6FDB880043C7BB /* Release */ = { 50692D212E6FDB880043C7BB /* Test */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = SecretiveUpdater/SecretiveUpdater.entitlements; CODE_SIGN_ENTITLEMENTS = SecretiveUpdater/SecretiveUpdater.entitlements;
CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES; ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -1186,7 +1093,52 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater; PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).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;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = "$(SECRETIVE_DEVELOPMENT_TEAM)";
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;
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 = "$(SECRETIVE_BASE_BUNDLE_ID).SecretiveUpdater";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
@@ -1208,7 +1160,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = Z72PRUAWF6; DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES; ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -1221,7 +1173,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser; PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentInputParser";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
@@ -1234,17 +1186,14 @@
}; };
name = Debug; name = Debug;
}; };
50692E602E6FF9D20043C7BB /* Release */ = { 50692E5F2E6FF9D20043C7BB /* Test */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = SecretAgentInputParser/SecretAgentInputParser.entitlements; CODE_SIGN_ENTITLEMENTS = SecretAgentInputParser/SecretAgentInputParser.entitlements;
CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES; ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -1257,7 +1206,42 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser; PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).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;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = "$(SECRETIVE_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 = 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 = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentInputParser";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
@@ -1270,16 +1254,128 @@
}; };
name = Release; name = Release;
}; };
50A3B79B24026B7600D209EA /* Debug */ = { 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 = "$(SECRETIVE_BASE_BUNDLE_ID).Host";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
};
508A5917241EF1A00069DC07 /* Test */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements; CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
DEVELOPMENT_TEAM = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES; ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -1302,7 +1398,44 @@
); );
MACOSX_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1; MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent; PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
};
50A3B79B24026B7600D209EA /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
ENABLE_APP_SANDBOX = YES;
ENABLE_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;
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 = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
}; };
name = Debug; name = Debug;
@@ -1317,7 +1450,7 @@
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
DEVELOPMENT_TEAM = Z72PRUAWF6; DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES; ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -1340,82 +1473,12 @@
); );
MACOSX_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1; MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent; PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent"; PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent";
}; };
name = Release; 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 */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
@@ -1423,6 +1486,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
50617D9B23FCE48E0099B055 /* Debug */, 50617D9B23FCE48E0099B055 /* Debug */,
508A5914241EF1A00069DC07 /* Test */,
50617D9C23FCE48E0099B055 /* Release */, 50617D9C23FCE48E0099B055 /* Release */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
@@ -1432,6 +1496,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
50617D9E23FCE48E0099B055 /* Debug */, 50617D9E23FCE48E0099B055 /* Debug */,
508A5915241EF1A00069DC07 /* Test */,
50617D9F23FCE48E0099B055 /* Release */, 50617D9F23FCE48E0099B055 /* Release */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
@@ -1441,6 +1506,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
50692D202E6FDB880043C7BB /* Debug */, 50692D202E6FDB880043C7BB /* Debug */,
50692D212E6FDB880043C7BB /* Test */,
50692D222E6FDB880043C7BB /* Release */, 50692D222E6FDB880043C7BB /* Release */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
@@ -1450,6 +1516,7 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
50692E5E2E6FF9D20043C7BB /* Debug */, 50692E5E2E6FF9D20043C7BB /* Debug */,
50692E5F2E6FF9D20043C7BB /* Test */,
50692E602E6FF9D20043C7BB /* Release */, 50692E602E6FF9D20043C7BB /* Release */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
@@ -1459,23 +1526,19 @@
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
50A3B79B24026B7600D209EA /* Debug */, 50A3B79B24026B7600D209EA /* Debug */,
508A5917241EF1A00069DC07 /* Test */,
50A3B79C24026B7600D209EA /* Release */, 50A3B79C24026B7600D209EA /* Release */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
50E4C4DB2E77C4B300C73783 /* Build configuration list for PBXNativeTarget "SecretiveCertificateParser" */ = {
isa = XCConfigurationList;
buildConfigurations = (
50E4C4DC2E77C4B300C73783 /* Debug */,
50E4C4DE2E77C4B300C73783 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
5002C3AA2EEF483300FFAD22 /* XPCWrappers */ = {
isa = XCSwiftPackageProductDependency;
productName = XPCWrappers;
};
5003EF3A278005E800DF2006 /* SecretKit */ = { 5003EF3A278005E800DF2006 /* SecretKit */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = SecretKit; productName = SecretKit;
@@ -1512,10 +1575,6 @@
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = Brief; productName = Brief;
}; };
505993522E7E70C90092CFFA /* CertificateKit */ = {
isa = XCSwiftPackageProductDependency;
productName = CertificateKit;
};
50692D2C2E6FDC000043C7BB /* XPCWrappers */ = { 50692D2C2E6FDC000043C7BB /* XPCWrappers */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = XPCWrappers; productName = XPCWrappers;
@@ -1524,6 +1583,10 @@
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = Brief; productName = Brief;
}; };
50692E6B2E6FFA510043C7BB /* SecretAgentKit */ = {
isa = XCSwiftPackageProductDependency;
productName = SecretAgentKit;
};
50E0145B2EDB9CDF00B121F1 /* Common */ = { 50E0145B2EDB9CDF00B121F1 /* Common */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = Common; productName = Common;
@@ -1532,22 +1595,6 @@
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = Common; 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 */ /* End XCSwiftPackageProductDependency section */
}; };
rootObject = 50617D7723FCE48D0099B055 /* Project object */; rootObject = 50617D7723FCE48D0099B055 /* Project object */;

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "2600" LastUpgradeVersion = "2640"
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -14,7 +14,8 @@
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans> <TestPlans>
<TestPlanReference <TestPlanReference
reference = "container:Config/Secretive.xctestplan"> reference = "container:Config/Secretive.xctestplan"
default = "YES">
</TestPlanReference> </TestPlanReference>
</TestPlans> </TestPlans>
</TestAction> </TestAction>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "2600" LastUpgradeVersion = "2640"
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "2600" LastUpgradeVersion = "2640"
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -23,7 +23,7 @@
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction
buildConfiguration = "Debug" buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">

View File

@@ -3,7 +3,6 @@ import SecretKit
import SecureEnclaveSecretKit import SecureEnclaveSecretKit
import SmartCardSecretKit import SmartCardSecretKit
import Brief import Brief
import CertificateKit
@main @main
struct Secretive: App { struct Secretive: App {
@@ -15,7 +14,6 @@ struct Secretive: App {
WindowGroup { WindowGroup {
ContentView() ContentView()
.environment(EnvironmentValues._secretStoreList) .environment(EnvironmentValues._secretStoreList)
.environment(EnvironmentValues._certificateStore)
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
Task { Task {
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false @AppStorage("defaultsHasRunSetup") var hasRunSetup = false
@@ -94,18 +92,15 @@ extension EnvironmentValues {
@MainActor fileprivate static let _secretStoreList: SecretStoreList = { @MainActor fileprivate static let _secretStoreList: SecretStoreList = {
let list = SecretStoreList() let list = SecretStoreList()
let cryptoKit = SecureEnclave.Store() let cryptoKit = SecureEnclave.Store()
let cryptoKitMigrator = SecureEnclave.CryptoKitMigrator() let migrator = SecureEnclave.CryptoKitMigrator()
try? cryptoKitMigrator.migrate(to: cryptoKit) try? migrator.migrate(to: cryptoKit)
list.add(store: cryptoKit) list.add(store: cryptoKit)
list.add(store: SmartCard.Store()) list.add(store: SmartCard.Store())
return list return list
}() }()
@MainActor fileprivate static let _certificateStore: CertificateStore = CertificateStore()
private static let _agentLaunchController = AgentLaunchController() private static let _agentLaunchController = AgentLaunchController()
@Entry var agentLaunchController: any AgentLaunchControllerProtocol = _agentLaunchController @Entry var agentLaunchController: any AgentLaunchControllerProtocol = _agentLaunchController
private static let _updater: any UpdaterProtocol = { private static let _updater: any UpdaterProtocol = {
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false @AppStorage("defaultsHasRunSetup") var hasRunSetup = false
return Updater(checkOnLaunch: hasRunSetup) return Updater(checkOnLaunch: hasRunSetup)
@@ -118,10 +113,6 @@ extension EnvironmentValues {
@MainActor var secretStoreList: SecretStoreList { @MainActor var secretStoreList: SecretStoreList {
EnvironmentValues._secretStoreList EnvironmentValues._secretStoreList
} }
@MainActor var certificateStore: CertificateStore {
EnvironmentValues._certificateStore
}
} }
extension FocusedValues { extension FocusedValues {

View File

@@ -1,29 +0,0 @@
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

@@ -4,16 +4,22 @@
<dict> <dict>
<key>com.apple.security.hardened-process</key> <key>com.apple.security.hardened-process</key>
<true/> <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> <key>com.apple.security.hardened-process.dyld-ro</key>
<true/> <true/>
<key>com.apple.security.hardened-process.enhanced-security-version</key>
<integer>1</integer>
<key>com.apple.security.hardened-process.hardened-heap</key> <key>com.apple.security.hardened-process.hardened-heap</key>
<true/> <true/>
<key>com.apple.security.hardened-process.platform-restrictions</key> <key>com.apple.security.hardened-process.enhanced-security-version-string</key>
<integer>2</integer> <string>1</string>
<key>com.apple.security.smartcard</key> <key>com.apple.security.smartcard</key>
<true/> <true/>
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
<string>2</string>
<key>keychain-access-groups</key> <key>keychain-access-groups</key>
<array> <array>
<string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string> <string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string>

View File

@@ -1,6 +1,7 @@
import SwiftUI import SwiftUI
import SecretKit import SecretKit
import SSHProtocolKit import SSHProtocolKit
import Common
struct ToolConfigurationView: View { struct ToolConfigurationView: View {
@@ -112,10 +113,9 @@ struct ToolConfigurationView: View {
let writer = OpenSSHPublicKeyWriter() let writer = OpenSSHPublicKeyWriter()
let gitAllowedSignersString = [email.isEmpty ? String(localized: .integrationsConfigureUsingEmailPlaceholder) : email, writer.openSSHString(secret: selectedSecret)] let gitAllowedSignersString = [email.isEmpty ? String(localized: .integrationsConfigureUsingEmailPlaceholder) : email, writer.openSSHString(secret: selectedSecret)]
.joined(separator: " ") .joined(separator: " ")
let fileController = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL)
return text return text
.replacingOccurrences(of: Instructions.Constants.publicKeyPlaceholder, with: gitAllowedSignersString) .replacingOccurrences(of: Instructions.Constants.publicKeyPlaceholder, with: gitAllowedSignersString)
.replacingOccurrences(of: Instructions.Constants.publicKeyPathPlaceholder, with: fileController.publicKeyPath(for: selectedSecret)) .replacingOccurrences(of: Instructions.Constants.publicKeyPathPlaceholder, with: URL.publicKeyPath(for: selectedSecret, in: URL.publicKeyDirectory))
} }
} }

View File

@@ -1,5 +1,6 @@
import SwiftUI import SwiftUI
import SecretKit import SecretKit
import Common
import SSHProtocolKit import SSHProtocolKit
struct SecretDetailView<SecretType: Secret>: View { struct SecretDetailView<SecretType: Secret>: View {
@@ -7,7 +8,6 @@ struct SecretDetailView<SecretType: Secret>: View {
let secret: SecretType let secret: SecretType
private let keyWriter = OpenSSHPublicKeyWriter() private let keyWriter = OpenSSHPublicKeyWriter()
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL)
var body: some View { var body: some View {
ScrollView { ScrollView {
@@ -22,7 +22,7 @@ struct SecretDetailView<SecretType: Secret>: View {
CopyableView(title: .secretDetailPublicKeyLabel, image: Image(systemName: "key"), text: keyString) CopyableView(title: .secretDetailPublicKeyLabel, image: Image(systemName: "key"), text: keyString)
Spacer() Spacer()
.frame(height: 20) .frame(height: 20)
CopyableView(title: .secretDetailPublicKeyPathLabel, image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.publicKeyPath(for: secret), showRevealInFinder: true) CopyableView(title: .secretDetailPublicKeyPathLabel, image: Image(systemName: "lock.doc"), text: URL.publicKeyPath(for: secret, in: URL.publicKeyDirectory), showRevealInFinder: true)
Spacer() Spacer()
} }
} }

View File

@@ -3,7 +3,6 @@ import SecretKit
import SecureEnclaveSecretKit import SecureEnclaveSecretKit
import SmartCardSecretKit import SmartCardSecretKit
import Brief import Brief
import SSHProtocolKit
struct ContentView: View { struct ContentView: View {
@@ -14,7 +13,6 @@ struct ContentView: View {
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
@Environment(\.openWindow) private var openWindow @Environment(\.openWindow) private var openWindow
@Environment(\.secretStoreList) private var storeList @Environment(\.secretStoreList) private var storeList
@Environment(\.certificateStore) private var certificateStore
@Environment(\.updater) private var updater @Environment(\.updater) private var updater
@Environment(\.agentLaunchController) private var agentLaunchController @Environment(\.agentLaunchController) private var agentLaunchController
@@ -44,22 +42,6 @@ struct ContentView: View {
runningSetup = true 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) { .focusedSceneValue(\.showCreateSecret, .init(isEnabled: !runningSetup) {
showingCreation = true showingCreation = true
}) })

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>XPCService</key>
<dict>
<key>ServiceType</key>
<string>Application</string>
</dict>
</dict>
</plist>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>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,17 +0,0 @@
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

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

View File

@@ -12,11 +12,11 @@
<true/> <true/>
<key>com.apple.security.hardened-process.dyld-ro</key> <key>com.apple.security.hardened-process.dyld-ro</key>
<true/> <true/>
<key>com.apple.security.hardened-process.enhanced-security-version</key> <key>com.apple.security.hardened-process.enhanced-security-version-string</key>
<integer>1</integer> <string>1</string>
<key>com.apple.security.hardened-process.hardened-heap</key> <key>com.apple.security.hardened-process.hardened-heap</key>
<true/> <true/>
<key>com.apple.security.hardened-process.platform-restrictions</key> <key>com.apple.security.hardened-process.platform-restrictions-string</key>
<integer>2</integer> <string>2</string>
</dict> </dict>
</plist> </plist>

56
configure_team_id.sh Executable file
View File

@@ -0,0 +1,56 @@
#!/bin/bash
TEAM_ID_FILE=Sources/Config/OpenSource.xcconfig
function print_team_ids() {
echo ""
echo "FYI, here are the team IDs found in your Xcode preferences:"
echo ""
XCODEPREFS="$HOME/Library/Preferences/com.apple.dt.Xcode.plist"
TEAM_KEYS=(`/usr/libexec/PlistBuddy -c "Print :IDEProvisioningTeams" "$XCODEPREFS" | perl -lne 'print $1 if /^ (\S*) =/'`)
for KEY in $TEAM_KEYS
do
i=0
while true ; do
NAME=$(/usr/libexec/PlistBuddy -c "Print :IDEProvisioningTeams:$KEY:$i:teamName" "$XCODEPREFS" 2>/dev/null)
TEAMID=$(/usr/libexec/PlistBuddy -c "Print :IDEProvisioningTeams:$KEY:$i:teamID" "$XCODEPREFS" 2>/dev/null)
if [ $? -ne 0 ]; then
break
fi
echo "$TEAMID - $NAME"
i=$(($i + 1))
done
done
}
if [ -z "$1" ]; then
print_team_ids
echo ""
echo "> What is your Apple Developer Team ID? (looks like 1A23BDCD)"
read TEAM_ID
else
TEAM_ID=$1
fi
if [ -z "$TEAM_ID" ]; then
echo "You must enter a team id"
print_team_ids
exit 1
fi
echo "Setting team ID to $TEAM_ID"
echo "// This file was automatically generated, do not edit directly." > $TEAM_ID_FILE
echo "" >> $TEAM_ID_FILE
echo "SECRETIVE_BASE_BUNDLE_ID_OSS=${TEAM_ID}.com.example.Secretive" >> $TEAM_ID_FILE
echo "SECRETIVE_DEVELOPMENT_TEAM_OSS=${TEAM_ID}" >> $TEAM_ID_FILE
echo ""
echo "Successfully generated configuration at $TEAM_ID_FILE, you may now build the app using the \"Secretive\" target"
echo "You may need to close and re-open the project in Xcode if it's already open"
echo ""