mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-10-08 04:00:57 +00:00
WIP
This commit is contained in:
parent
940b6b1b86
commit
a0cf74f9dc
@ -19,9 +19,12 @@ let package = Package(
|
|||||||
.library(
|
.library(
|
||||||
name: "SmartCardSecretKit",
|
name: "SmartCardSecretKit",
|
||||||
targets: ["SmartCardSecretKit"]),
|
targets: ["SmartCardSecretKit"]),
|
||||||
|
.library(
|
||||||
|
name: "CertificateKit",
|
||||||
|
targets: ["CertificateKit"]),
|
||||||
.library(
|
.library(
|
||||||
name: "SecretAgentKit",
|
name: "SecretAgentKit",
|
||||||
targets: ["SecretAgentKit", "XPCWrappers"]),
|
targets: ["SecretAgentKit"]),
|
||||||
.library(
|
.library(
|
||||||
name: "Brief",
|
name: "Brief",
|
||||||
targets: ["Brief"]),
|
targets: ["Brief"]),
|
||||||
@ -58,9 +61,15 @@ let package = Package(
|
|||||||
resources: [localization],
|
resources: [localization],
|
||||||
swiftSettings: swiftSettings,
|
swiftSettings: swiftSettings,
|
||||||
),
|
),
|
||||||
|
.target(
|
||||||
|
name: "CertificateKit",
|
||||||
|
dependencies: ["SecretKit"],
|
||||||
|
resources: [localization],
|
||||||
|
// swiftSettings: swiftSettings,
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "SecretAgentKit",
|
name: "SecretAgentKit",
|
||||||
dependencies: ["SecretKit", "SSHProtocolKit"],
|
dependencies: ["SecretKit", "SSHProtocolKit", "CertificateKit"],
|
||||||
resources: [localization],
|
resources: [localization],
|
||||||
swiftSettings: swiftSettings,
|
swiftSettings: swiftSettings,
|
||||||
),
|
),
|
||||||
@ -72,7 +81,7 @@ let package = Package(
|
|||||||
name: "SSHProtocolKit",
|
name: "SSHProtocolKit",
|
||||||
dependencies: ["SecretKit"],
|
dependencies: ["SecretKit"],
|
||||||
resources: [localization],
|
resources: [localization],
|
||||||
swiftSettings: swiftSettings,
|
// swiftSettings: swiftSettings,
|
||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "SSHProtocolKitTests",
|
name: "SSHProtocolKitTests",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import OSLog
|
import OSLog
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
public struct OpenSSHCertificate: Sendable, Codable, Equatable, Hashable, Identifiable, CustomDebugStringConvertible {
|
public struct OpenSSHCertificate: Sendable, Codable, Equatable, Hashable, Identifiable, CustomDebugStringConvertible {
|
||||||
|
|
||||||
@ -8,6 +9,12 @@ public struct OpenSSHCertificate: Sendable, Codable, Equatable, Hashable, Identi
|
|||||||
public let name: String?
|
public let name: String?
|
||||||
public let data: Data
|
public let data: Data
|
||||||
|
|
||||||
|
public var publicKey: Data
|
||||||
|
public var principals: [String]
|
||||||
|
public var keyID: String
|
||||||
|
public var serial: UInt64
|
||||||
|
public var validityRange: Range<Date>?
|
||||||
|
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
"OpenSSH Certificate \(name, default: "Unnamed"): \(data.formatted(.hex()))"
|
"OpenSSH Certificate \(name, default: "Unnamed"): \(data.formatted(.hex()))"
|
||||||
}
|
}
|
||||||
@ -54,7 +61,53 @@ public struct OpenSSHCertificateParser: OpenSSHCertificateParserProtocol, Sendab
|
|||||||
throw OpenSSHCertificateError.parsingFailed
|
throw OpenSSHCertificateError.parsingFailed
|
||||||
}
|
}
|
||||||
let name = elements.first
|
let name = elements.first
|
||||||
return OpenSSHCertificate(type: type, name: name, data: decoded)
|
do {
|
||||||
|
let dataParser = OpenSSHReader(data: decoded)
|
||||||
|
_ = try dataParser.readNextChunkAsString() // Redundant key type
|
||||||
|
_ = try dataParser.readNextChunk() // Nonce
|
||||||
|
_ = try dataParser.readNextChunkAsString() // curve
|
||||||
|
let publicKey = try dataParser.readNextChunk()
|
||||||
|
let serialNumber = try dataParser.readNextBytes(as: UInt64.self, convertEndianness: true)
|
||||||
|
let role = try dataParser.readNextBytes(as: UInt32.self, convertEndianness: true)
|
||||||
|
let keyIdentifier = try dataParser.readNextChunkAsString()
|
||||||
|
let principalsReader = try dataParser.readNextChunkAsSubReader()
|
||||||
|
var principals: [String] = []
|
||||||
|
while !principalsReader.done {
|
||||||
|
try principals.append(principalsReader.readNextChunkAsString())
|
||||||
|
}
|
||||||
|
let validAfter = try dataParser.readNextBytes(as: UInt64.self, convertEndianness: true)
|
||||||
|
let validBefore = try dataParser.readNextBytes(as: UInt64.self, convertEndianness: true)
|
||||||
|
let validityRange = Date(timeIntervalSince1970: TimeInterval(validAfter))..<Date(timeIntervalSince1970: TimeInterval(validBefore
|
||||||
|
))
|
||||||
|
let criticalOptionsReader = try dataParser.readNextChunkAsSubReader()
|
||||||
|
let extensionsReader = try dataParser.readNextChunkAsSubReader()
|
||||||
|
_ = try dataParser.readNextChunk() // reserved
|
||||||
|
let signatureKey = try dataParser.readNextChunk()
|
||||||
|
let signature = try dataParser.readNextChunk()
|
||||||
|
|
||||||
|
print(pkw(data: signatureKey), pkw(data: publicKey), pkw(data: signature))
|
||||||
|
|
||||||
|
|
||||||
|
return OpenSSHCertificate(
|
||||||
|
type: type,
|
||||||
|
name: name,
|
||||||
|
data: data,
|
||||||
|
publicKey: publicKey,
|
||||||
|
principals: principals,
|
||||||
|
keyID: keyIdentifier,
|
||||||
|
serial: serialNumber,
|
||||||
|
validityRange: validityRange
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
throw .parsingFailed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pkw(data: Data) -> String {
|
||||||
|
let base64 = Data(SHA256.hash(data: data)).base64EncodedString()
|
||||||
|
let paddingRange = base64.index(base64.endIndex, offsetBy: -2)..<base64.endIndex
|
||||||
|
let cleaned = base64.replacingOccurrences(of: "=", with: "", range: paddingRange)
|
||||||
|
return "SHA256:\(cleaned)"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import Foundation
|
|||||||
final class OpenSSHReader {
|
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.
|
||||||
@ -14,22 +15,28 @@ final class OpenSSHReader {
|
|||||||
/// 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 {
|
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 {
|
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 {
|
func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String {
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
|
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
|
||||||
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; };
|
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; };
|
||||||
505993512E7E59FB0092CFFA /* XPCCertificateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505993502E7E59F70092CFFA /* XPCCertificateParser.swift */; };
|
505993512E7E59FB0092CFFA /* XPCCertificateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505993502E7E59F70092CFFA /* XPCCertificateParser.swift */; };
|
||||||
|
505993532E7E70C90092CFFA /* CertificateKit in Frameworks */ = {isa = PBXBuildFile; productRef = 505993522E7E70C90092CFFA /* CertificateKit */; };
|
||||||
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
|
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
|
||||||
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; };
|
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; };
|
||||||
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; };
|
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; };
|
||||||
@ -273,6 +274,7 @@
|
|||||||
files = (
|
files = (
|
||||||
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */,
|
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */,
|
||||||
501421622781262300BBAA70 /* Brief in Frameworks */,
|
501421622781262300BBAA70 /* Brief in Frameworks */,
|
||||||
|
505993532E7E70C90092CFFA /* CertificateKit in Frameworks */,
|
||||||
5003EF5F2780081600DF2006 /* SecureEnclaveSecretKit in Frameworks */,
|
5003EF5F2780081600DF2006 /* SecureEnclaveSecretKit in Frameworks */,
|
||||||
5003EF612780081600DF2006 /* SmartCardSecretKit in Frameworks */,
|
5003EF612780081600DF2006 /* SmartCardSecretKit in Frameworks */,
|
||||||
);
|
);
|
||||||
@ -560,6 +562,7 @@
|
|||||||
5003EF5E2780081600DF2006 /* SecureEnclaveSecretKit */,
|
5003EF5E2780081600DF2006 /* SecureEnclaveSecretKit */,
|
||||||
5003EF602780081600DF2006 /* SmartCardSecretKit */,
|
5003EF602780081600DF2006 /* SmartCardSecretKit */,
|
||||||
501421612781262300BBAA70 /* Brief */,
|
501421612781262300BBAA70 /* Brief */,
|
||||||
|
505993522E7E70C90092CFFA /* CertificateKit */,
|
||||||
);
|
);
|
||||||
productName = Secretive;
|
productName = Secretive;
|
||||||
productReference = 50617D7F23FCE48E0099B055 /* Secretive.app */;
|
productReference = 50617D7F23FCE48E0099B055 /* Secretive.app */;
|
||||||
@ -1785,6 +1788,10 @@
|
|||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = Brief;
|
productName = Brief;
|
||||||
};
|
};
|
||||||
|
505993522E7E70C90092CFFA /* CertificateKit */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = CertificateKit;
|
||||||
|
};
|
||||||
50692D2C2E6FDC000043C7BB /* XPCWrappers */ = {
|
50692D2C2E6FDC000043C7BB /* XPCWrappers */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = XPCWrappers;
|
productName = XPCWrappers;
|
||||||
|
@ -3,6 +3,7 @@ import SecretKit
|
|||||||
import SecureEnclaveSecretKit
|
import SecureEnclaveSecretKit
|
||||||
import SmartCardSecretKit
|
import SmartCardSecretKit
|
||||||
import Brief
|
import Brief
|
||||||
|
import CertificateKit
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct Secretive: App {
|
struct Secretive: App {
|
||||||
@ -14,6 +15,7 @@ struct Secretive: App {
|
|||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ContentView()
|
||||||
.environment(EnvironmentValues._secretStoreList)
|
.environment(EnvironmentValues._secretStoreList)
|
||||||
|
.environment(EnvironmentValues._certificateStore)
|
||||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
||||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||||
guard hasRunSetup else { return }
|
guard hasRunSetup else { return }
|
||||||
@ -121,6 +123,8 @@ extension EnvironmentValues {
|
|||||||
return list
|
return list
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@MainActor fileprivate static let _certificateStore: CertificateStore = CertificateStore()
|
||||||
|
|
||||||
private static let _agentStatusChecker = AgentStatusChecker()
|
private static let _agentStatusChecker = AgentStatusChecker()
|
||||||
@Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker
|
@Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker
|
||||||
private static let _updater: any UpdaterProtocol = {
|
private static let _updater: any UpdaterProtocol = {
|
||||||
@ -135,6 +139,10 @@ extension EnvironmentValues {
|
|||||||
@MainActor var secretStoreList: SecretStoreList {
|
@MainActor var secretStoreList: SecretStoreList {
|
||||||
EnvironmentValues._secretStoreList
|
EnvironmentValues._secretStoreList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor var certificateStore: CertificateStore {
|
||||||
|
EnvironmentValues._certificateStore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FocusedValues {
|
extension FocusedValues {
|
||||||
|
@ -14,6 +14,7 @@ struct ContentView: View {
|
|||||||
@Environment(\.colorScheme) private var colorScheme
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
@Environment(\.openWindow) private var openWindow
|
@Environment(\.openWindow) private var openWindow
|
||||||
@Environment(\.secretStoreList) private var storeList
|
@Environment(\.secretStoreList) private var storeList
|
||||||
|
@Environment(\.certificateStore) private var certificateStore
|
||||||
@Environment(\.updater) private var updater
|
@Environment(\.updater) private var updater
|
||||||
@Environment(\.agentStatusChecker) private var agentStatusChecker
|
@Environment(\.agentStatusChecker) private var agentStatusChecker
|
||||||
|
|
||||||
@ -49,6 +50,12 @@ struct ContentView: View {
|
|||||||
let data = try! Data(contentsOf: url)
|
let data = try! Data(contentsOf: url)
|
||||||
let parser = try! await XPCCertificateParser()
|
let parser = try! await XPCCertificateParser()
|
||||||
let cert = try! await parser.parse(data: data)
|
let cert = try! await parser.parse(data: data)
|
||||||
|
let secret = storeList.allSecrets.first { secret in
|
||||||
|
secret.name == cert.name
|
||||||
|
}
|
||||||
|
guard let secret = secret ?? storeList.allSecrets.first else { return }
|
||||||
|
print(cert.data.formatted(.hex()))
|
||||||
|
certificateStore.saveCertificate(cert.data, for: secret)
|
||||||
print(cert)
|
print(cert)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
Loading…
Reference in New Issue
Block a user