mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-01-07 17:02:00 +01:00
Merge
This commit is contained in:
commit
780d18e2c6
@ -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,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
37
Sources/Packages/Sources/SSHProtocolKit/Data+Hex.swift
Normal file
37
Sources/Packages/Sources/SSHProtocolKit/Data+Hex.swift
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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 {
|
||||||
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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 {
|
||||||
@ -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 {
|
||||||
|
|||||||
@ -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)] = [:]
|
||||||
|
|||||||
@ -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 {
|
||||||
@ -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 {
|
||||||
|
|||||||
@ -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"))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
@testable import SecretAgentKit
|
import SSHProtocolKit
|
||||||
|
|
||||||
@Suite struct OpenSSHReaderTests {
|
@Suite struct OpenSSHReaderTests {
|
||||||
|
|
||||||
11
Sources/Packages/Tests/SSHProtocolKitTests/TestSecret.swift
Normal file
11
Sources/Packages/Tests/SSHProtocolKitTests/TestSecret.swift
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
import SSHProtocolKit
|
||||||
|
|
||||||
struct Stub {}
|
struct Stub {}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
}()
|
}()
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user