Compare commits

..

1 Commits

Author SHA1 Message Date
Max Goedjen
7e11458fc3 Update SecretListItemView.swift 2025-12-13 19:51:00 -08:00
73 changed files with 450 additions and 2410 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

4
.gitignore vendored
View File

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

View File

@@ -10,10 +10,6 @@ Security is obviously paramount for a project like Secretive. As such, any contr
Secretive is designed to be easily auditable by people who are considering using it. In keeping with this, Secretive has no third party dependencies, and any contributions which bring in new dependencies will be rejected.
### AI/LLM Policy
For security and auditing reasons similar to the policy Secretive has on dependencies, any code generated with AI or LLM tools will not be accepted.
## Code of Conduct
All contributors must abide by the [Code of Conduct](CODE_OF_CONDUCT.md)

View File

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

View File

@@ -1,8 +1,3 @@
CI_VERSION = GITHUB_CI_VERSION
CI_BUILD_NUMBER = GITHUB_BUILD_NUMBER
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,30 +19,18 @@ let package = Package(
.library(
name: "SmartCardSecretKit",
targets: ["SmartCardSecretKit"]),
.library(
name: "CertificateKit",
targets: ["CertificateKit"]),
.library(
name: "SecretAgentKit",
targets: ["SecretAgentKit"]),
.library(
name: "Formatters",
targets: ["Formatters"]),
targets: ["SecretAgentKit", "XPCWrappers"]),
.library(
name: "Common",
targets: ["Common"]),
.library(
name: "SharedXPCServices",
targets: ["SharedXPCServices"]),
.library(
name: "Brief",
targets: ["Brief"]),
.library(
name: "XPCWrappers",
targets: ["XPCWrappers"]),
.library(
name: "SSHProtocolKit",
targets: ["SSHProtocolKit"]),
],
dependencies: [
],
@@ -55,7 +43,7 @@ let package = Package(
),
.testTarget(
name: "SecretKitTests",
dependencies: ["SecretKit", "SecretAgentKit", "SecureEnclaveSecretKit", "SmartCardSecretKit"],
dependencies: ["SecretKit", "SecureEnclaveSecretKit", "SmartCardSecretKit"],
swiftSettings: swiftSettings,
),
.target(
@@ -70,15 +58,9 @@ let package = Package(
resources: [localization],
swiftSettings: swiftSettings,
),
.target(
name: "CertificateKit",
dependencies: ["SecretKit", "Formatters"],
resources: [localization],
swiftSettings: swiftSettings,
),
.target(
name: "SecretAgentKit",
dependencies: ["SecretKit", "SSHProtocolKit", "CertificateKit", "Common", "Formatters"],
dependencies: ["SecretKit"],
resources: [localization],
swiftSettings: swiftSettings,
),
@@ -87,37 +69,14 @@ let package = Package(
dependencies: ["SecretAgentKit"],
),
.target(
name: "SSHProtocolKit",
dependencies: ["SecretKit", "CertificateKit"],
resources: [localization],
swiftSettings: swiftSettings,
),
.testTarget(
name: "SSHProtocolKitTests",
dependencies: ["SSHProtocolKit"],
swiftSettings: swiftSettings,
),
.target(
name: "Formatters",
name: "Common",
dependencies: [],
resources: [localization],
swiftSettings: swiftSettings,
),
.target(
name: "Common",
dependencies: ["SSHProtocolKit", "SecretKit"],
resources: [localization],
swiftSettings: swiftSettings,
),
.target(
name: "SharedXPCServices",
dependencies: ["CertificateKit", "SSHProtocolKit"],
resources: [localization],
swiftSettings: swiftSettings,
),
.target(
name: "Brief",
dependencies: ["XPCWrappers", "SSHProtocolKit"],
dependencies: ["XPCWrappers"],
resources: [localization],
swiftSettings: swiftSettings,
),

View File

@@ -5547,130 +5547,6 @@
}
}
}
},
"certificate_detail_critical_options_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Critical Options"
}
}
}
},
"certificate_detail_extensions_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Extensions"
}
}
}
},
"certificate_detail_key_id_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Key ID"
}
}
}
},
"certificate_detail_path_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Certificate Path"
}
}
}
},
"certificate_detail_principals_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Principals"
}
}
}
},
"certificate_detail_serial_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Serial Number"
}
}
}
},
"certificate_detail_sha256_public_key_fingerprint_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Public Key Fingerprint"
}
}
}
},
"certificate_detail_sha256_signing_key_fingerprint_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Signing CA Fingerprint"
}
}
}
},
"certificate_detail_valid_after_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Valid After"
}
}
}
},
"certificate_detail_valid_until_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Valid Until"
}
}
}
},
"certificate_detail_validity_range_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Validity Range"
}
}
}
},
"Certificates" : {
},
"copyable_click_to_copy_button" : {
"extractionState" : "manual",
@@ -10118,181 +9994,181 @@
"af" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"ar" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"ca" : {
"stringUnit" : {
"state" : "translated",
"value" : "Esborrar %1$(name)@?"
"value" : "Esborrar %1$(secretName)@?"
}
},
"cs" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"da" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "%1$(name)@ Löschen?"
"value" : "%1$(secretName)@ Löschen?"
}
},
"el" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Delete %1$(name)@?"
"state" : "new",
"value" : "Delete %1$(secretName)@?"
}
},
"es" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Poista %1$(name)@?"
"value" : "Poista %1$(secretName)@?"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Supprimer %1$(name)@?"
"value" : "Supprimer %1$(secretName)@?"
}
},
"he" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"hu" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Eliminare %1$(name)@?"
"value" : "Eliminare %1$(secretName)@?"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "%1$(name)@を削除しますか?"
"value" : "%1$(secretName)@を削除しますか?"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "%1$(name)@를 지우겠습니까?"
"value" : "%1$(secretName)@를 지우겠습니까?"
}
},
"nb" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"nl" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Usunąć %1$(name)@?"
"value" : "Usunąć %1$(secretName)@?"
}
},
"pt" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Deletar %1$(name)@?"
"value" : "Deletar %1$(secretName)@?"
}
},
"ro" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Удалить %1$(name)@?"
"value" : "Удалить %1$(secretName)@?"
}
},
"sr" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"sv" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"tr" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"uk" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"vi" : {
"stringUnit" : {
"state" : "new",
"value" : "Delete %1$(name)@?"
"value" : "Delete %1$(secretName)@?"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "删除“%1$(name)@”吗?"
"value" : "删除“%1$(secretName)@”吗?"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "刪除「%1$(name)@」嗎?"
"value" : "刪除「%1$(secretName)@」嗎?"
}
}
}
@@ -19761,28 +19637,6 @@
}
}
},
"rename_certificate_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Name"
}
}
}
},
"rename_certificate_name_placeholder" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Certificate Name"
}
}
}
},
"reveal_in_finder_button" : {
"extractionState" : "manual",
"localizations" : {
@@ -19968,17 +19822,6 @@
}
}
},
"secret_detail_certificate_path_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Matching Certificates"
}
}
}
},
"secret_detail_md5_fingerprint_label" : {
"extractionState" : "manual",
"localizations" : {

View File

@@ -1,24 +0,0 @@
import Foundation
import CryptoKit
import Formatters
@dynamicMemberLookup
public struct Certificate: Sendable, Codable, Equatable, Hashable, Identifiable, CustomDebugStringConvertible {
public var openSSHCertificate: OpenSSHCertificate
public let rawData: Data
public init(openSSHCertificate: OpenSSHCertificate, rawData: Data) {
self.openSSHCertificate = openSSHCertificate
self.rawData = rawData
}
public var id: String { Insecure.MD5.hash(data: rawData).formatted(.hex(separator: "")) }
public var debugDescription: String { openSSHCertificate.debugDescription }
public subscript<T>(dynamicMember keyPath: KeyPath<OpenSSHCertificate, T>) -> T {
openSSHCertificate[keyPath: keyPath]
}
}

View File

@@ -1,153 +0,0 @@
import Foundation
import Observation
import Security
import os
import SecretKit
@Observable @MainActor public final class CertificateStore: Sendable {
public private(set) var certificates: [Certificate] = []
/// Initializes a Store.
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()
}
}
}
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)
}
}
public func save(certificate: Certificate) throws {
let attributes = try JSONEncoder().encode(certificate.openSSHCertificate)
let keychainAttributes = KeychainDictionary([
kSecClass: Constants.keyClass,
kSecAttrService: Constants.keyTag,
kSecAttrAccount: certificate.id,
kSecUseDataProtectionKeychain: true,
kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
kSecValueData: certificate.rawData,
kSecAttrGeneric: attributes
])
let status = SecItemAdd(keychainAttributes, nil)
if status != errSecSuccess && status != errSecDuplicateItem {
throw KeychainError(statusCode: status)
}
reloadCertificates()
}
public func delete(certificate: Certificate) throws {
let deleteAttributes = KeychainDictionary([
kSecClass: Constants.keyClass,
kSecAttrService: Constants.keyTag,
kSecUseDataProtectionKeychain: true,
kSecAttrAccount: certificate.id,
])
let status = SecItemDelete(deleteAttributes)
if status != errSecSuccess {
throw KeychainError(statusCode: status)
}
reloadCertificates()
}
public func update(certificate: Certificate) throws {
let updateQuery = KeychainDictionary([
kSecClass: Constants.keyClass,
kSecAttrAccount: certificate.id,
])
let cert = try JSONEncoder().encode(certificate.openSSHCertificate)
let updatedAttributes = KeychainDictionary([
kSecAttrGeneric: cert,
])
let status = SecItemUpdate(updateQuery, updatedAttributes)
if status != errSecSuccess {
throw KeychainError(statusCode: status)
}
reloadCertificates()
}
public func certificates(for secret: any Secret) -> [Certificate] {
certificates.filter { $0.openSSHCertificate.publicKey.data == secret.publicKey }
}
}
extension CertificateStore {
/// Loads all certificates from the store.
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 decoder = JSONDecoder()
let wrapped: [Certificate] = typed.compactMap {
do {
guard let data = $0[kSecValueData] as? Data,
let attributesData = $0[kSecAttrGeneric] as? Data else {
throw MissingAttributesError()
}
return Certificate(openSSHCertificate: try decoder.decode(OpenSSHCertificate.self, from: attributesData), rawData: data)
} catch {
return nil
}
}
.filter {
if let validityRange = $0.validityRange {
validityRange.contains(Date())
} else {
true
}
}
certificates.append(contentsOf: wrapped)
}
}
extension CertificateStore {
enum Constants {
static let keyClass = kSecClassGenericPassword as String
static let keyTag = Data("com.maxgoedjen.certificatestore.opensshcertificate".utf8)
static let notificationToken = UUID().uuidString
}
struct UnsupportedAlgorithmError: Error {}
struct MissingAttributesError: Error {}
}
extension NSNotification.Name {
// Distributed notification that keys were modified out of process (ie, that the management tool added/removed certificates)
public static let certificateStoreUpdated = NSNotification.Name("com.maxgoedjen.Secretive.certificateStore.updated")
// Internal notification that certificates were reloaded from the backing store.
public static let certificateStoreReloaded = NSNotification.Name("com.maxgoedjen.Secretive.certificateStore.reloaded")
}

View File

@@ -1,82 +0,0 @@
import Foundation
import Formatters
public struct OpenSSHCertificate: Sendable, Codable, Equatable, Hashable, CustomDebugStringConvertible {
public var type: CertificateType
public var name: String
public var data: Data
public var publicKey: PublicKey
public var principals: [String]
public var keyID: String
public var serial: UInt64
public var validityRange: Range<Date>?
public var criticalOptions: [String]
public var extensions: [String]
public var signingKey: PublicKey
public init(
type: OpenSSHCertificate.CertificateType,
name: String,
data: Data,
publicKey: PublicKey,
principals: [String],
keyID: String,
serial: UInt64,
validityRange: Range<Date>? = nil,
criticalOptions: [String],
extensions: [String],
signingKey: PublicKey,
) {
self.type = type
self.name = name
self.data = data
self.publicKey = publicKey
self.principals = principals
self.keyID = keyID
self.serial = serial
self.validityRange = validityRange
self.criticalOptions = criticalOptions
self.extensions = extensions
self.signingKey = signingKey
}
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"
public var keyIdentifier: String {
rawValue.replacingOccurrences(of: "-cert-v01@openssh.com", with: "")
}
}
}
extension OpenSSHCertificate {
public struct PublicKey: Hashable, Sendable, Codable {
public let keyType: String
public let curveName: String
public let data: Data
public init(keyType: String, curveName: String, data: Data) {
self.keyType = keyType
self.curveName = curveName
self.data = data
}
}
}

View File

@@ -1,7 +1,4 @@
import Foundation
import SSHProtocolKit
import CertificateKit
import SecretKit
extension URL {
@@ -17,32 +14,6 @@ extension URL {
#endif
}
public static var publicKeyDirectory: URL {
agentHomeURL.appending(component: "PublicKeys")
}
public static var certificatesDirectory: URL {
agentHomeURL.appending(component: "Certificates")
}
/// 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()
}
/// The path for a certificate.
/// - Parameter certificate: The Certificate to return the path for.
/// - Returns: The path to the Certificate.
/// - Warning: This method returning a path does not imply that a certificate has been written to disk already. This method only describes where it will be written to.
public static func certificatePath(for certificateID: String, in directory: URL) -> String {
return directory.appending(component: "\(certificateID)-cert.pub").path()
}
}
extension String {
@@ -56,4 +27,3 @@ extension String {
}
}

View File

@@ -1,74 +0,0 @@
import Foundation
import CryptoKit
public struct HexDataStyle<SequenceType: Sequence>: Hashable, Codable {
let separator: String
public init(separator: String) {
self.separator = separator
}
}
extension HexDataStyle: FormatStyle where SequenceType.Element == UInt8 {
public func format(_ value: SequenceType) -> String {
value
.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }
.joined(separator: separator)
}
}
extension FormatStyle where Self == HexDataStyle<Data> {
public static func hex(separator: String = "") -> HexDataStyle<Data> {
HexDataStyle(separator: separator)
}
}
extension FormatStyle where Self == HexDataStyle<Insecure.MD5Digest> {
public static func hex(separator: String = ":") -> HexDataStyle<Insecure.MD5Digest> {
HexDataStyle(separator: separator)
}
}
public struct Base64DataStyle<SequenceType: Sequence>: Hashable, Codable {
private let stripPadding: Bool
public init(stripPadding: Bool) {
self.stripPadding = stripPadding
}
}
extension Base64DataStyle: FormatStyle where SequenceType.Element == UInt8 {
public func format(_ value: SequenceType) -> String {
let base64 = Data(value).base64EncodedString()
let paddingRange = base64.index(base64.endIndex, offsetBy: -2)..<base64.endIndex
return base64.replacingOccurrences(of: "=", with: "", range: paddingRange)
}
}
extension FormatStyle where Self == Base64DataStyle<Data> {
public static func base64(stripPadding: Bool) -> Base64DataStyle<Data> {
Base64DataStyle(stripPadding: stripPadding)
}
}
extension FormatStyle where Self == Base64DataStyle<SHA256.Digest> {
public static func base64(stripPadding: Bool) -> Base64DataStyle<SHA256.Digest> {
Base64DataStyle(stripPadding: stripPadding)
}
}

View File

@@ -1,30 +0,0 @@
import Foundation
import CryptoKit
import CertificateKit
import Formatters
/// Generates OpenSSH representations of Certificates.
public struct OpenSSHCertificateWriter: Sendable {
/// Initializes the writer.
public init() {
}
/// Generates an OpenSSH data payload identifying the certificate.
/// - Returns: OpenSSH data payload identifying the certificate.
public func data(publicKey: OpenSSHCertificate.PublicKey) -> Data {
// https://datatracker.ietf.org/doc/html/rfc5656#section-3.1
publicKey.keyType.lengthAndData +
publicKey.curveName.lengthAndData +
publicKey.data.lengthAndData
}
/// Generates an OpenSSH SHA256 fingerprint string.
/// - Returns: OpenSSH SHA256 fingerprint string.
public func openSSHSHA256KeyFingerprint(publicKey: OpenSSHCertificate.PublicKey) -> String {
// OpenSSL format seems to strip the padding at the end.
let cleaned = SHA256.hash(data: data(publicKey: publicKey)).formatted(.base64(stripPadding: true))
return "SHA256:\(cleaned)"
}
}

View File

@@ -1,95 +0,0 @@
import Foundation
import CertificateKit
public protocol OpenSSHCertificateParserProtocol {
func parse(data: Data) async throws -> OpenSSHCertificate
}
public struct OpenSSHCertificateParser: OpenSSHCertificateParserProtocol, Sendable {
public init() {
assert(Bundle.main.bundleURL.pathExtension == "xpc" || ProcessInfo.processInfo.processName == "xctest", "Potentially unsafe parsing code should run in an XPC service")
}
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 comment = elements.first
do {
let dataParser = OpenSSHReader(data: decoded)
let publicKeyType = try dataParser.readNextChunkAsString() // Theoretically the same as typeString, but
.replacingOccurrences(of: "-cert-v01@openssh.com", with: "")
_ = try dataParser.readNextChunk() // Nonce
let publicKeyCurveName = try dataParser.readNextChunkAsString()
let publicKeyData = try dataParser.readNextChunk()
let publicKey = OpenSSHCertificate.PublicKey(keyType: publicKeyType, curveName: publicKeyCurveName, data: publicKeyData)
let serialNumber = try dataParser.readNextBytes(as: UInt64.self, convertEndianness: true)
let role = try dataParser.readNextBytes(as: UInt32.self, convertEndianness: true)
_ = role
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))
let criticalOptionsReader = try dataParser.readNextChunkAsSubReader()
var criticalOptions: [String] = []
while !criticalOptionsReader.done {
let next = try criticalOptionsReader.readNextChunkAsString()
if !next.isEmpty {
criticalOptions.append(next)
}
}
let extensionsReader = try dataParser.readNextChunkAsSubReader()
var extensions: [String] = []
while !extensionsReader.done {
let next = try extensionsReader.readNextChunkAsString()
if !next.isEmpty {
extensions.append(next)
}
}
_ = try dataParser.readNextChunk() // reserved
let signingKeyReader = try dataParser.readNextChunkAsSubReader()
let signingKeyType = try signingKeyReader.readNextChunkAsString()
let signingKeyCurveName = try signingKeyReader.readNextChunkAsString()
let signingKeyData = try signingKeyReader.readNextChunk()
let signingKey = OpenSSHCertificate.PublicKey(keyType: signingKeyType, curveName: signingKeyCurveName, data: signingKeyData)
return OpenSSHCertificate(
type: type,
name: comment ?? keyIdentifier,
data: decoded,
publicKey: publicKey,
principals: principals,
keyID: keyIdentifier,
serial: serialNumber,
validityRange: validityRange,
criticalOptions: criticalOptions,
extensions: extensions,
signingKey: signingKey,
)
} catch {
throw .parsingFailed
}
}
}
public enum OpenSSHCertificateError: Error, Codable {
case unsupportedType
case parsingFailed
}

View File

@@ -2,29 +2,29 @@ import Foundation
import CryptoKit
import OSLog
import SecretKit
import CertificateKit
import AppKit
import SSHProtocolKit
/// The `Agent` is an implementation of an SSH agent. It manages coordination and access between a socket, traces requests, notifies witnesses and passes requests to stores.
public final class Agent: Sendable {
private let storeList: SecretStoreList
private let certificateStore: CertificateStore
private let witness: SigningWitness?
private let publicKeyWriter = OpenSSHPublicKeyWriter()
private let signatureWriter = OpenSSHSignatureWriter()
private let certificateHandler = OpenSSHCertificateHandler()
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "Agent")
/// Initializes an agent with a store list and a witness.
/// - Parameters:
/// - storeList: The `SecretStoreList` to make available.
/// - witness: A witness to notify of requests.
public init(storeList: SecretStoreList, certificateStore: CertificateStore, witness: SigningWitness? = nil) {
public init(storeList: SecretStoreList, witness: SigningWitness? = nil) {
logger.debug("Agent is running")
self.storeList = storeList
self.certificateStore = certificateStore
self.witness = witness
Task { @MainActor in
await certificateHandler.reloadCertificates(for: storeList.allSecrets)
}
}
}
@@ -67,6 +67,7 @@ extension Agent {
/// - Returns: An OpenSSH formatted Data payload listing the identities available for signing operations.
func identities() async -> Data {
let secrets = await storeList.allSecrets
await certificateHandler.reloadCertificates(for: secrets)
var count = 0
var keyData = Data()
@@ -75,9 +76,10 @@ extension Agent {
keyData.append(keyBlob.lengthAndData)
keyData.append(publicKeyWriter.comment(secret: secret).lengthAndData)
count += 1
for certificate in await certificateStore.certificates(for: secret) {
keyData.append(certificate.data.lengthAndData)
keyData.append(certificate.name.lengthAndData)
if let (certificateData, name) = try? await certificateHandler.keyBlobAndName(for: secret) {
keyData.append(certificateData.lengthAndData)
keyData.append(name.lengthAndData)
count += 1
}
}
@@ -94,7 +96,7 @@ extension Agent {
/// - Returns: An OpenSSH formatted Data payload containing the signed data response.
func sign(data: Data, keyBlob: Data, provenance: SigningRequestProvenance) async throws -> Data {
guard let (secret, store) = await secret(matching: keyBlob) else {
let keyBlobHex = keyBlob.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)")
throw NoMatchingKeyError()
}

View File

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

View File

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

View File

@@ -1,83 +0,0 @@
import Foundation
import OSLog
import SecretKit
import SSHProtocolKit
import CertificateKit
import Common
/// Controller responsible for writing public keys to disk, so that they're easily accessible by scripts.
public final class PublicKeyFileStoreController: Sendable {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "PublicKeyFileStoreController")
private let publicKeysURL: URL
private let certificatesURL: URL
private let keyWriter = OpenSSHPublicKeyWriter()
/// Initializes a PublicKeyFileStoreController.
public init(publicKeysURL: URL, certificatesURL: URL) {
self.publicKeysURL = publicKeysURL
self.certificatesURL = certificatesURL
}
/// Writes out the keys specified to disk.
/// - Parameter secrets: The Secrets to generate keys for.
/// - Parameter clear: Whether or not any untracked files in the directory should be removed.
public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws {
logger.log("Writing public keys to disk")
if clear {
let validPaths = Set(secrets.map { URL.publicKeyPath(for: $0, in: publicKeysURL) })
.union(Set(secrets.map { legacySSHCertificatePath(for: $0) }))
let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: publicKeysURL.path())) ?? []
let fullPathContents = contentsOfDirectory.map { publicKeysURL.appending(path: $0).path() }
let untracked = Set(fullPathContents)
.subtracting(validPaths)
for path in untracked {
// string instead of fileURLWithPath since we're already using fileURL format.
try? FileManager.default.removeItem(at: URL(string: path)!)
}
}
try? FileManager.default.createDirectory(at: publicKeysURL, withIntermediateDirectories: false, attributes: nil)
for secret in secrets {
let path = URL.publicKeyPath(for: secret, in: publicKeysURL)
let data = Data(keyWriter.openSSHString(secret: secret).utf8)
FileManager.default.createFile(atPath: path, contents: data, attributes: nil)
}
logger.log("Finished writing public keys")
}
/// Writes out the certificates specified to disk.
/// - Parameter certificates: The Secrets to generate keys for.
/// - Parameter clear: Whether or not any untracked files in the directory should be removed.
public func generateCertificates(for certificates: [Certificate], clear: Bool = false) throws {
logger.log("Writing certificates to disk")
if clear {
let validPaths = Set(certificates.map { URL.certificatePath(for: $0.id, in: certificatesURL) })
let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: certificatesURL.path())) ?? []
let fullPathContents = contentsOfDirectory.map { certificatesURL.appending(path: $0).path() }
let untracked = Set(fullPathContents)
.subtracting(validPaths)
for path in untracked {
// string instead of fileURLWithPath since we're already using fileURL format.
try? FileManager.default.removeItem(at: URL(string: path)!)
}
}
try? FileManager.default.createDirectory(at: certificatesURL, withIntermediateDirectories: false, attributes: nil)
for certificate in certificates {
let path = URL.certificatePath(for: certificate.id, in: certificatesURL)
FileManager.default.createFile(atPath: path, contents: certificate.rawData, attributes: nil)
}
logger.log("Finished writing certificates")
}
/// The path for a Secret's SSH Certificate public key.
/// - Parameter secret: The Secret to return the path for.
/// - Returns: The path to the SSH Certificate public key.
/// - Warning: This method returning a path does not imply that a key has a SSH certificates. This method only describes where it will be.
private func legacySSHCertificatePath<SecretType: Secret>(for secret: SecretType) -> String {
let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "")
return publicKeysURL.appending(component: "\(minimalHex).pub").path()
}
}

View File

@@ -1,12 +1,11 @@
import Foundation
import OSLog
import SecretKit
import CertificateKit
public protocol SSHAgentInputParserProtocol {
func parse(data: Data) async throws -> SSHAgent.Request
}
public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
@@ -14,7 +13,7 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "InputParser")
public init() {
assert(Bundle.main.bundleURL.pathExtension == "xpc" || ProcessInfo.processInfo.processName == "xctest", "Potentially unsafe parsing code should run in an XPC service")
}
public func parse(data: Data) throws(AgentParsingError) -> SSHAgent.Request {
@@ -75,16 +74,21 @@ extension SSHAgentInputParser {
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 +
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
}

View File

@@ -1,69 +0,0 @@
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,6 +1,5 @@
import Foundation
import CryptoKit
import SecretKit
/// Generates OpenSSH representations of the public key sof secrets.
public struct OpenSSHPublicKeyWriter: Sendable {
@@ -41,14 +40,18 @@ public struct OpenSSHPublicKeyWriter: Sendable {
/// - Returns: OpenSSH SHA256 fingerprint string.
public func openSSHSHA256Fingerprint<SecretType: Secret>(secret: SecretType) -> String {
// OpenSSL format seems to strip the padding at the end.
let cleaned = SHA256.hash(data: data(secret: secret)).formatted(.base64(stripPadding: true))
let base64 = Data(SHA256.hash(data: data(secret: secret))).base64EncodedString()
let paddingRange = base64.index(base64.endIndex, offsetBy: -2)..<base64.endIndex
let cleaned = base64.replacingOccurrences(of: "=", with: "", range: paddingRange)
return "SHA256:\(cleaned)"
}
/// Generates an OpenSSH MD5 fingerprint string.
/// - Returns: OpenSSH MD5 fingerprint string.
public func openSSHMD5Fingerprint<SecretType: Secret>(secret: SecretType) -> String {
Insecure.MD5.hash(data: data(secret: secret)).formatted(.hex(separator: ":"))
Insecure.MD5.hash(data: data(secret: secret))
.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }
.joined(separator: ":")
}
public func comment<SecretType: Secret>(secret: SecretType) -> String {

View File

@@ -1,6 +1,5 @@
import Foundation
import CryptoKit
import SecretKit
/// Generates OpenSSH representations of Secrets.
public struct OpenSSHSignatureWriter: Sendable {
@@ -30,28 +29,19 @@ public struct OpenSSHSignatureWriter: Sendable {
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 {
let rawLength = rawRepresentation.count/2
let r = mpint(fromFixedWidthPositiveBytes: Data(rawRepresentation[0..<rawLength]))
let s = mpint(fromFixedWidthPositiveBytes: Data(rawRepresentation[rawLength...]))
// Check if we need to pad with 0x00 to prevent certain
// ssh servers from thinking r or s is negative
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()
signatureChunk.append(r.lengthAndData)

View File

@@ -0,0 +1,73 @@
import Foundation
import OSLog
/// Controller responsible for writing public keys to disk, so that they're easily accessible by scripts.
public final class PublicKeyFileStoreController: Sendable {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "PublicKeyFileStoreController")
private let directory: URL
private let keyWriter = OpenSSHPublicKeyWriter()
/// Initializes a PublicKeyFileStoreController.
public init(homeDirectory: URL) {
directory = homeDirectory.appending(component: "PublicKeys")
}
/// Writes out the keys specified to disk.
/// - Parameter secrets: The Secrets to generate keys for.
/// - Parameter clear: Whether or not any untracked files in the directory should be removed.
public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws {
logger.log("Writing public keys to disk")
if clear {
let validPaths = Set(secrets.map { publicKeyPath(for: $0) })
.union(Set(secrets.map { sshCertificatePath(for: $0) }))
let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: directory.path())) ?? []
let fullPathContents = contentsOfDirectory.map { directory.appending(path: $0).path() }
let untracked = Set(fullPathContents)
.subtracting(validPaths)
for path in untracked {
// string instead of fileURLWithPath since we're already using fileURL format.
try? FileManager.default.removeItem(at: URL(string: path)!)
}
}
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false, attributes: nil)
for secret in secrets {
let path = publicKeyPath(for: secret)
let data = Data(keyWriter.openSSHString(secret: secret).utf8)
FileManager.default.createFile(atPath: path, contents: data, attributes: nil)
}
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.
public var hasAnyCertificates: Bool {
do {
return try FileManager.default
.contentsOfDirectory(atPath: directory.path())
.filter { $0.hasSuffix("-cert.pub") }
.isEmpty == false
} catch {
return false
}
}
/// The path for a Secret's SSH Certificate public key.
/// - Parameter secret: The Secret to return the path for.
/// - Returns: The path to the SSH Certificate public key.
/// - Warning: This method returning a path does not imply that a key has a SSH certificates. This method only describes where it will be.
public func sshCertificatePath<SecretType: Secret>(for secret: SecretType) -> String {
let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "")
return directory.appending(component: "\(minimalHex)-cert.pub").path()
}
}

View File

@@ -0,0 +1,70 @@
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 name = String(localized: .secureEnclave)
private let persistentAuthenticationHandler = PersistentAuthenticationHandler<Secret>()
private let persistentAuthenticationHandler = PersistentAuthenticationHandler()
/// Initializes a Store.
@MainActor public init() {

View File

@@ -1,53 +0,0 @@
import Foundation
import Security
import CryptoTokenKit
import CryptoKit
import os
import SSHProtocolKit
import CertificateKit
public struct CertificateMigrator {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.migration", category: "CertificateKitMigrator")
private let publicKeysDirectory: URL
private let certificatesDirectory: URL
private let certificateStore: CertificateStore
/// Initializes a PublicKeyFileStoreController.
public init(homeDirectory: URL, certificateStore: CertificateStore) {
publicKeysDirectory = homeDirectory.appending(component: "PublicKeys")
certificatesDirectory = homeDirectory.appending(component: "Certificates")
self.certificateStore = certificateStore
}
@MainActor public func migrate() throws {
try migrate(directory: publicKeysDirectory)
try migrate(directory: certificatesDirectory)
}
@MainActor public func migrate(directory: URL) throws {
let fileCerts = try FileManager.default
.contentsOfDirectory(atPath: directory.path())
.filter { $0.hasSuffix("-cert.pub") }
Task {
for path in fileCerts {
do {
let url = directory.appending(component: path)
let data = try Data(contentsOf: url)
let parser = try await XPCCertificateParser()
let cert = try await parser.parse(data: data)
try certificateStore.save(certificate: Certificate(openSSHCertificate: cert, rawData: data))
do {
try FileManager.default.removeItem(at: url)
} catch {
logger.error("Failed to delete successfully migrated cert: \(path)")
}
} catch {
logger.error("Failed to migrate cert: \(path)")
}
}
}
}
}

View File

@@ -1,29 +0,0 @@
import Foundation
import OSLog
import SSHProtocolKit
import CertificateKit
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

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

View File

@@ -1,26 +0,0 @@
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)
let exportedObject = exportedObject
newConnection.exportedObject = exportedObject
newConnection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"\(ProcessInfo.processInfo.teamID)\"")
newConnection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = Z72PRUAWF6")
newConnection.resume()
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 {
let connection = NSXPCConnection(serviceName: serviceName)
connection.remoteObjectInterface = NSXPCInterface(with: (any _XPCProtocol).self)
connection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"\(ProcessInfo.processInfo.teamID)\"")
connection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = Z72PRUAWF6")
connection.resume()
guard let proxy = connection.remoteObjectProxy as? _XPCProtocol else { fatalError() }
self.connection = connection

View File

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

@@ -1,11 +0,0 @@
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,17 +1,15 @@
import Foundation
import Testing
import CryptoKit
import CertificateKit
@testable import SSHProtocolKit
@testable import SecretKit
@testable import SecretAgentKit
@Suite @MainActor struct AgentTests {
@Suite struct AgentTests {
// MARK: Identity Listing
@Test func emptyStores() async throws {
let agent = Agent(storeList: SecretStoreList(), certificateStore: CertificateStore())
let agent = Agent(storeList: SecretStoreList())
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities)
let response = await agent.handle(request: request, provenance: .test)
#expect(response == Constants.Responses.requestIdentitiesEmpty)
@@ -19,7 +17,7 @@ import CertificateKit
@Test func identitiesList() async throws {
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
let agent = Agent(storeList: list, certificateStore: CertificateStore())
let agent = Agent(storeList: list)
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities)
let response = await agent.handle(request: request, provenance: .test)
@@ -33,7 +31,7 @@ import CertificateKit
@Test func noMatchingIdentities() async throws {
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
let agent = Agent(storeList: list, certificateStore: CertificateStore())
let agent = Agent(storeList: list)
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignatureWithNoneMatching)
let response = await agent.handle(request: request, provenance: .test)
#expect(response == Constants.Responses.requestFailure)
@@ -43,11 +41,11 @@ import CertificateKit
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
guard case SSHAgent.Request.signRequest(let context) = request else { return }
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
let agent = Agent(storeList: list, certificateStore: CertificateStore())
let agent = Agent(storeList: list)
let response = await agent.handle(request: request, provenance: .test)
let responseReader = OpenSSHReader(data: response)
let length = try responseReader.readNextBytes(as: UInt32.self)
let type = try responseReader.readNextBytes(as: UInt8.self)
let length = try responseReader.readNextBytes(as: UInt32.self).bigEndian
let type = try responseReader.readNextBytes(as: UInt8.self).bigEndian
#expect(length == response.count - MemoryLayout<UInt32>.size)
#expect(type == SSHAgent.Response.agentSignResponse.rawValue)
let outer = OpenSSHReader(data: responseReader.remaining)
@@ -78,7 +76,7 @@ import CertificateKit
let witness = StubWitness(speakNow: { _,_ in
return true
}, witness: { _, _ in })
let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness)
let agent = Agent(storeList: list, witness: witness)
let response = await agent.handle(request: .signRequest(.empty), provenance: .test)
#expect(response == Constants.Responses.requestFailure)
}
@@ -91,7 +89,7 @@ import CertificateKit
}, witness: { _, trace in
witnessed = true
})
let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness)
let agent = Agent(storeList: list, witness: witness)
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
_ = await agent.handle(request: request, provenance: .test)
#expect(witnessed)
@@ -107,7 +105,7 @@ import CertificateKit
}, witness: { _, trace in
witnessTrace = trace
})
let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness)
let agent = Agent(storeList: list, witness: witness)
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
_ = await agent.handle(request: request, provenance: .test)
#expect(witnessTrace == speakNowTrace)
@@ -118,9 +116,9 @@ import CertificateKit
@Test func signatureException() async throws {
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
let store = list.stores.first?.base as! Stub.Store
let store = await list.stores.first?.base as! Stub.Store
store.shouldThrow = true
let agent = Agent(storeList: list, certificateStore: CertificateStore())
let agent = Agent(storeList: list)
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
let response = await agent.handle(request: request, provenance: .test)
#expect(response == Constants.Responses.requestFailure)
@@ -129,7 +127,7 @@ import CertificateKit
// MARK: Unsupported
@Test func unhandledAdd() async throws {
let agent = Agent(storeList: SecretStoreList(), certificateStore: CertificateStore())
let agent = Agent(storeList: SecretStoreList())
let response = await agent.handle(request: .addIdentity, provenance: .test)
#expect(response == Constants.Responses.requestFailure)
}
@@ -144,7 +142,7 @@ extension SigningRequestProvenance {
extension AgentTests {
func storeList(with secrets: [Stub.Secret]) async -> SecretStoreList {
@MainActor func storeList(with secrets: [Stub.Secret]) async -> SecretStoreList {
let store = Stub.Store()
store.secrets.append(contentsOf: secrets)
let storeList = SecretStoreList()

View File

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

View File

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

View File

@@ -1,7 +1,8 @@
import Foundation
import Testing
@testable import SecretKit
import SSHProtocolKit
@testable import SecureEnclaveSecretKit
@testable import SmartCardSecretKit
@Suite struct OpenSSHPublicKeyWriterTests {
@@ -46,8 +47,8 @@ import SSHProtocolKit
extension OpenSSHPublicKeyWriterTests {
enum Constants {
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 = 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"))
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 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"))
}

View File

@@ -6,21 +6,8 @@ import SmartCardSecretKit
import SecretAgentKit
import Brief
import Observation
import SSHProtocolKit
import CertificateKit
import Common
import SwiftUI
extension EnvironmentValues {
@MainActor fileprivate static let _certificateStore: CertificateStore = CertificateStore()
@MainActor var certificateStore: CertificateStore {
EnvironmentValues._certificateStore
}
}
@main
class AppDelegate: NSObject, NSApplicationDelegate {
@@ -31,15 +18,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
try? migrator.migrate(to: cryptoKit)
list.add(store: cryptoKit)
list.add(store: SmartCard.Store())
let certsMigrator = CertificateMigrator(homeDirectory: URL.homeDirectory, certificateStore: EnvironmentValues._certificateStore)
try? certsMigrator.migrate()
return list
}()
private let updater = Updater(checkOnLaunch: true)
private let notifier = Notifier()
private let publicKeyFileStoreController = PublicKeyFileStoreController(publicKeysURL: URL.publicKeyDirectory, certificatesURL: URL.certificatesDirectory)
@MainActor private lazy var agent: Agent = {
Agent(storeList: storeList, certificateStore: EnvironmentValues._certificateStore, witness: notifier)
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.homeDirectory)
private lazy var agent: Agent = {
Agent(storeList: storeList, witness: notifier)
}()
private lazy var socketController: SocketController = {
let path = URL.socketPath as String
@@ -50,9 +35,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
logger.debug("SecretAgent finished launching")
Task {
let inputParser = try await XPCAgentInputParser()
for await session in socketController.sessions {
Task {
let inputParser = try await XPCAgentInputParser()
do {
for await message in session.messages {
let request = try await inputParser.parse(data: message)
@@ -70,13 +55,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
}
}
Task {
for await _ in NotificationCenter.default.notifications(named: .certificateStoreReloaded) {
try? publicKeyFileStoreController.generateCertificates(for: EnvironmentValues._certificateStore.certificates, clear: true)
}
}
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
try? publicKeyFileStoreController.generateCertificates(for: EnvironmentValues._certificateStore.certificates, clear: true)
notifier.prompt()
_ = withObservationTracking {
updater.update

View File

@@ -1,47 +0,0 @@
import Foundation
import Security
import CryptoTokenKit
import CryptoKit
import os
import SSHProtocolKit
import CertificateKit
import SharedXPCServices
public struct CertificateMigrator {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.migration", category: "CertificateKitMigrator")
private let directory: URL
private let certificateStore: CertificateStore
/// Initializes a PublicKeyFileStoreController.
public init(homeDirectory: URL, certificateStore: CertificateStore) {
directory = homeDirectory.appending(component: "PublicKeys")
self.certificateStore = certificateStore
}
@MainActor public func migrate() throws {
let fileCerts = try FileManager.default
.contentsOfDirectory(atPath: directory.path())
.filter { $0.hasSuffix("-cert.pub") }
Task {
for path in fileCerts {
do {
let url = directory.appending(component: path)
let data = try Data(contentsOf: url)
let parser = try await XPCCertificateParser()
let cert = try await parser.parse(data: data)
try certificateStore.save(certificate: Certificate(openSSHCertificate: cert, rawData: data))
do {
try FileManager.default.removeItem(at: url)
} catch {
logger.error("Failed to delete successfully migrated cert: \(path)")
}
} catch {
logger.error("Failed to migrate cert: \(path)")
}
}
}
}
}

View File

@@ -2,24 +2,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-string</key>
<string>1</string>
<key>com.apple.security.hardened-process.hardened-heap</key>
<true/>
<key>com.apple.security.smartcard</key>
<true/>
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
<string>2</string>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string>

View File

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

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.hardened-heap</key>
<true/>
<key>com.apple.security.hardened-process.enhanced-security-version-string</key>
<string>1</string>
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
<string>2</string>
</dict>
</plist>

View File

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

View File

@@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */; };
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
5002C3AB2EEF483300FFAD22 /* XPCWrappers in Frameworks */ = {isa = PBXBuildFile; productRef = 5002C3AA2EEF483300FFAD22 /* XPCWrappers */; };
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3A278005E800DF2006 /* SecretKit */; };
5003EF3D278005F300DF2006 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3C278005F300DF2006 /* Brief */; };
5003EF3F278005F300DF2006 /* SecretAgentKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3E278005F300DF2006 /* SecretAgentKit */; };
@@ -31,7 +30,6 @@
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; };
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */; };
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
505F5EF22FA9635700C45824 /* CertificateKit in Frameworks */ = {isa = PBXBuildFile; productRef = 505F5EF12FA9635700C45824 /* CertificateKit */; };
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; };
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; };
@@ -62,7 +60,6 @@
50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79324026B7600D209EA /* Preview Assets.xcassets */; };
50A3B79724026B7600D209EA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79524026B7600D209EA /* Main.storyboard */; };
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 */; };
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; };
50BDCB722E63BAF20072D2E7 /* AgentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */; };
@@ -72,23 +69,6 @@
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; };
50E0145C2EDB9CDF00B121F1 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 50E0145B2EDB9CDF00B121F1 /* Common */; };
50E0145E2EDB9CE400B121F1 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 50E0145D2EDB9CE400B121F1 /* Common */; };
50E204E92FA9D12700402380 /* CertificateDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E204E82FA9D12700402380 /* CertificateDetailView.swift */; };
50E204ED2FAA997F00402380 /* CertificateListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E204EC2FAA997F00402380 /* CertificateListItemView.swift */; };
50E204EF2FAA9C1400402380 /* MultilineInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E204EE2FAA9C1400402380 /* MultilineInfoView.swift */; };
50E2051D2FAAB81C00402380 /* SecretiveCertificateParser.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
50E205282FAAB82700402380 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E205242FAAB82700402380 /* main.swift */; };
50E2052C2FAAB85000402380 /* SecretiveCertificateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E2052B2FAAB85000402380 /* SecretiveCertificateParser.swift */; };
50E2052D2FAAB92000402380 /* SecretiveCertificateParser.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
50E205312FAAB95500402380 /* XPCWrappers in Frameworks */ = {isa = PBXBuildFile; productRef = 50E205302FAAB95500402380 /* XPCWrappers */; };
50E205332FAAB95A00402380 /* SSHProtocolKit in Frameworks */ = {isa = PBXBuildFile; productRef = 50E205322FAAB95A00402380 /* SSHProtocolKit */; };
50E205362FAABC6300402380 /* EditCertificateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E205352FAABC6300402380 /* EditCertificateView.swift */; };
50E205372FAABC6300402380 /* DeleteCertificateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E205342FAABC6300402380 /* DeleteCertificateView.swift */; };
50E205802FAB291E00402380 /* CertificateMigrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E2057F2FAB291E00402380 /* CertificateMigrator.swift */; };
50E205822FAB293B00402380 /* SharedXPCServices in Frameworks */ = {isa = PBXBuildFile; productRef = 50E205812FAB293B00402380 /* SharedXPCServices */; };
50E205842FAB296A00402380 /* SharedXPCServices in Frameworks */ = {isa = PBXBuildFile; productRef = 50E205832FAB296A00402380 /* SharedXPCServices */; };
50E205862FAC2EA000402380 /* Formatters in Frameworks */ = {isa = PBXBuildFile; productRef = 50E205852FAC2EA000402380 /* Formatters */; };
50E205882FAC2EAB00402380 /* Formatters in Frameworks */ = {isa = PBXBuildFile; productRef = 50E205872FAC2EAB00402380 /* Formatters */; };
50E2058A2FAC2EB600402380 /* Formatters in Frameworks */ = {isa = PBXBuildFile; productRef = 50E205892FAC2EB600402380 /* Formatters */; };
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */; };
50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4C22E7765DF00C73783 /* AboutView.swift */; };
50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; };
@@ -138,20 +118,6 @@
remoteGlobalIDString = 50692E4F2E6FF9D20043C7BB;
remoteInfo = SecretAgentInputParser;
};
50E2051B2FAAB81C00402380 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 50E205132FAAB81C00402380;
remoteInfo = SecretAgentCertificateParser;
};
50E2052E2FAAB92000402380 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 50E205132FAAB81C00402380;
remoteInfo = SecretiveCertificateParser;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -161,7 +127,6 @@
dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
dstSubfolderSpec = 16;
files = (
50E2051D2FAAB81C00402380 /* SecretiveCertificateParser.xpc in Embed XPC Services */,
50692D1D2E6FDB880043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */,
50692E5B2E6FF9D20043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */,
);
@@ -175,7 +140,6 @@
dstSubfolderSpec = 16;
files = (
50692E6D2E6FFA5F0043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */,
50E2052D2FAAB92000402380 /* SecretiveCertificateParser.xpc in Embed XPC Services */,
50692E702E6FFA6E0043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */,
);
name = "Embed XPC Services";
@@ -217,8 +181,6 @@
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>"; };
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; };
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>"; };
@@ -272,21 +234,9 @@
50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItemView.swift; sourceTree = "<group>"; };
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
50E204E82FA9D12700402380 /* CertificateDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateDetailView.swift; sourceTree = "<group>"; };
50E204EC2FAA997F00402380 /* CertificateListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateListItemView.swift; sourceTree = "<group>"; };
50E204EE2FAA9C1400402380 /* MultilineInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineInfoView.swift; sourceTree = "<group>"; };
50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretiveCertificateParser.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
50E205232FAAB82700402380 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
50E205242FAAB82700402380 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
50E2052A2FAAB85000402380 /* SecretiveCertificateParser.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretiveCertificateParser.entitlements; sourceTree = "<group>"; };
50E2052B2FAAB85000402380 /* SecretiveCertificateParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretiveCertificateParser.swift; sourceTree = "<group>"; };
50E205342FAABC6300402380 /* DeleteCertificateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteCertificateView.swift; sourceTree = "<group>"; };
50E205352FAABC6300402380 /* EditCertificateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCertificateView.swift; sourceTree = "<group>"; };
50E2057F2FAB291E00402380 /* CertificateMigrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateMigrator.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>"; };
50E4C4C72E777E4200C73783 /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = "<group>"; };
F418C9A82F0C57F000E9ADF8 /* OpenSource.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = OpenSource.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -295,13 +245,10 @@
buildActionMask = 2147483647;
files = (
50E0145C2EDB9CDF00B121F1 /* Common in Frameworks */,
50E2058A2FAC2EB600402380 /* Formatters in Frameworks */,
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */,
501421622781262300BBAA70 /* Brief in Frameworks */,
50E205842FAB296A00402380 /* SharedXPCServices in Frameworks */,
5003EF5F2780081600DF2006 /* SecureEnclaveSecretKit in Frameworks */,
5003EF612780081600DF2006 /* SmartCardSecretKit in Frameworks */,
505F5EF22FA9635700C45824 /* CertificateKit in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -309,7 +256,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
50E205862FAC2EA000402380 /* Formatters in Frameworks */,
50692D2D2E6FDC000043C7BB /* XPCWrappers in Frameworks */,
50692D312E6FDC390043C7BB /* Brief in Frameworks */,
);
@@ -319,7 +265,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5002C3AB2EEF483300FFAD22 /* XPCWrappers in Frameworks */,
50692E6C2E6FFA510043C7BB /* SecretAgentKit in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -331,23 +276,12 @@
5003EF3D278005F300DF2006 /* Brief in Frameworks */,
5003EF632780081B00DF2006 /* SecureEnclaveSecretKit in Frameworks */,
5003EF652780081B00DF2006 /* SmartCardSecretKit in Frameworks */,
50E205822FAB293B00402380 /* SharedXPCServices in Frameworks */,
5003EF3F278005F300DF2006 /* SecretAgentKit in Frameworks */,
5003EF41278005FA00DF2006 /* SecretKit in Frameworks */,
50E0145E2EDB9CE400B121F1 /* Common in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
50E205112FAAB81C00402380 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
50E205882FAC2EAB00402380 /* Formatters in Frameworks */,
50E205332FAAB95A00402380 /* SSHProtocolKit in Frameworks */,
50E205312FAAB95500402380 /* XPCWrappers in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -369,14 +303,10 @@
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */,
50B8550C24138C4F009958AC /* DeleteSecretView.swift */,
2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */,
50E205342FAABC6300402380 /* DeleteCertificateView.swift */,
50E205352FAABC6300402380 /* EditCertificateView.swift */,
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */,
506772C82425BB8500034DED /* NoStoresView.swift */,
50C385A42407A76D00AF2719 /* SecretDetailView.swift */,
50E204E82FA9D12700402380 /* CertificateDetailView.swift */,
50153E21250DECA300525160 /* SecretListItemView.swift */,
50E204EC2FAA997F00402380 /* CertificateListItemView.swift */,
5079BA0E250F29BF00EA86F4 /* StoreListView.swift */,
);
path = Secrets;
@@ -402,7 +332,6 @@
50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */,
50617D8423FCE48E0099B055 /* ContentView.swift */,
5066A6C72516FE6E004B5A36 /* CopyableView.swift */,
50E204EE2FAA9C1400402380 /* MultilineInfoView.swift */,
50153E1F250AFCB200525160 /* UpdateView.swift */,
);
path = Views;
@@ -417,7 +346,6 @@
508A58AF241E144C0069DC07 /* Config */,
50692D272E6FDB8D0043C7BB /* SecretiveUpdater */,
50692E662E6FF9E20043C7BB /* SecretAgentInputParser */,
50E205262FAAB82700402380 /* SecretiveCertificateParser */,
50617D8023FCE48E0099B055 /* Products */,
5099A08B240243730062B6F2 /* Frameworks */,
);
@@ -430,7 +358,6 @@
50A3B78A24026B7500D209EA /* SecretAgent.app */,
50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */,
50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */,
50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */,
);
name = Products;
sourceTree = "<group>";
@@ -465,7 +392,6 @@
50692D272E6FDB8D0043C7BB /* SecretiveUpdater */ = {
isa = PBXGroup;
children = (
500666D02F04786900328939 /* SecretiveUpdater.entitlements */,
50692D232E6FDB8D0043C7BB /* Info.plist */,
50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */,
50692D242E6FDB8D0043C7BB /* main.swift */,
@@ -477,7 +403,6 @@
50692E662E6FF9E20043C7BB /* SecretAgentInputParser */ = {
isa = PBXGroup;
children = (
500666D12F04787200328939 /* SecretAgentInputParser.entitlements */,
50692E622E6FF9E20043C7BB /* Info.plist */,
50692E632E6FF9E20043C7BB /* main.swift */,
50692E642E6FF9E20043C7BB /* SecretAgentInputParser.swift */,
@@ -490,7 +415,6 @@
children = (
508A590F241EEF6D0069DC07 /* Secretive.xctestplan */,
508A58AB241E121B0069DC07 /* Config.xcconfig */,
F418C9A82F0C57F000E9ADF8 /* OpenSource.xcconfig */,
);
path = Config;
sourceTree = "<group>";
@@ -529,7 +453,6 @@
50020BAF24064869003D4025 /* AppDelegate.swift */,
5018F54E24064786002EB505 /* Notifier.swift */,
501578122E6C0479004A37D0 /* XPCInputParser.swift */,
50E2057F2FAB291E00402380 /* CertificateMigrator.swift */,
50A3B79524026B7600D209EA /* Main.storyboard */,
50A3B79824026B7600D209EA /* Info.plist */,
508BF29425B4F140009EFB7E /* InternetAccessPolicy.plist */,
@@ -547,17 +470,6 @@
path = "Preview Content";
sourceTree = "<group>";
};
50E205262FAAB82700402380 /* SecretiveCertificateParser */ = {
isa = PBXGroup;
children = (
50E205232FAAB82700402380 /* Info.plist */,
50E205242FAAB82700402380 /* main.swift */,
50E2052A2FAAB85000402380 /* SecretiveCertificateParser.entitlements */,
50E2052B2FAAB85000402380 /* SecretiveCertificateParser.swift */,
);
path = SecretiveCertificateParser;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -578,7 +490,6 @@
50142167278126B500BBAA70 /* PBXTargetDependency */,
50692D1C2E6FDB880043C7BB /* PBXTargetDependency */,
50692E5A2E6FF9D20043C7BB /* PBXTargetDependency */,
50E2051C2FAAB81C00402380 /* PBXTargetDependency */,
);
name = Secretive;
packageProductDependencies = (
@@ -587,9 +498,6 @@
5003EF602780081600DF2006 /* SmartCardSecretKit */,
501421612781262300BBAA70 /* Brief */,
50E0145B2EDB9CDF00B121F1 /* Common */,
505F5EF12FA9635700C45824 /* CertificateKit */,
50E205832FAB296A00402380 /* SharedXPCServices */,
50E205892FAC2EB600402380 /* Formatters */,
);
productName = Secretive;
productReference = 50617D7F23FCE48E0099B055 /* Secretive.app */;
@@ -611,7 +519,6 @@
packageProductDependencies = (
50692D2C2E6FDC000043C7BB /* XPCWrappers */,
50692D302E6FDC390043C7BB /* Brief */,
50E205852FAC2EA000402380 /* Formatters */,
);
productName = SecretiveUpdater;
productReference = 50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */;
@@ -632,7 +539,6 @@
name = SecretAgentInputParser;
packageProductDependencies = (
50692E6B2E6FFA510043C7BB /* SecretAgentKit */,
5002C3AA2EEF483300FFAD22 /* XPCWrappers */,
);
productName = SecretAgentInputParser;
productReference = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */;
@@ -654,7 +560,6 @@
501577D42E6BC5DD004A37D0 /* PBXTargetDependency */,
50692E6F2E6FFA5F0043C7BB /* PBXTargetDependency */,
50692E722E6FFA6E0043C7BB /* PBXTargetDependency */,
50E2052F2FAAB92000402380 /* PBXTargetDependency */,
);
name = SecretAgent;
packageProductDependencies = (
@@ -664,34 +569,11 @@
5003EF622780081B00DF2006 /* SecureEnclaveSecretKit */,
5003EF642780081B00DF2006 /* SmartCardSecretKit */,
50E0145D2EDB9CE400B121F1 /* Common */,
50E205812FAB293B00402380 /* SharedXPCServices */,
);
productName = SecretAgent;
productReference = 50A3B78A24026B7500D209EA /* SecretAgent.app */;
productType = "com.apple.product-type.application";
};
50E205132FAAB81C00402380 /* SecretiveCertificateParser */ = {
isa = PBXNativeTarget;
buildConfigurationList = 50E2051F2FAAB81C00402380 /* Build configuration list for PBXNativeTarget "SecretiveCertificateParser" */;
buildPhases = (
50E205102FAAB81C00402380 /* Sources */,
50E205112FAAB81C00402380 /* Frameworks */,
50E205122FAAB81C00402380 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = SecretiveCertificateParser;
packageProductDependencies = (
50E205302FAAB95500402380 /* XPCWrappers */,
50E205322FAAB95A00402380 /* SSHProtocolKit */,
50E205872FAC2EAB00402380 /* Formatters */,
);
productName = SecretAgentCertificateParser;
productReference = 50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */;
productType = "com.apple.product-type.xpc-service";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -699,8 +581,8 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 2650;
LastUpgradeCheck = 2640;
LastSwiftUpdateCheck = 2600;
LastUpgradeCheck = 2600;
ORGANIZATIONNAME = "Max Goedjen";
TargetAttributes = {
50617D7E23FCE48D0099B055 = {
@@ -715,9 +597,6 @@
50A3B78924026B7500D209EA = {
CreatedOnToolsVersion = 11.4;
};
50E205132FAAB81C00402380 = {
CreatedOnToolsVersion = 26.5;
};
};
};
buildConfigurationList = 50617D7A23FCE48D0099B055 /* Build configuration list for PBXProject "Secretive" */;
@@ -745,7 +624,6 @@
50A3B78924026B7500D209EA /* SecretAgent */,
50692D112E6FDB880043C7BB /* SecretiveUpdater */,
50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */,
50E205132FAAB81C00402380 /* SecretiveCertificateParser */,
);
};
/* End PBXProject section */
@@ -766,7 +644,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
50B832C02F62202A00D2FCB8 /* InternetAccessPolicy.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -789,13 +666,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
50E205122FAAB81C00402380 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -808,16 +678,13 @@
50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */,
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */,
50E204E92FA9D12700402380 /* CertificateDetailView.swift in Sources */,
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */,
50E204EF2FAA9C1400402380 /* MultilineInfoView.swift in Sources */,
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */,
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */,
50E204ED2FAA997F00402380 /* CertificateListItemView.swift in Sources */,
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */,
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */,
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */,
@@ -830,14 +697,12 @@
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */,
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */,
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
50E205372FAABC6300402380 /* DeleteCertificateView.swift in Sources */,
50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */,
50617D8323FCE48E0099B055 /* App.swift in Sources */,
504788F42E681F6900B4556F /* ToolConfigurationView.swift in Sources */,
506772C92425BB8500034DED /* NoStoresView.swift in Sources */,
50153E22250DECA300525160 /* SecretListItemView.swift in Sources */,
508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */,
50E205362FAABC6300402380 /* EditCertificateView.swift in Sources */,
508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -864,22 +729,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
50E205802FAB291E00402380 /* CertificateMigrator.swift in Sources */,
50020BB024064869003D4025 /* AppDelegate.swift in Sources */,
5018F54F24064786002EB505 /* Notifier.swift in Sources */,
501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
50E205102FAAB81C00402380 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
50E205282FAAB82700402380 /* main.swift in Sources */,
50E2052C2FAAB85000402380 /* SecretiveCertificateParser.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -912,16 +767,6 @@
target = 50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */;
targetProxy = 50692E712E6FFA6E0043C7BB /* PBXContainerItemProxy */;
};
50E2051C2FAAB81C00402380 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 50E205132FAAB81C00402380 /* SecretiveCertificateParser */;
targetProxy = 50E2051B2FAAB81C00402380 /* PBXContainerItemProxy */;
};
50E2052F2FAAB92000402380 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 50E205132FAAB81C00402380 /* SecretiveCertificateParser */;
targetProxy = 50E2052E2FAAB92000402380 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -1092,7 +937,7 @@
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
DEVELOPMENT_TEAM = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
@@ -1115,7 +960,7 @@
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).Host";
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
};
@@ -1132,7 +977,7 @@
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
DEVELOPMENT_TEAM = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
@@ -1155,7 +1000,7 @@
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).Host";
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Host";
};
@@ -1165,19 +1010,16 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = SecretiveUpdater/SecretiveUpdater.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
DEVELOPMENT_TEAM = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_POINTER_AUTHENTICATION = YES;
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
@@ -1194,7 +1036,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretiveUpdater";
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
@@ -1211,16 +1053,13 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = SecretiveUpdater/SecretiveUpdater.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
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;
@@ -1237,7 +1076,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretiveUpdater";
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
@@ -1253,19 +1092,16 @@
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)";
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_POINTER_AUTHENTICATION = YES;
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
@@ -1282,7 +1118,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretiveUpdater";
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
REGISTER_APP_GROUPS = YES;
@@ -1299,16 +1135,13 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = SecretAgentInputParser/SecretAgentInputParser.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
DEVELOPMENT_TEAM = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_POINTER_AUTHENTICATION = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SecretAgentInputParser/Info.plist;
@@ -1317,7 +1150,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentInputParser";
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
@@ -1334,14 +1167,11 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = SecretAgentInputParser/SecretAgentInputParser.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
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;
@@ -1350,7 +1180,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentInputParser";
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
@@ -1366,17 +1196,14 @@
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)";
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_POINTER_AUTHENTICATION = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SecretAgentInputParser/Info.plist;
@@ -1385,7 +1212,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentInputParser";
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
REGISTER_APP_GROUPS = YES;
@@ -1506,7 +1333,7 @@
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).Host";
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
@@ -1515,17 +1342,14 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
ENABLE_APP_SANDBOX = YES;
ENABLE_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;
@@ -1542,7 +1366,7 @@
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
@@ -1556,13 +1380,11 @@
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
DEVELOPMENT_TEAM = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
ENABLE_POINTER_AUTHENTICATION = YES;
ENABLE_PREVIEWS = YES;
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
@@ -1579,7 +1401,7 @@
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
@@ -1594,13 +1416,11 @@
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
DEVELOPMENT_TEAM = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_ENHANCED_SECURITY = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
ENABLE_POINTER_AUTHENTICATION = YES;
ENABLE_PREVIEWS = YES;
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
@@ -1617,109 +1437,12 @@
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent";
};
name = Release;
};
50E205202FAAB81C00402380 /* 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 = "$(SECRETIVE_DEVELOPMENT_TEAM)";
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SecretiveCertificateParser/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SecretiveCertificateParser;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).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;
};
50E205212FAAB81C00402380 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = SecretiveCertificateParser/SecretiveCertificateParser.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SecretiveCertificateParser/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SecretiveCertificateParser;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretiveCertificateParser";
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;
};
50E205222FAAB81C00402380 /* 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 = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SecretiveCertificateParser/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SecretiveCertificateParser;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretiveCertificateParser";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -1773,23 +1496,9 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
50E2051F2FAAB81C00402380 /* Build configuration list for PBXNativeTarget "SecretiveCertificateParser" */ = {
isa = XCConfigurationList;
buildConfigurations = (
50E205202FAAB81C00402380 /* Debug */,
50E205212FAAB81C00402380 /* Test */,
50E205222FAAB81C00402380 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCSwiftPackageProductDependency section */
5002C3AA2EEF483300FFAD22 /* XPCWrappers */ = {
isa = XCSwiftPackageProductDependency;
productName = XPCWrappers;
};
5003EF3A278005E800DF2006 /* SecretKit */ = {
isa = XCSwiftPackageProductDependency;
productName = SecretKit;
@@ -1826,10 +1535,6 @@
isa = XCSwiftPackageProductDependency;
productName = Brief;
};
505F5EF12FA9635700C45824 /* CertificateKit */ = {
isa = XCSwiftPackageProductDependency;
productName = CertificateKit;
};
50692D2C2E6FDC000043C7BB /* XPCWrappers */ = {
isa = XCSwiftPackageProductDependency;
productName = XPCWrappers;
@@ -1850,34 +1555,6 @@
isa = XCSwiftPackageProductDependency;
productName = Common;
};
50E205302FAAB95500402380 /* XPCWrappers */ = {
isa = XCSwiftPackageProductDependency;
productName = XPCWrappers;
};
50E205322FAAB95A00402380 /* SSHProtocolKit */ = {
isa = XCSwiftPackageProductDependency;
productName = SSHProtocolKit;
};
50E205812FAB293B00402380 /* SharedXPCServices */ = {
isa = XCSwiftPackageProductDependency;
productName = SharedXPCServices;
};
50E205832FAB296A00402380 /* SharedXPCServices */ = {
isa = XCSwiftPackageProductDependency;
productName = SharedXPCServices;
};
50E205852FAC2EA000402380 /* Formatters */ = {
isa = XCSwiftPackageProductDependency;
productName = Formatters;
};
50E205872FAC2EAB00402380 /* Formatters */ = {
isa = XCSwiftPackageProductDependency;
productName = Formatters;
};
50E205892FAC2EB600402380 /* Formatters */ = {
isa = XCSwiftPackageProductDependency;
productName = Formatters;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 50617D7723FCE48D0099B055 /* Project object */;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,93 +0,0 @@
import SwiftUI
import SecretKit
import Common
import CertificateKit
import SSHProtocolKit
import CryptoKit
struct CertificateDetailView: View {
let certificate: Certificate
var body: some View {
ScrollView {
Form {
Section {
CopyableView(
title: .certificateDetailKeyIdLabel,
image: Image(systemName: "person.text.rectangle"),
text: certificate.keyID
)
Spacer()
.frame(height: 20)
CopyableView(
title: .certificateDetailSerialLabel,
image: Image(systemName: "number.circle"),
text: certificate.serial.formatted()
)
Spacer()
.frame(height: 20)
CopyableView(
title: .secretDetailSha256FingerprintLabel,
image: Image(systemName: "touchid"),
text: OpenSSHCertificateWriter().openSSHSHA256KeyFingerprint(publicKey: certificate.publicKey)
)
Spacer()
.frame(height: 20)
CopyableView(
title: .secretDetailSha256FingerprintLabel,
image: Image(systemName: "touchid"),
text: OpenSSHCertificateWriter().openSSHSHA256KeyFingerprint(publicKey: certificate.signingKey)
)
Spacer()
.frame(height: 20)
CopyableView(
title: .certificateDetailPathLabel,
image: Image(systemName: "checkmark.seal.text.page"),
text: URL.certificatePath(for: certificate.id, in: URL.certificatesDirectory),
showRevealInFinder: true
)
if let validityRange = certificate.validityRange {
let epoch = Date(timeIntervalSince1970: 0)
let end = Date(timeIntervalSince1970: TimeInterval(UInt64.max))
switch (validityRange.lowerBound, validityRange.upperBound) {
case (epoch, end):
EmptyView()
case (epoch, let otherEnd):
Spacer()
.frame(height: 20)
MultilineInfoView(title: .certificateDetailValidUntilLabel, image: Image(systemName: "calendar.badge.clock"), items: [otherEnd.formatted()])
case (let otherStart, end):
Spacer()
.frame(height: 20)
MultilineInfoView(title: .certificateDetailValidAfterLabel, image: Image(systemName: "calendar.badge.clock"), items: [otherStart.formatted()])
default:
Spacer()
.frame(height: 20)
MultilineInfoView(title: .certificateDetailValidityRangeLabel, image: Image(systemName: "calendar.badge.clock"), items: [validityRange.formatted()])
}
}
if !certificate.principals.isEmpty {
Spacer()
.frame(height: 20)
MultilineInfoView(title: .certificateDetailPrincipalsLabel, image: Image(systemName: "person.2"), items: certificate.principals)
}
if !certificate.criticalOptions.isEmpty {
Spacer()
.frame(height: 20)
MultilineInfoView(title: .certificateDetailCriticalOptionsLabel, image: Image(systemName: "person.2"), items: certificate.criticalOptions)
}
if !certificate.extensions.isEmpty {
Spacer()
.frame(height: 20)
MultilineInfoView(title: .certificateDetailExtensionsLabel, image: Image(systemName: "person.2"), items: certificate.extensions)
}
Spacer()
}
}
.padding()
}
.frame(minHeight: 200, maxHeight: .infinity)
}
}

View File

@@ -1,42 +0,0 @@
import SwiftUI
import CertificateKit
import SSHProtocolKit
struct CertificateListItemView: View {
@Environment(\.certificateStore) private var store
var certificate: Certificate
@State var isDeleting: Bool = false
@State var isRenaming: Bool = false
var deletedCertificate: (Certificate) -> Void
var renamedCertificate: (Certificate) -> Void
var body: some View {
NavigationLink(value: certificate) {
Text(certificate.name)
}
.sheet(isPresented: $isRenaming, onDismiss: {
renamedCertificate(certificate)
}, content: {
EditCertificateView(store: store, certificate: certificate)
})
.showingDeleteConfirmation(isPresented: $isDeleting, certificate, store) { deleted in
if deleted {
deletedCertificate(certificate)
}
}
.contextMenu {
Button(action: { isRenaming = true }) {
Image(systemName: "pencil")
Text(.secretListEditButton)
}
Button(action: { isDeleting = true }) {
Image(systemName: "trash")
Text(.secretListDeleteButton)
}
}
}
}

View File

@@ -1,52 +0,0 @@
import SwiftUI
import CertificateKit
import SSHProtocolKit
extension View {
func showingDeleteConfirmation(isPresented: Binding<Bool>, _ certificate: Certificate, _ store: CertificateStore, dismissalBlock: @escaping (Bool) -> ()) -> some View {
modifier(DeleteCertificateConfirmationModifier(isPresented: isPresented, certificate: certificate, store: store, dismissalBlock: dismissalBlock))
}
}
struct DeleteCertificateConfirmationModifier: ViewModifier {
var isPresented: Binding<Bool>
var certificate: Certificate
var store: CertificateStore
var dismissalBlock: (Bool) -> ()
@State var confirmedSecretName = ""
@State private var errorText: String?
func body(content: Content) -> some View {
content
.confirmationDialog(
String(localized: .deleteConfirmationTitle(name: certificate.name)),
isPresented: isPresented,
titleVisibility: .visible,
actions: {
Button(.deleteConfirmationDeleteButton, action: delete)
Button(.deleteConfirmationCancelButton, role: .cancel) {
dismissalBlock(false)
}
},
)
.dialogIcon(Image(systemName: "lock.trianglebadge.exclamationmark.fill"))
.onExitCommand {
dismissalBlock(false)
}
}
func delete() {
Task {
do {
try store.delete(certificate: certificate)
dismissalBlock(true)
} catch {
errorText = error.localizedDescription
}
}
}
}

View File

@@ -21,7 +21,7 @@ struct DeleteSecretConfirmationModifier: ViewModifier {
func body(content: Content) -> some View {
content
.confirmationDialog(
.deleteConfirmationTitle(name: secret.name),
.deleteConfirmationTitle(secretName: secret.name),
isPresented: isPresented,
titleVisibility: .visible,
actions: {

View File

@@ -1,60 +0,0 @@
import SwiftUI
import SSHProtocolKit
import CertificateKit
struct EditCertificateView: View {
let store: CertificateStore
let certificate: Certificate
@State private var name: String
@State var errorText: String?
@Environment(\.dismiss) var dismiss
init(store: CertificateStore, certificate: Certificate) {
self.store = store
self.certificate = certificate
name = certificate.name
}
var body: some View {
VStack(alignment: .trailing) {
Form {
Section {
TextField(String(localized: .renameCertificateLabel), text: $name, prompt: Text(.renameCertificateNamePlaceholder))
} footer: {
if let errorText {
Text(verbatim: errorText)
.errorStyle()
}
}
}
HStack {
Button(.editCancelButton) {
dismiss()
}
.keyboardShortcut(.cancelAction)
Button(.editSaveButton, action: rename)
.disabled(name.isEmpty)
.keyboardShortcut(.return)
.primaryButton()
}
.padding()
}
.formStyle(.grouped)
}
func rename() {
Task {
do {
var updated = certificate
updated.openSSHCertificate.name = name
try store.update(certificate: updated)
dismiss()
} catch {
errorText = error.localizedDescription
}
}
}
}

View File

@@ -1,57 +1,27 @@
import SwiftUI
import SecretKit
import Common
import CertificateKit
import SSHProtocolKit
struct SecretDetailView<SecretType: Secret>: View {
let secret: SecretType
let certificates: [Certificate]
let navigateToCertificate: ((Certificate) -> Void)?
private let keyWriter = OpenSSHPublicKeyWriter()
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL)
var body: some View {
ScrollView {
Form {
Section {
CopyableView(
title: .secretDetailSha256FingerprintLabel,
image: Image(systemName: "touchid"),
text: keyWriter.openSSHSHA256Fingerprint(secret: secret)
)
CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "touchid"), text: keyWriter.openSSHSHA256Fingerprint(secret: secret))
Spacer()
.frame(height: 20)
CopyableView(
title: .secretDetailMd5FingerprintLabel,
image: Image(systemName: "touchid"),
text: keyWriter.openSSHMD5Fingerprint(secret: secret)
)
CopyableView(title: .secretDetailMd5FingerprintLabel, image: Image(systemName: "touchid"), text: keyWriter.openSSHMD5Fingerprint(secret: secret))
Spacer()
.frame(height: 20)
CopyableView(
title: .secretDetailPublicKeyPathLabel,
image: Image(systemName: "lock.doc"),
text: URL.publicKeyPath(for: secret, in: URL.publicKeyDirectory),
showRevealInFinder: true
)
if !certificates.isEmpty {
Spacer()
.frame(height: 20)
MultilineInfoView(
title: .secretDetailCertificatePathLabel,
image: Image(
systemName: "checkmark.seal.text.page"
),
items: certificates.map({ certificate in
MultilineInfoView.Item(
text: certificate.name,
action: (Image(systemName: "chevron.forward"), { navigateToCertificate?(certificate) })
)
})
)
}
CopyableView(title: .secretDetailPublicKeyLabel, image: Image(systemName: "key"), text: keyString)
Spacer()
.frame(height: 20)
CopyableView(title: .secretDetailPublicKeyPathLabel, image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.publicKeyPath(for: secret), showRevealInFinder: true)
Spacer()
}
}
@@ -61,6 +31,10 @@ struct SecretDetailView<SecretType: Secret>: View {
}
var keyString: String {
keyWriter.openSSHString(secret: secret)
}
}
//#Preview {

View File

@@ -24,6 +24,23 @@ struct SecretListItemView: View {
Text(secret.name)
}
}
.contextMenu {
if store is AnySecretStoreModifiable {
Button(action: { isRenaming = true }) {
// Image(systemName: "pencil")
Text(.secretListEditButton)
}
Button(action: { isDeleting = true }) {
// Image(systemName: "trash")
Text(.secretListDeleteButton)
}
}
}
.showingDeleteConfirmation(isPresented: $isDeleting, secret, store as? AnySecretStoreModifiable) { deleted in
if deleted {
deletedSecret(secret)
}
}
.sheet(isPresented: $isRenaming, onDismiss: {
renamedSecret(secret)
}, content: {
@@ -31,22 +48,5 @@ struct SecretListItemView: View {
EditSecretView(store: modifiable, secret: secret)
}
})
.showingDeleteConfirmation(isPresented: $isDeleting, secret, store as? AnySecretStoreModifiable) { deleted in
if deleted {
deletedSecret(secret)
}
}
.contextMenu {
if store is AnySecretStoreModifiable {
Button(action: { isRenaming = true }) {
Image(systemName: "pencil")
Text(.secretListEditButton)
}
Button(action: { isDeleting = true }) {
Image(systemName: "trash")
Text(.secretListDeleteButton)
}
}
}
}
}

View File

@@ -1,33 +1,25 @@
import SwiftUI
import SecretKit
import SSHProtocolKit
import CertificateKit
struct StoreListView: View {
enum StoreListSelection: Hashable {
case secret(AnySecret)
case certificate(Certificate)
}
@Binding var selection: StoreListSelection?
@Binding var activeSecret: AnySecret?
@Environment(\.secretStoreList) private var storeList
@Environment(\.certificateStore) private var certificateStore
private func secretDeleted(secret: AnySecret) {
selection = nextDefaultSecret.map(StoreListSelection.secret)
activeSecret = nextDefaultSecret
}
private func secretRenamed(secret: AnySecret) {
// Pull new version from store, so we get all updated attributes
selection = nil
selection = storeList.allSecrets.first(where: { $0.id == secret.id }).map(StoreListSelection.secret)
activeSecret = nil
activeSecret = storeList.allSecrets.first(where: { $0.id == secret.id })
}
var body: some View {
NavigationSplitView {
List(selection: $selection) {
List(selection: $activeSecret) {
ForEach(storeList.stores) { store in
if store.isAvailable {
Section(header: Text(store.name)) {
@@ -38,51 +30,29 @@ struct StoreListView: View {
deletedSecret: secretDeleted,
renamedSecret: secretRenamed,
)
.tag(StoreListSelection.secret(secret))
}
}
}
}
if !certificateStore.certificates.isEmpty {
Section("Certificates") {
ForEach(certificateStore.certificates) { certificate in
CertificateListItemView(
certificate: certificate,
deletedCertificate: { _ in },
renamedCertificate: { _ in }
)
.tag(StoreListSelection.certificate(certificate))
}
}
}
}
} detail: {
switch selection {
case .secret(let secret):
SecretDetailView(secret: secret, certificates: certificateStore.certificates(for: secret)) {
selection = .certificate($0)
}
case .certificate(let certificate):
CertificateDetailView(certificate: certificate)
case nil:
if let nextDefaultSecret {
// This just means onAppear hasn't executed yet.
// Do this to avoid a blip.
SecretDetailView(secret: nextDefaultSecret, certificates: certificateStore.certificates(for: nextDefaultSecret)) {
selection = .certificate($0)
}
if let activeSecret {
SecretDetailView(secret: activeSecret)
} else if let nextDefaultSecret {
// This just means onAppear hasn't executed yet.
// Do this to avoid a blip.
SecretDetailView(secret: nextDefaultSecret)
} else {
if let modifiable = storeList.modifiableStore, modifiable.isAvailable {
EmptyStoreView(store: modifiable)
} else {
if let modifiable = storeList.modifiableStore, modifiable.isAvailable {
EmptyStoreView(store: modifiable)
} else {
EmptyStoreView(store: storeList.stores.first(where: \.isAvailable))
}
EmptyStoreView(store: storeList.stores.first(where: \.isAvailable))
}
}
}
.navigationSplitViewStyle(.balanced)
.onAppear {
selection = nextDefaultSecret.map(StoreListSelection.secret)
activeSecret = nextDefaultSecret
}
.frame(minWidth: 100, idealWidth: 240)

View File

@@ -3,20 +3,16 @@ import SecretKit
import SecureEnclaveSecretKit
import SmartCardSecretKit
import Brief
import SSHProtocolKit
import SharedXPCServices
import CertificateKit
struct ContentView: View {
@State var selection: StoreListView.StoreListSelection?
@State var activeSecret: AnySecret?
@State private var selectedUpdate: Release?
@Environment(\.colorScheme) private var colorScheme
@Environment(\.openWindow) private var openWindow
@Environment(\.secretStoreList) private var storeList
@Environment(\.certificateStore) private var certificateStore
@Environment(\.updater) private var updater
@Environment(\.agentLaunchController) private var agentLaunchController
@@ -29,7 +25,7 @@ struct ContentView: View {
var body: some View {
VStack {
if storeList.anyAvailable {
StoreListView(selection: $selection)
StoreListView(activeSecret: $activeSecret)
} else {
NoStoresView()
}
@@ -46,22 +42,6 @@ struct ContentView: View {
runningSetup = true
}
}
.dropDestination(for: URL.self) { items, location in
guard let url = items.first, url.pathExtension == "pub" else { return false }
Task {
do {
let data = try Data(contentsOf: url)
let parser = try await XPCCertificateParser()
let cert = try await parser.parse(data: data)
let wrapped = Certificate(openSSHCertificate: cert, rawData: data)
try certificateStore.save(certificate: wrapped)
selection = .certificate(wrapped)
} catch {
}
}
return true
} isTargeted: { _ in }
.focusedSceneValue(\.showCreateSecret, .init(isEnabled: !runningSetup) {
showingCreation = true
})
@@ -69,7 +49,7 @@ struct ContentView: View {
if let modifiable = storeList.modifiableStore {
CreateSecretView(store: modifiable) { created in
if let created {
selection = .secret(created)
activeSecret = created
}
}
}

View File

@@ -4,13 +4,12 @@ import UniformTypeIdentifiers
struct CopyableView: View {
var title: LocalizedStringResource
var subtitle: String?
var image: Image
var text: String
var showRevealInFinder = false
@State private var interactionState: InteractionState = .normal
var content: some View {
VStack(alignment: .leading, spacing: 15) {
HStack {
@@ -18,16 +17,9 @@ struct CopyableView: View {
.renderingMode(.template)
.imageScale(.large)
.foregroundColor(primaryTextColor)
VStack(alignment: .leading) {
Text(title)
.font(.headline)
.foregroundColor(primaryTextColor)
if let subtitle {
Text(subtitle)
.font(.system(.subheadline, design: .monospaced))
.foregroundColor(secondaryTextColor)
}
}
Text(title)
.font(.headline)
.foregroundColor(primaryTextColor)
Spacer()
if interactionState != .normal {
HStack {

View File

@@ -1,167 +0,0 @@
import SwiftUI
import UniformTypeIdentifiers
struct MultilineInfoView: View {
struct Item {
let text: String
let action: (Image, () -> Void)?
}
var title: LocalizedStringResource
var image: Image
var items: [Item]
init(title: LocalizedStringResource, image: Image, items: [Item]) {
self.title = title
self.image = image
self.items = items
}
init(title: LocalizedStringResource, image: Image, items: [String]) {
self.title = title
self.image = image
self.items = items.map({ Item(text: $0, action: nil) })
}
@State private var interactionState: InteractionState = .normal
@State private var interactionStateIndex: Int?
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack {
image
.renderingMode(.template)
.imageScale(.large)
.foregroundColor(primaryTextColor)
Text(title)
.font(.headline)
.foregroundColor(primaryTextColor)
Spacer()
}
.safeAreaPadding(20)
ForEach(Array(items.enumerated()), id: \.offset) { item in
Divider()
.ignoresSafeArea()
.opacity(item.offset == 0 ? 1 : 0.75)
HStack {
Text(item.element.text)
Spacer()
if let (image, _) = item.element.action {
image
.foregroundStyle(.secondary)
}
}
.safeAreaPadding(20)
._background(interactionState: interactionStateIndex == item.offset ? interactionState : .normal, cornerRadius: 0)
.onHover { hovering in
withAnimation {
guard item.element.action != nil else { return }
interactionState = hovering ? .hovering : .normal
interactionStateIndex = item.offset
}
}
.gesture(
TapGesture()
.onEnded {
item.element.action?.1()
withAnimation {
interactionState = .normal
interactionStateIndex = nil
}
}
)
}
}
._background(interactionState: .normal)
.frame(minWidth: 150, maxWidth: .infinity)
}
var primaryTextColor: Color {
switch interactionState {
case .normal, .hovering:
return Color(.textColor)
}
}
var secondaryTextColor: Color {
switch interactionState {
case .normal, .hovering:
return Color(.secondaryLabelColor)
}
}
}
fileprivate enum InteractionState {
case normal, hovering
}
extension View {
fileprivate func _background(interactionState: InteractionState, cornerRadius: Double = 15) -> some View {
modifier(BackgroundViewModifier(interactionState: interactionState, cornerRadius: cornerRadius))
}
}
fileprivate struct BackgroundViewModifier: ViewModifier {
@Environment(\.colorScheme) private var colorScheme
@Environment(\.appearsActive) private var appearsActive
let interactionState: InteractionState
let cornerRadius: Double
func body(content: Content) -> some View {
if #available(macOS 26.0, *) {
content
// Very thin opacity lets user hover anywhere over the view, glassEffect doesn't allow.
.background(.white.opacity(0.01), in: RoundedRectangle(cornerRadius: 15))
.glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), in: RoundedRectangle(cornerRadius: cornerRadius))
.mask(RoundedRectangle(cornerRadius: cornerRadius))
.shadow(color: .black.opacity(0.1), radius: 5)
} else {
content
.background(backgroundColor(interactionState: interactionState))
.cornerRadius(10)
}
}
func backgroundColor(interactionState: InteractionState) -> Color {
guard appearsActive else { return Color.clear }
if #available(macOS 26.0, *) {
let base = colorScheme == .dark ? Color(white: 0.2) : Color(white: 1)
switch interactionState {
case .normal:
return base
case .hovering:
return base.mix(with: .accentColor, by: colorScheme == .dark ? 0.2 : 0.1)
}
} else {
switch interactionState {
case .normal:
return colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.885)
case .hovering:
return colorScheme == .dark ? Color(white: 0.275) : Color(white: 0.82)
}
}
}
}
#Preview {
MultilineInfoView(title: "Multiple", image: Image(systemName: "figure.wave"), items: [
MultilineInfoView.Item(text: "hello", action: (Image(systemName: "chevron.forward"), {})),
MultilineInfoView.Item(text: "World", action: (Image(systemName: "chevron.forward"), {})),
])
.padding()
}
#Preview {
MultilineInfoView(title: "One", image: Image(systemName: "figure.wave"), items: ["Hello world."])
.padding()
}

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,18 +0,0 @@
import Foundation
import OSLog
import XPCWrappers
import SSHProtocolKit
import CertificateKit
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")
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

@@ -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-string</key>
<string>1</string>
<key>com.apple.security.hardened-process.hardened-heap</key>
<true/>
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
<string>2</string>
</dict>
</plist>

View File

@@ -1,56 +0,0 @@
#!/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 ""