Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ SwiftNIO SSH supports SSHv2 with the following feature set:

- All session channel features, including shell and exec channel requests
- Direct and reverse TCP port forwarding
- Modern cryptographic primitives only: Ed25519 and EDCSA over P256 for asymmetric cryptography, AES-GCM for symmetric cryptography, x25519 for key exchange
- Modern cryptographic primitives only: Ed25519 and EDCSA over the major NIST curves (P256, P384, P521) for asymmetric cryptography, AES-GCM for symmetric cryptography, x25519 for key exchange
- Password and public key user authentication
- Supports all platforms supported by SwiftNIO and Swift Crypto

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ extension SSHKeyExchangeStateMachine {
static let supportedKeyExchangeAlgorithms: [Substring] = ["curve25519-sha256", "[email protected]"]

/// All known host key algorithms.
static let supportedServerHostKeyAlgorithms: [Substring] = ["ssh-ed25519", "ecdsa-sha2-nistp256"]
static let supportedServerHostKeyAlgorithms: [Substring] = ["ssh-ed25519", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp521"]
}

extension SSHKeyExchangeStateMachine {
Expand Down
34 changes: 34 additions & 0 deletions Sources/NIOSSH/Keys And Signatures/NIOSSHPrivateKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ public struct NIOSSHPrivateKey {
self.backingKey = .ecdsaP256(key)
}

public init(p384Key key: P384.Signing.PrivateKey) {
self.backingKey = .ecdsaP384(key)
}

public init(p521Key key: P521.Signing.PrivateKey) {
self.backingKey = .ecdsaP521(key)
}

#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
public init(secureEnclaveP256Key key: SecureEnclave.P256.Signing.PrivateKey) {
self.backingKey = .secureEnclaveP256(key)
Expand All @@ -51,6 +59,10 @@ public struct NIOSSHPrivateKey {
return ["ssh-ed25519"]
case .ecdsaP256:
return ["ecdsa-sha2-nistp256"]
case .ecdsaP384:
return ["ecdsa-sha2-nistp384"]
case .ecdsaP521:
return ["ecdsa-sha2-nistp521"]
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
case .secureEnclaveP256:
return ["ecdsa-sha2-nistp256"]
Expand All @@ -64,6 +76,8 @@ extension NIOSSHPrivateKey {
internal enum BackingKey {
case ed25519(Curve25519.Signing.PrivateKey)
case ecdsaP256(P256.Signing.PrivateKey)
case ecdsaP384(P384.Signing.PrivateKey)
case ecdsaP521(P521.Signing.PrivateKey)

#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
case secureEnclaveP256(SecureEnclave.P256.Signing.PrivateKey)
Expand All @@ -84,6 +98,16 @@ extension NIOSSHPrivateKey {
try key.signature(for: ptr)
}
return SSHSignature(backingSignature: .ecdsaP256(signature))
case .ecdsaP384(let key):
let signature = try digest.withUnsafeBytes { ptr in
try key.signature(for: ptr)
}
return SSHSignature(backingSignature: .ecdsaP384(signature))
case .ecdsaP521(let key):
let signature = try digest.withUnsafeBytes { ptr in
try key.signature(for: ptr)
}
return SSHSignature(backingSignature: .ecdsaP521(signature))

#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
case .secureEnclaveP256(let key):
Expand All @@ -103,6 +127,12 @@ extension NIOSSHPrivateKey {
case .ecdsaP256(let key):
let signature = try key.signature(for: payload.bytes.readableBytesView)
return SSHSignature(backingSignature: .ecdsaP256(signature))
case .ecdsaP384(let key):
let signature = try key.signature(for: payload.bytes.readableBytesView)
return SSHSignature(backingSignature: .ecdsaP384(signature))
case .ecdsaP521(let key):
let signature = try key.signature(for: payload.bytes.readableBytesView)
return SSHSignature(backingSignature: .ecdsaP521(signature))
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
case .secureEnclaveP256(let key):
let signature = try key.signature(for: payload.bytes.readableBytesView)
Expand All @@ -120,6 +150,10 @@ extension NIOSSHPrivateKey {
return NIOSSHPublicKey(backingKey: .ed25519(privateKey.publicKey))
case .ecdsaP256(let privateKey):
return NIOSSHPublicKey(backingKey: .ecdsaP256(privateKey.publicKey))
case .ecdsaP384(let privateKey):
return NIOSSHPublicKey(backingKey: .ecdsaP384(privateKey.publicKey))
case .ecdsaP521(let privateKey):
return NIOSSHPublicKey(backingKey: .ecdsaP521(privateKey.publicKey))
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
case .secureEnclaveP256(let privateKey):
return NIOSSHPublicKey(backingKey: .ecdsaP256(privateKey.publicKey))
Expand Down
123 changes: 119 additions & 4 deletions Sources/NIOSSH/Keys And Signatures/NIOSSHPublicKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,18 @@ extension NIOSSHPublicKey {
return digest.withUnsafeBytes { digestPtr in
key.isValidSignature(sig, for: digestPtr)
}
case (.ed25519, .ecdsaP256), (.ecdsaP256, .ed25519):
case (.ecdsaP384(let key), .ecdsaP384(let sig)):
return digest.withUnsafeBytes { digestPtr in
key.isValidSignature(sig, for: digestPtr)
}
case (.ecdsaP521(let key), .ecdsaP521(let sig)):
return digest.withUnsafeBytes { digestPtr in
key.isValidSignature(sig, for: digestPtr)
}
case (.ed25519, _),
(.ecdsaP256, _),
(.ecdsaP384, _),
(.ecdsaP521, _):
return false
}
}
Expand All @@ -62,7 +73,14 @@ extension NIOSSHPublicKey {
return key.isValidSignature(sig, for: payload.bytes.readableBytesView)
case (.ecdsaP256(let key), .ecdsaP256(let sig)):
return key.isValidSignature(sig, for: payload.bytes.readableBytesView)
case (.ed25519, .ecdsaP256), (.ecdsaP256, .ed25519):
case (.ecdsaP384(let key), .ecdsaP384(let sig)):
return key.isValidSignature(sig, for: payload.bytes.readableBytesView)
case (.ecdsaP521(let key), .ecdsaP521(let sig)):
return key.isValidSignature(sig, for: payload.bytes.readableBytesView)
case (.ed25519, _),
(.ecdsaP256, _),
(.ecdsaP384, _),
(.ecdsaP521, _):
return false
}
}
Expand All @@ -73,6 +91,8 @@ extension NIOSSHPublicKey {
internal enum BackingKey {
case ed25519(Curve25519.Signing.PublicKey)
case ecdsaP256(P256.Signing.PublicKey)
case ecdsaP384(P384.Signing.PublicKey)
case ecdsaP521(P521.Signing.PublicKey)
}

/// The prefix of an Ed25519 public key.
Expand All @@ -81,17 +101,27 @@ extension NIOSSHPublicKey {
/// The prefix of a P256 ECDSA public key.
fileprivate static let ecdsaP256PublicKeyPrefix = "ecdsa-sha2-nistp256".utf8

/// The prefix of a P384 ECDSA public key.
fileprivate static let ecdsaP384PublicKeyPrefix = "ecdsa-sha2-nistp384".utf8

/// The prefix of a P521 ECDSA public key.
fileprivate static let ecdsaP521PublicKeyPrefix = "ecdsa-sha2-nistp521".utf8

internal var keyPrefix: String.UTF8View {
switch self.backingKey {
case .ed25519:
return Self.ed25519PublicKeyPrefix
case .ecdsaP256:
return Self.ecdsaP256PublicKeyPrefix
case .ecdsaP384:
return Self.ecdsaP384PublicKeyPrefix
case .ecdsaP521:
return Self.ecdsaP521PublicKeyPrefix
}
}

internal static var knownAlgorithms: [String.UTF8View] {
[Self.ed25519PublicKeyPrefix, Self.ecdsaP256PublicKeyPrefix]
[Self.ed25519PublicKeyPrefix, Self.ecdsaP384PublicKeyPrefix, Self.ecdsaP256PublicKeyPrefix, Self.ecdsaP521PublicKeyPrefix]
}
}

Expand All @@ -103,7 +133,14 @@ extension NIOSSHPublicKey.BackingKey: Equatable {
return lhs.rawRepresentation == rhs.rawRepresentation
case (.ecdsaP256(let lhs), .ecdsaP256(let rhs)):
return lhs.rawRepresentation == rhs.rawRepresentation
case (.ed25519, .ecdsaP256), (.ecdsaP256, .ed25519):
case (.ecdsaP384(let lhs), .ecdsaP384(let rhs)):
return lhs.rawRepresentation == rhs.rawRepresentation
case (.ecdsaP521(let lhs), .ecdsaP521(let rhs)):
return lhs.rawRepresentation == rhs.rawRepresentation
case (.ed25519, _),
(.ecdsaP256, _),
(.ecdsaP384, _),
(.ecdsaP521, _):
return false
}
}
Expand All @@ -118,6 +155,12 @@ extension NIOSSHPublicKey.BackingKey: Hashable {
case .ecdsaP256(let pkey):
hasher.combine(2)
hasher.combine(pkey.rawRepresentation)
case .ecdsaP384(let pkey):
hasher.combine(3)
hasher.combine(pkey.rawRepresentation)
case .ecdsaP521(let pkey):
hasher.combine(4)
hasher.combine(pkey.rawRepresentation)
}
}
}
Expand All @@ -131,6 +174,10 @@ extension ByteBuffer {
return self.writeEd25519PublicKey(baseKey: key)
case .ecdsaP256(let key):
return self.writeECDSAP256PublicKey(baseKey: key)
case .ecdsaP384(let key):
return self.writeECDSAP384PublicKey(baseKey: key)
case .ecdsaP521(let key):
return self.writeECDSAP521PublicKey(baseKey: key)
}
}

Expand All @@ -147,6 +194,10 @@ extension ByteBuffer {
return try buffer.readEd25519PublicKey()
} else if bytesView.elementsEqual(NIOSSHPublicKey.ecdsaP256PublicKeyPrefix) {
return try buffer.readECDSAP256PublicKey()
} else if bytesView.elementsEqual(NIOSSHPublicKey.ecdsaP384PublicKeyPrefix) {
return try buffer.readECDSAP384PublicKey()
} else if bytesView.elementsEqual(NIOSSHPublicKey.ecdsaP521PublicKeyPrefix) {
return try buffer.readECDSAP521PublicKey()
} else {
// We don't know this public key type.
let unexpectedAlgorithm = keyIdentifierBytes.readString(length: keyIdentifierBytes.readableBytes) ?? "<unknown algorithm>"
Expand All @@ -171,6 +222,24 @@ extension ByteBuffer {
return writtenBytes
}

private mutating func writeECDSAP384PublicKey(baseKey: P384.Signing.PublicKey) -> Int {
// For ECDSA-P384, the key format is the key prefix, then the string "nistp384", followed by the
// the public point Q.
var writtenBytes = self.writeSSHString(NIOSSHPublicKey.ecdsaP384PublicKeyPrefix)
writtenBytes += self.writeSSHString("nistp384".utf8)
writtenBytes += self.writeSSHString(baseKey.x963Representation)
return writtenBytes
}

private mutating func writeECDSAP521PublicKey(baseKey: P521.Signing.PublicKey) -> Int {
// For ECDSA-P521, the key format is the key prefix, then the string "nistp521", followed by the
// the public point Q.
var writtenBytes = self.writeSSHString(NIOSSHPublicKey.ecdsaP521PublicKeyPrefix)
writtenBytes += self.writeSSHString("nistp521".utf8)
writtenBytes += self.writeSSHString(baseKey.x963Representation)
return writtenBytes
}

/// A helper function that reads an Ed25519 public key.
///
/// Not safe to call from arbitrary code as this does not return the reader index on failure: it relies on the caller performing
Expand Down Expand Up @@ -208,6 +277,52 @@ extension ByteBuffer {
return NIOSSHPublicKey(backingKey: .ecdsaP256(key))
}

/// A helper function that reads an ECDSA P-384 public key.
///
/// Not safe to call from arbitrary code as this does not return the reader index on failure: it relies on the caller performing
/// the rewind.
private mutating func readECDSAP384PublicKey() throws -> NIOSSHPublicKey? {
// For ECDSA-P384, the key format is the string "nistp384" followed by the
// the public point Q.
guard var domainParameter = self.readSSHString() else {
return nil
}
guard domainParameter.readableBytesView.elementsEqual("nistp384".utf8) else {
let unexpectedParameter = domainParameter.readString(length: domainParameter.readableBytes) ?? "<unknown domain parameter>"
throw NIOSSHError.invalidDomainParametersForKey(parameters: unexpectedParameter)
}

guard let qBytes = self.readSSHString() else {
return nil
}

let key = try P384.Signing.PublicKey(x963Representation: qBytes.readableBytesView)
return NIOSSHPublicKey(backingKey: .ecdsaP384(key))
}

/// A helper function that reads an ECDSA P-521 public key.
///
/// Not safe to call from arbitrary code as this does not return the reader index on failure: it relies on the caller performing
/// the rewind.
private mutating func readECDSAP521PublicKey() throws -> NIOSSHPublicKey? {
// For ECDSA-P521, the key format is the string "nistp521" followed by the
// the public point Q.
guard var domainParameter = self.readSSHString() else {
return nil
}
guard domainParameter.readableBytesView.elementsEqual("nistp521".utf8) else {
let unexpectedParameter = domainParameter.readString(length: domainParameter.readableBytes) ?? "<unknown domain parameter>"
throw NIOSSHError.invalidDomainParametersForKey(parameters: unexpectedParameter)
}

guard let qBytes = self.readSSHString() else {
return nil
}

let key = try P521.Signing.PublicKey(x963Representation: qBytes.readableBytesView)
return NIOSSHPublicKey(backingKey: .ecdsaP521(key))
}

/// A helper function for complex readers that will reset a buffer on nil or on error, as though the read
/// never occurred.
internal mutating func rewindOnNilOrError<T>(_ body: (inout ByteBuffer) throws -> T?) rethrows -> T? {
Expand Down
Loading