This commit is contained in:
Max Goedjen
2025-09-20 17:43:43 -07:00
parent 940b6b1b86
commit a0cf74f9dc
6 changed files with 100 additions and 9 deletions

View File

@@ -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",

View File

@@ -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)"
}
}

View File

@@ -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 {