This commit is contained in:
Max Goedjen 2025-08-24 14:53:24 -07:00
parent 698b7571c8
commit e08d6661e5
No known key found for this signature in database
3 changed files with 35 additions and 73 deletions

View File

@ -136,42 +136,7 @@ extension Agent {
let dataToSign = reader.readNextChunk() let dataToSign = reader.readNextChunk()
let rawRepresentation = try await store.sign(data: dataToSign, with: secret, for: provenance) let rawRepresentation = try await store.sign(data: dataToSign, with: secret, for: provenance)
let signedData = signatureWriter.data(secret: secret, signature: rawRepresentation)
let curveData = publicKeyWriter.openSSHIdentifier(for: secret.keyType)
let signedData: Data
if secret.keyType.algorithm == .ecdsa {
let rawLength = rawRepresentation.count/2
// Check if we need to pad with 0x00 to prevent certain
// ssh servers from thinking r or s is negative
let paddingRange: ClosedRange<UInt8> = 0x80...0xFF
var r = Data(rawRepresentation[0..<rawLength])
if paddingRange ~= r.first! {
r.insert(0x00, at: 0)
}
var s = Data(rawRepresentation[rawLength...])
if paddingRange ~= s.first! {
s.insert(0x00, at: 0)
}
var signatureChunk = Data()
signatureChunk.append(r.lengthAndData)
signatureChunk.append(s.lengthAndData)
var mutSignedData = Data()
var sub = Data()
sub.append(curveData.lengthAndData)
sub.append(signatureChunk.lengthAndData)
mutSignedData.append(sub.lengthAndData)
signedData = mutSignedData
} else {
var mutSignedData = Data()
var sub = Data()
sub.append("rsa-sha2-512".lengthAndData)
sub.append(rawRepresentation.lengthAndData)
mutSignedData.append(sub.lengthAndData)
signedData = mutSignedData
}
if let witness = witness { if let witness = witness {
try await witness.witness(accessTo: secret, from: store, by: provenance) try await witness.witness(accessTo: secret, from: store, by: provenance)

View File

@ -14,56 +14,53 @@ public struct OpenSSHSignatureWriter: Sendable {
switch secret.keyType.algorithm { switch secret.keyType.algorithm {
case .ecdsa: case .ecdsa:
// https://datatracker.ietf.org/doc/html/rfc5656#section-3.1 // https://datatracker.ietf.org/doc/html/rfc5656#section-3.1
fatalError() ecdsaSignature(signature, keyType: secret.keyType)
case .mldsa: case .mldsa:
// https://www.ietf.org/archive/id/draft-sfluhrer-ssh-mldsa-04.txt // https://www.ietf.org/archive/id/draft-sfluhrer-ssh-mldsa-04.txt
fatalError() fatalError()
case .rsa: case .rsa:
// https://datatracker.ietf.org/doc/html/rfc4253#section-6.6 // https://datatracker.ietf.org/doc/html/rfc4253#section-6.6
fatalError() rsaSignature(signature)
} }
} }
} }
extension OpenSSHSignatureWriter { extension OpenSSHSignatureWriter {
func ecdsaSignature(_ rawRepresentation: Data, keyType: KeyType) -> Data {
/// The fully qualified OpenSSH identifier for the algorithm. let rawLength = rawRepresentation.count/2
/// - Parameters: // Check if we need to pad with 0x00 to prevent certain
/// - algorithm: The algorithm to identify. // ssh servers from thinking r or s is negative
/// - length: The key length of the algorithm. let paddingRange: ClosedRange<UInt8> = 0x80...0xFF
/// - Returns: The OpenSSH identifier for the algorithm. var r = Data(rawRepresentation[0..<rawLength])
public func openSSHIdentifier(for keyType: KeyType) -> String { if paddingRange ~= r.first! {
switch (keyType.algorithm, keyType.size) { r.insert(0x00, at: 0)
case (.ecdsa, 256), (.ecdsa, 384):
"ecdsa-sha2-nistp" + String(describing: keyType.size)
case (.mldsa, 65), (.mldsa, 87):
"ssh-mldsa-" + String(describing: keyType.size)
case (.rsa, _):
"ssh-rsa"
default:
"unknown"
} }
} var s = Data(rawRepresentation[rawLength...])
if paddingRange ~= s.first! {
} s.insert(0x00, at: 0)
}
extension OpenSSHSignatureWriter {
var signatureChunk = Data()
public func rsaPublicKeyBlob<SecretType: Secret>(secret: SecretType) -> Data { signatureChunk.append(r.lengthAndData)
// Cheap way to pull out e and n as defined in https://datatracker.ietf.org/doc/html/rfc4253 signatureChunk.append(s.lengthAndData)
// Keychain stores it as a thin ASN.1 wrapper with this format: var mutSignedData = Data()
// [4 byte prefix][2 byte prefix][n][2 byte prefix][e] var sub = Data()
// Rather than parse out the whole ASN.1 blob, we'll cheat and pull values directly since sub.append(OpenSSHPublicKeyWriter().openSSHIdentifier(for: keyType).lengthAndData)
// we only support one key type, and the keychain always gives it in a specific format. sub.append(signatureChunk.lengthAndData)
let keySize = secret.keyType.size mutSignedData.append(sub.lengthAndData)
guard secret.keyType.algorithm == .rsa && keySize == 2048 else { fatalError() } return mutSignedData
let length = secret.keyType.size/8 }
let data = secret.publicKey
let n = Data(data[8..<(9+length)]) func rsaSignature(_ rawRepresentation: Data) -> Data {
let e = Data(data[(2+9+length)...]) var mutSignedData = Data()
return e.lengthAndData + n.lengthAndData var sub = Data()
sub.append("rsa-sha2-512".lengthAndData)
sub.append(rawRepresentation.lengthAndData)
mutSignedData.append(sub.lengthAndData)
return mutSignedData
} }
} }