This commit is contained in:
Max Goedjen 2025-12-14 11:54:53 -08:00
commit 780d18e2c6
No known key found for this signature in database
25 changed files with 158 additions and 45 deletions

View File

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

View File

@ -21,7 +21,7 @@ let package = Package(
targets: ["SmartCardSecretKit"]), targets: ["SmartCardSecretKit"]),
.library( .library(
name: "SecretAgentKit", name: "SecretAgentKit",
targets: ["SecretAgentKit", "XPCWrappers"]), targets: ["SecretAgentKit"]),
.library( .library(
name: "Common", name: "Common",
targets: ["Common"]), targets: ["Common"]),
@ -31,6 +31,9 @@ let package = Package(
.library( .library(
name: "XPCWrappers", name: "XPCWrappers",
targets: ["XPCWrappers"]), targets: ["XPCWrappers"]),
.library(
name: "SSHProtocolKit",
targets: ["SSHProtocolKit"]),
], ],
dependencies: [ dependencies: [
], ],
@ -60,7 +63,7 @@ let package = Package(
), ),
.target( .target(
name: "SecretAgentKit", name: "SecretAgentKit",
dependencies: ["SecretKit"], dependencies: ["SecretKit", "SSHProtocolKit", "Common"],
resources: [localization], resources: [localization],
swiftSettings: swiftSettings, swiftSettings: swiftSettings,
), ),
@ -68,15 +71,26 @@ let package = Package(
name: "SecretAgentKitTests", name: "SecretAgentKitTests",
dependencies: ["SecretAgentKit"], dependencies: ["SecretAgentKit"],
), ),
.target(
name: "SSHProtocolKit",
dependencies: ["SecretKit"],
resources: [localization],
swiftSettings: swiftSettings,
),
.testTarget(
name: "SSHProtocolKitTests",
dependencies: ["SSHProtocolKit"],
swiftSettings: swiftSettings,
),
.target( .target(
name: "Common", name: "Common",
dependencies: [], dependencies: ["SSHProtocolKit", "SecretKit"],
resources: [localization], resources: [localization],
swiftSettings: swiftSettings, swiftSettings: swiftSettings,
), ),
.target( .target(
name: "Brief", name: "Brief",
dependencies: ["XPCWrappers"], dependencies: ["XPCWrappers", "SSHProtocolKit"],
resources: [localization], resources: [localization],
swiftSettings: swiftSettings, swiftSettings: swiftSettings,
), ),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import CryptoKit
import OSLog import OSLog
import SecretKit import SecretKit
import AppKit 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. /// 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 { public final class Agent: Sendable {

View File

@ -1,11 +1,12 @@
import Foundation import Foundation
import OSLog import OSLog
import SecretKit import SecretKit
import SSHProtocolKit
/// Manages storage and lookup for OpenSSH certificates. /// Manages storage and lookup for OpenSSH certificates.
public actor OpenSSHCertificateHandler: Sendable { public actor OpenSSHCertificateHandler: Sendable {
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.homeDirectory) private let publicKeyFileStoreController = PublicKeyFileStoreController(directory: URL.publicKeyDirectory)
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "OpenSSHCertificateHandler") private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "OpenSSHCertificateHandler")
private let writer = OpenSSHPublicKeyWriter() private let writer = OpenSSHPublicKeyWriter()
private var keyBlobsAndNames: [AnySecret: (Data, Data)] = [:] private var keyBlobsAndNames: [AnySecret: (Data, Data)] = [:]

View File

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

View File

@ -1,11 +1,12 @@
import Foundation import Foundation
import OSLog import OSLog
import SecretKit import SecretKit
import SSHProtocolKit
public protocol SSHAgentInputParserProtocol { public protocol SSHAgentInputParserProtocol {
func parse(data: Data) async throws -> SSHAgent.Request func parse(data: Data) async throws -> SSHAgent.Request
} }
public struct SSHAgentInputParser: SSHAgentInputParserProtocol { public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
@ -13,7 +14,7 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "InputParser") private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "InputParser")
public init() { public init() {
} }
public func parse(data: Data) throws(AgentParsingError) -> SSHAgent.Request { public func parse(data: Data) throws(AgentParsingError) -> SSHAgent.Request {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,7 +22,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}() }()
private let updater = Updater(checkOnLaunch: true) private let updater = Updater(checkOnLaunch: true)
private let notifier = Notifier() private let notifier = Notifier()
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.homeDirectory) private let publicKeyFileStoreController = PublicKeyFileStoreController(directory: URL.publicKeyDirectory)
private lazy var agent: Agent = { private lazy var agent: Agent = {
Agent(storeList: storeList, witness: notifier) Agent(storeList: storeList, witness: notifier)
}() }()

View File

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

View File

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

View File

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */; }; 2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */; };
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; }; 50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
5002C3AB2EEF483300FFAD22 /* XPCWrappers in Frameworks */ = {isa = PBXBuildFile; productRef = 5002C3AA2EEF483300FFAD22 /* XPCWrappers */; };
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3A278005E800DF2006 /* SecretKit */; }; 5003EF3B278005E800DF2006 /* SecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3A278005E800DF2006 /* SecretKit */; };
5003EF3D278005F300DF2006 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3C278005F300DF2006 /* Brief */; }; 5003EF3D278005F300DF2006 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3C278005F300DF2006 /* Brief */; };
5003EF3F278005F300DF2006 /* SecretAgentKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3E278005F300DF2006 /* SecretAgentKit */; }; 5003EF3F278005F300DF2006 /* SecretAgentKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3E278005F300DF2006 /* SecretAgentKit */; };
@ -265,6 +266,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
5002C3AB2EEF483300FFAD22 /* XPCWrappers in Frameworks */,
50692E6C2E6FFA510043C7BB /* SecretAgentKit in Frameworks */, 50692E6C2E6FFA510043C7BB /* SecretAgentKit in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -539,6 +541,7 @@
name = SecretAgentInputParser; name = SecretAgentInputParser;
packageProductDependencies = ( packageProductDependencies = (
50692E6B2E6FFA510043C7BB /* SecretAgentKit */, 50692E6B2E6FFA510043C7BB /* SecretAgentKit */,
5002C3AA2EEF483300FFAD22 /* XPCWrappers */,
); );
productName = SecretAgentInputParser; productName = SecretAgentInputParser;
productReference = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */; productReference = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */;
@ -1499,6 +1502,10 @@
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
5002C3AA2EEF483300FFAD22 /* XPCWrappers */ = {
isa = XCSwiftPackageProductDependency;
productName = XPCWrappers;
};
5003EF3A278005E800DF2006 /* SecretKit */ = { 5003EF3A278005E800DF2006 /* SecretKit */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = SecretKit; productName = SecretKit;

View File

@ -14,7 +14,8 @@
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans> <TestPlans>
<TestPlanReference <TestPlanReference
reference = "container:Config/Secretive.xctestplan"> reference = "container:Config/Secretive.xctestplan"
default = "YES">
</TestPlanReference> </TestPlanReference>
</TestPlans> </TestPlans>
</TestAction> </TestAction>

View File

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

View File

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