Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b48fee7
Add test certificate generation and loading utilities
nedithgar Jul 31, 2025
fcf3636
Update test certificates for various algorithms
nedithgar Jul 31, 2025
7166b9f
feat: implement ASN.1 DER encoding for ECDSA signatures
nedithgar Jul 31, 2025
6c34a29
feat: enhance SSH certificate constraints validation and error handling
nedithgar Jul 31, 2025
178193c
feat: implement CA key verification and enhance certificate signature…
nedithgar Jul 31, 2025
c402859
Add CIDRMatcher for IPv4 and IPv6 support, implement PatternMatcher f…
nedithgar Jul 31, 2025
b89e936
feat: enhance principal validation to allow optional empty principals
nedithgar Jul 31, 2025
ea52c85
feat: read nonce as the first field after key type in SSH certificate…
nedithgar Jul 31, 2025
b4cf895
feat: add signature type extraction and validation for SSH certificates
nedithgar Jul 31, 2025
c6b765d
fix: update original buffer handling for signature verification in SS…
nedithgar Jul 31, 2025
a6981df
feat: add RSA key length validation and tests for SSH certificates
nedithgar Jul 31, 2025
152dd38
feat: implement principal limit and no-touch-required extension handl…
nedithgar Aug 1, 2025
c0c9d27
Refactor SSH Certificate Tests to Use NIOSSH for Certificate Parsing
nedithgar Aug 1, 2025
f586b49
Remove test certificates and associated scripts
nedithgar Aug 1, 2025
ea1d28b
refactor: make IP address validation cross-platform and improve code …
nedithgar Aug 1, 2025
1b8800d
build: remove TestCertificates resource reference from Package.swift
nedithgar Aug 1, 2025
e1feb23
refactor: improve IP address validation using getaddrinfo for robustness
nedithgar Aug 1, 2025
2845370
refactor: remove deprecated certificate tests and update to NIOSSH in…
nedithgar Aug 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
549 changes: 0 additions & 549 deletions Sources/Citadel/Algorithms/ECDSACertificate.swift

This file was deleted.

168 changes: 2 additions & 166 deletions Sources/Citadel/Algorithms/Ed25519.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,169 +3,5 @@ import Crypto
import NIO
import NIOSSH

public enum Ed25519 {

// MARK: - Ed25519 Certificate Public Key Type

/// Ed25519 certificate public key
public final class CertificatePublicKey: NIOSSHPublicKeyProtocol, Equatable, Hashable {
/// SSH certificate type identifier
public static let publicKeyPrefix = "ssh-ed25519-cert-v01@openssh.com"

/// The underlying Ed25519 public key
public let publicKey: Curve25519.Signing.PublicKey

/// The certificate data
public let certificate: SSHCertificate

/// The original certificate data (for serialization)
private let originalCertificateData: Data

/// The raw representation of the public key
public var rawRepresentation: Data {
publicKey.rawRepresentation
}

/// Initialize from raw certificate data
public init(certificateData: Data) throws {
self.originalCertificateData = certificateData
self.certificate = try SSHCertificate(from: certificateData, expectedKeyType: Self.publicKeyPrefix)

// Extract the public key from the certificate
guard let publicKeyData = certificate.publicKey else {
throw SSHCertificateError.missingPublicKey
}

self.publicKey = try Curve25519.Signing.PublicKey(rawRepresentation: publicKeyData)
}

/// Initialize from certificate and public key
public init(certificate: SSHCertificate, publicKey: Curve25519.Signing.PublicKey) {
self.certificate = certificate
self.publicKey = publicKey
// When initialized this way, we need to serialize the certificate
self.originalCertificateData = Data()
}

// MARK: - NIOSSHPublicKeyProtocol conformance

public static func read(from buffer: inout ByteBuffer) throws -> CertificatePublicKey {
// Save the entire certificate blob for later use
let startIndex = buffer.readerIndex

// Skip the key type string
guard let keyType = buffer.readSSHString() else {
throw SSHCertificateError.invalidCertificateType
}

guard keyType == publicKeyPrefix else {
throw SSHCertificateError.invalidCertificateType
}

// Read the entire certificate
buffer.moveReaderIndex(to: startIndex)
let certificateLength = buffer.readableBytes
guard let certificateBytes = buffer.readBytes(length: certificateLength) else {
throw SSHCertificateError.invalidCertificateType
}
let certificateData = Data(certificateBytes)

return try CertificatePublicKey(certificateData: certificateData)
}

public func write(to buffer: inout ByteBuffer) -> Int {
// If we have the original certificate data, use it directly
if !originalCertificateData.isEmpty {
return buffer.writeData(originalCertificateData)
}

// Otherwise, serialize the certificate from its components
var certBuffer = ByteBufferAllocator().buffer(capacity: 1024)

// Write key type
certBuffer.writeSSHString(CertificatePublicKey.publicKeyPrefix)

// Write nonce
certBuffer.writeSSHData(certificate.nonce)

// Write public key
certBuffer.writeSSHData(publicKey.rawRepresentation)

// Write certificate fields
certBuffer.writeInteger(certificate.serial)
certBuffer.writeInteger(certificate.type)
certBuffer.writeSSHString(certificate.keyId)

// Write valid principals
var principalsBuffer = ByteBufferAllocator().buffer(capacity: 512)
for principal in certificate.validPrincipals {
principalsBuffer.writeSSHString(principal)
}
certBuffer.writeSSHString(Data(principalsBuffer.readableBytesView))

// Write validity period
certBuffer.writeInteger(certificate.validAfter)
certBuffer.writeInteger(certificate.validBefore)

// Write critical options
var criticalOptionsBuffer = ByteBufferAllocator().buffer(capacity: 512)
for (name, value) in certificate.criticalOptions {
criticalOptionsBuffer.writeSSHString(name)
criticalOptionsBuffer.writeSSHData(value)
}
certBuffer.writeSSHString(Data(criticalOptionsBuffer.readableBytesView))

// Write extensions
var extensionsBuffer = ByteBufferAllocator().buffer(capacity: 512)
for (name, value) in certificate.extensions {
extensionsBuffer.writeSSHString(name)
extensionsBuffer.writeSSHData(value)
}
certBuffer.writeSSHString(Data(extensionsBuffer.readableBytesView))

// Write reserved
certBuffer.writeSSHData(certificate.reserved)

// Write signature key
certBuffer.writeSSHData(certificate.signatureKey)

// Write signature
certBuffer.writeSSHData(certificate.signature)

// Write the complete certificate to the output buffer
return buffer.writeBuffer(&certBuffer)
}

public static func == (lhs: CertificatePublicKey, rhs: CertificatePublicKey) -> Bool {
lhs.publicKey.rawRepresentation == rhs.publicKey.rawRepresentation &&
lhs.certificate.serial == rhs.certificate.serial
}

public func hash(into hasher: inout Hasher) {
hasher.combine(publicKey.rawRepresentation)
hasher.combine(certificate.serial)
}

public func isValidSignature<D: DataProtocol>(_ signature: NIOSSHSignatureProtocol, for data: D) -> Bool {
// Ed25519 certificates use the same signature validation as regular Ed25519 keys
// The signature should be an Ed25519 signature
let signatureBytes = signature.rawRepresentation

// Parse the signature format (algorithm name + signature data)
var signatureBuffer = ByteBuffer(data: signatureBytes)
guard let algorithm = signatureBuffer.readSSHString(),
algorithm == "ssh-ed25519" else {
return false
}

guard let signatureData = signatureBuffer.readSSHData(),
signatureData.count == 64 else { // Ed25519 signatures are always 64 bytes
return false
}

// Verify using Curve25519.Signing.PublicKey
return publicKey.isValidSignature(signatureData, for: data)
}
}
}

// Ed25519 functionality is now provided directly by NIOSSH
// Use NIOSSHCertifiedPublicKey for certificate support
Loading