mirror of
				https://github.com/maxgoedjen/secretive.git
				synced 2025-11-04 01:10:56 +00:00 
			
		
		
		
	WIP
This commit is contained in:
		
							parent
							
								
									940b6b1b86
								
							
						
					
					
						commit
						a0cf74f9dc
					
				@ -19,9 +19,12 @@ let package = Package(
 | 
			
		||||
        .library(
 | 
			
		||||
            name: "SmartCardSecretKit",
 | 
			
		||||
            targets: ["SmartCardSecretKit"]),
 | 
			
		||||
        .library(
 | 
			
		||||
            name: "CertificateKit",
 | 
			
		||||
            targets: ["CertificateKit"]),
 | 
			
		||||
        .library(
 | 
			
		||||
            name: "SecretAgentKit",
 | 
			
		||||
            targets: ["SecretAgentKit", "XPCWrappers"]),
 | 
			
		||||
            targets: ["SecretAgentKit"]),
 | 
			
		||||
        .library(
 | 
			
		||||
            name: "Brief",
 | 
			
		||||
            targets: ["Brief"]),
 | 
			
		||||
@ -58,9 +61,15 @@ let package = Package(
 | 
			
		||||
            resources: [localization],
 | 
			
		||||
            swiftSettings: swiftSettings,
 | 
			
		||||
        ),
 | 
			
		||||
        .target(
 | 
			
		||||
            name: "CertificateKit",
 | 
			
		||||
            dependencies: ["SecretKit"],
 | 
			
		||||
            resources: [localization],
 | 
			
		||||
//            swiftSettings: swiftSettings,
 | 
			
		||||
        ),
 | 
			
		||||
        .target(
 | 
			
		||||
            name: "SecretAgentKit",
 | 
			
		||||
            dependencies: ["SecretKit", "SSHProtocolKit"],
 | 
			
		||||
            dependencies: ["SecretKit", "SSHProtocolKit", "CertificateKit"],
 | 
			
		||||
            resources: [localization],
 | 
			
		||||
            swiftSettings: swiftSettings,
 | 
			
		||||
        ),
 | 
			
		||||
@ -72,7 +81,7 @@ let package = Package(
 | 
			
		||||
            name: "SSHProtocolKit",
 | 
			
		||||
            dependencies: ["SecretKit"],
 | 
			
		||||
            resources: [localization],
 | 
			
		||||
            swiftSettings: swiftSettings,
 | 
			
		||||
//            swiftSettings: swiftSettings,
 | 
			
		||||
        ),
 | 
			
		||||
        .testTarget(
 | 
			
		||||
            name: "SSHProtocolKitTests",
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import Foundation
 | 
			
		||||
import OSLog
 | 
			
		||||
import CryptoKit
 | 
			
		||||
 | 
			
		||||
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 data: Data
 | 
			
		||||
 | 
			
		||||
    public var publicKey: Data
 | 
			
		||||
    public var principals: [String]
 | 
			
		||||
    public var keyID: String
 | 
			
		||||
    public var serial: UInt64
 | 
			
		||||
    public var validityRange: Range<Date>?
 | 
			
		||||
 | 
			
		||||
    public var debugDescription: String {
 | 
			
		||||
        "OpenSSH Certificate \(name, default: "Unnamed"): \(data.formatted(.hex()))"
 | 
			
		||||
    }
 | 
			
		||||
@ -54,7 +61,53 @@ public struct OpenSSHCertificateParser: OpenSSHCertificateParserProtocol, Sendab
 | 
			
		||||
            throw OpenSSHCertificateError.parsingFailed
 | 
			
		||||
        }
 | 
			
		||||
        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 {
 | 
			
		||||
 | 
			
		||||
    var remaining: Data
 | 
			
		||||
    var done = false
 | 
			
		||||
 | 
			
		||||
    /// Initialize the reader with an OpenSSH data payload.
 | 
			
		||||
    /// - Parameter data: The data to read.
 | 
			
		||||
@ -14,22 +15,28 @@ final class OpenSSHReader {
 | 
			
		||||
    /// Reads the next chunk of data from the playload.
 | 
			
		||||
    /// - Returns: The next chunk of data.
 | 
			
		||||
    func readNextChunk(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> Data {
 | 
			
		||||
        let littleEndianLength = try readNextBytes(as: UInt32.self)
 | 
			
		||||
        let length = convertEndianness ? Int(littleEndianLength.bigEndian) : Int(littleEndianLength)
 | 
			
		||||
        let length = try readNextBytes(as: UInt32.self, convertEndianness: convertEndianness)
 | 
			
		||||
        guard remaining.count >= length else { throw .beyondBounds }
 | 
			
		||||
        let dataRange = 0..<length
 | 
			
		||||
        let dataRange = 0..<Int(length)
 | 
			
		||||
        let ret = Data(remaining[dataRange])
 | 
			
		||||
        remaining.removeSubrange(dataRange)
 | 
			
		||||
        if remaining.isEmpty {
 | 
			
		||||
            done = true
 | 
			
		||||
        }
 | 
			
		||||
        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
 | 
			
		||||
        guard remaining.count >= size else { throw .beyondBounds }
 | 
			
		||||
        let lengthRange = 0..<size
 | 
			
		||||
        let lengthChunk = remaining[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 {
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,7 @@
 | 
			
		||||
		50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
 | 
			
		||||
		50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.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 */; };
 | 
			
		||||
		50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; };
 | 
			
		||||
		50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; };
 | 
			
		||||
@ -273,6 +274,7 @@
 | 
			
		||||
			files = (
 | 
			
		||||
				5003EF3B278005E800DF2006 /* SecretKit in Frameworks */,
 | 
			
		||||
				501421622781262300BBAA70 /* Brief in Frameworks */,
 | 
			
		||||
				505993532E7E70C90092CFFA /* CertificateKit in Frameworks */,
 | 
			
		||||
				5003EF5F2780081600DF2006 /* SecureEnclaveSecretKit in Frameworks */,
 | 
			
		||||
				5003EF612780081600DF2006 /* SmartCardSecretKit in Frameworks */,
 | 
			
		||||
			);
 | 
			
		||||
@ -560,6 +562,7 @@
 | 
			
		||||
				5003EF5E2780081600DF2006 /* SecureEnclaveSecretKit */,
 | 
			
		||||
				5003EF602780081600DF2006 /* SmartCardSecretKit */,
 | 
			
		||||
				501421612781262300BBAA70 /* Brief */,
 | 
			
		||||
				505993522E7E70C90092CFFA /* CertificateKit */,
 | 
			
		||||
			);
 | 
			
		||||
			productName = Secretive;
 | 
			
		||||
			productReference = 50617D7F23FCE48E0099B055 /* Secretive.app */;
 | 
			
		||||
@ -1785,6 +1788,10 @@
 | 
			
		||||
			isa = XCSwiftPackageProductDependency;
 | 
			
		||||
			productName = Brief;
 | 
			
		||||
		};
 | 
			
		||||
		505993522E7E70C90092CFFA /* CertificateKit */ = {
 | 
			
		||||
			isa = XCSwiftPackageProductDependency;
 | 
			
		||||
			productName = CertificateKit;
 | 
			
		||||
		};
 | 
			
		||||
		50692D2C2E6FDC000043C7BB /* XPCWrappers */ = {
 | 
			
		||||
			isa = XCSwiftPackageProductDependency;
 | 
			
		||||
			productName = XPCWrappers;
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ import SecretKit
 | 
			
		||||
import SecureEnclaveSecretKit
 | 
			
		||||
import SmartCardSecretKit
 | 
			
		||||
import Brief
 | 
			
		||||
import CertificateKit
 | 
			
		||||
 | 
			
		||||
@main
 | 
			
		||||
struct Secretive: App {
 | 
			
		||||
@ -14,6 +15,7 @@ struct Secretive: App {
 | 
			
		||||
        WindowGroup {
 | 
			
		||||
            ContentView()
 | 
			
		||||
                .environment(EnvironmentValues._secretStoreList)
 | 
			
		||||
                .environment(EnvironmentValues._certificateStore)
 | 
			
		||||
                .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
 | 
			
		||||
                    @AppStorage("defaultsHasRunSetup") var hasRunSetup = false
 | 
			
		||||
                    guard hasRunSetup else { return }
 | 
			
		||||
@ -121,6 +123,8 @@ extension EnvironmentValues {
 | 
			
		||||
        return list
 | 
			
		||||
    }()
 | 
			
		||||
 | 
			
		||||
    @MainActor fileprivate static let _certificateStore: CertificateStore = CertificateStore()
 | 
			
		||||
 | 
			
		||||
    private static let _agentStatusChecker = AgentStatusChecker()
 | 
			
		||||
    @Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker
 | 
			
		||||
    private static let _updater: any UpdaterProtocol = {
 | 
			
		||||
@ -135,6 +139,10 @@ extension EnvironmentValues {
 | 
			
		||||
    @MainActor var secretStoreList: SecretStoreList {
 | 
			
		||||
        EnvironmentValues._secretStoreList
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @MainActor var certificateStore: CertificateStore {
 | 
			
		||||
        EnvironmentValues._certificateStore
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extension FocusedValues {
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,7 @@ struct ContentView: View {
 | 
			
		||||
    @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(\.agentStatusChecker) private var agentStatusChecker
 | 
			
		||||
 | 
			
		||||
@ -49,6 +50,12 @@ struct ContentView: View {
 | 
			
		||||
                let data = try! Data(contentsOf: url)
 | 
			
		||||
                let parser = try! await XPCCertificateParser()
 | 
			
		||||
                let cert = try! await parser.parse(data: data)
 | 
			
		||||
                let secret = storeList.allSecrets.first { secret in
 | 
			
		||||
                    secret.name == cert.name
 | 
			
		||||
                }
 | 
			
		||||
                guard let secret = secret ?? storeList.allSecrets.first else { return }
 | 
			
		||||
                print(cert.data.formatted(.hex()))
 | 
			
		||||
                certificateStore.saveCertificate(cert.data, for: secret)
 | 
			
		||||
                print(cert)
 | 
			
		||||
            }
 | 
			
		||||
            return true
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user