-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for custom cryptography #62
Open
Joannis
wants to merge
29
commits into
apple:main
Choose a base branch
from
Joannis:jo-rsa-private-keys
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 17 commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
620d931
Initial client-side RSA private key support
Joannis cc00081
Implement RSA in a separate module, to be removed before a merge
Joannis 17f5cad
Removed RSA, added a custom key test
Joannis f0e31b2
Merge branch 'main' into jo-rsa-private-keys
Joannis 077e954
Add docs
Joannis fc5f565
Merge branch 'jo-rsa-private-keys' of github.com:joannis/swift-nio-ss…
Joannis 497919b
Merge branch 'main' into jo-rsa-private-keys
Joannis a742b44
Merge branch 'main' into jo-rsa-private-keys
Joannis 4ecf937
Support custom public key types for host keys
Joannis 5852c0c
More transport options
Joannis 569c03e
Implemented passing sequence numbers, and adapter protocols so that o…
Joannis 3837545
Ignore the authentication banner
Joannis 316c8d6
Enable old algorithms
Joannis ee4b862
Merge remote-tracking branch 'apple/main' into jo-rsa-private-keys
Joannis 241b456
Merge remote-tracking branch 'apple/main' into jo-rsa-private-keys
Joannis d8c64fa
Remove conflicts with PR #98
Joannis a50df12
Fixed broken tests after merge. Added tests for all algorithms using …
Joannis 3f42d1f
Merge branch 'apple:main' into jo-rsa-private-keys
gwynne f4efdbc
Define transport protection & key exchange types on the client/server…
Joannis 642d961
Remove whitespace
Joannis c5ccb88
Fix typo
gwynne a4f1a81
Address some of the PR feedback
gwynne c66f0f1
Merge branch 'main' into jo-rsa-private-keys
gwynne df0b47e
Remove defaulted parameters per PR feedback and fix a pile of broken …
gwynne f8b8add
Encapsulate globals in an enum per PR feedback
gwynne b66b64f
Use fine-grained locking per PR feedback
gwynne d1fc273
Address soundness.sh issues with correct version of swiftformat.
gwynne 4b0e7ec
Fix copyright header year
gwynne 86a99b1
Merge branch 'apple:main' into jo-rsa-private-keys
JaapWijnen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,7 +60,7 @@ struct SSHConnectionStateMachine { | |
/// The state of this state machine. | ||
private var state: State | ||
|
||
private static let defaultTransportProtectionSchemes: [NIOSSHTransportProtection.Type] = [ | ||
internal static var defaultTransportProtectionSchemes: [NIOSSHTransportProtection.Type] = [ | ||
AES256GCMOpenSSHTransportProtection.self, AES128GCMOpenSSHTransportProtection.self, | ||
] | ||
|
||
|
@@ -181,6 +181,7 @@ struct SSHConnectionStateMachine { | |
return .noMessage | ||
case .unimplemented(let unimplemented): | ||
throw NIOSSHError.remotePeerDoesNotSupportMessage(unimplemented) | ||
|
||
default: | ||
// TODO: enforce RFC 4253: | ||
// | ||
|
@@ -305,7 +306,7 @@ struct SSHConnectionStateMachine { | |
let result = try state.receiveUserAuthRequest(message) | ||
self.state = .userAuthentication(state) | ||
return result | ||
|
||
case .userAuthSuccess: | ||
let result = try state.receiveUserAuthSuccess() | ||
// Hey, auth succeeded! | ||
|
@@ -818,7 +819,7 @@ struct SSHConnectionStateMachine { | |
case .userAuthRequest(let message): | ||
try state.writeUserAuthRequest(message, into: &buffer) | ||
self.state = .userAuthentication(state) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we remove the whitespace changes in this file? |
||
case .userAuthSuccess: | ||
try state.writeUserAuthSuccess(into: &buffer) | ||
// Ok we're good to go! | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,33 +16,49 @@ import Crypto | |
import NIO | ||
import NIOFoundationCompat | ||
|
||
public struct NIOSSHKeyExchangeServerReply { | ||
let hostKey: NIOSSHPublicKey | ||
let publicKey: ByteBuffer | ||
let signature: NIOSSHSignature | ||
} | ||
Lukasa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// This protocol defines a container used by the key exchange state machine to manage key exchange. | ||
/// This type erases the specific key exchanger. | ||
protocol EllipticCurveKeyExchangeProtocol { | ||
public protocol NIOSSHKeyExchangeAlgorithmProtocol { | ||
static var keyExchangeInitMessageId: UInt8 { get } | ||
static var keyExchangeReplyMessageId: UInt8 { get } | ||
|
||
init(ourRole: SSHConnectionRole, previousSessionIdentifier: ByteBuffer?) | ||
|
||
func initiateKeyExchangeClientSide(allocator: ByteBufferAllocator) -> SSHMessage.KeyExchangeECDHInitMessage | ||
func initiateKeyExchangeClientSide(allocator: ByteBufferAllocator) -> ByteBuffer | ||
|
||
mutating func completeKeyExchangeServerSide(clientKeyExchangeMessage message: SSHMessage.KeyExchangeECDHInitMessage, | ||
serverHostKey: NIOSSHPrivateKey, | ||
initialExchangeBytes: inout ByteBuffer, | ||
allocator: ByteBufferAllocator, | ||
expectedKeySizes: ExpectedKeySizes) throws -> (KeyExchangeResult, SSHMessage.KeyExchangeECDHReplyMessage) | ||
mutating func completeKeyExchangeServerSide( | ||
clientKeyExchangeMessage message: ByteBuffer, | ||
serverHostKey: NIOSSHPrivateKey, | ||
initialExchangeBytes: inout ByteBuffer, | ||
allocator: ByteBufferAllocator, | ||
expectedKeySizes: ExpectedKeySizes | ||
) throws -> (KeyExchangeResult, NIOSSHKeyExchangeServerReply) | ||
Lukasa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
mutating func receiveServerKeyExchangePayload(serverKeyExchangeMessage message: SSHMessage.KeyExchangeECDHReplyMessage, | ||
initialExchangeBytes: inout ByteBuffer, | ||
allocator: ByteBufferAllocator, | ||
expectedKeySizes: ExpectedKeySizes) throws -> KeyExchangeResult | ||
mutating func receiveServerKeyExchangePayload( | ||
serverKeyExchangeMessage: NIOSSHKeyExchangeServerReply, | ||
initialExchangeBytes: inout ByteBuffer, | ||
allocator: ByteBufferAllocator, | ||
expectedKeySizes: ExpectedKeySizes | ||
) throws -> KeyExchangeResult | ||
|
||
static var keyExchangeAlgorithmNames: [Substring] { get } | ||
} | ||
|
||
struct EllipticCurveKeyExchange<PrivateKey: ECDHCompatiblePrivateKey>: EllipticCurveKeyExchangeProtocol { | ||
struct EllipticCurveKeyExchange<PrivateKey: ECDHCompatiblePrivateKey>: NIOSSHKeyExchangeAlgorithmProtocol { | ||
private var previousSessionIdentifier: ByteBuffer? | ||
private var ourKey: PrivateKey | ||
private var theirKey: PrivateKey.PublicKey? | ||
private var ourRole: SSHConnectionRole | ||
private var sharedSecret: SharedSecret? | ||
|
||
static var keyExchangeInitMessageId: UInt8 { 30 } | ||
static var keyExchangeReplyMessageId: UInt8 { 31 } | ||
|
||
init(ourRole: SSHConnectionRole, previousSessionIdentifier: ByteBuffer?) { | ||
self.ourRole = ourRole | ||
|
@@ -59,14 +75,13 @@ extension EllipticCurveKeyExchange { | |
/// Initiates key exchange by producing an SSH message. | ||
/// | ||
/// For now, we just return the ByteBuffer containing the SSH string. | ||
func initiateKeyExchangeClientSide(allocator: ByteBufferAllocator) -> SSHMessage.KeyExchangeECDHInitMessage { | ||
func initiateKeyExchangeClientSide(allocator: ByteBufferAllocator) -> ByteBuffer { | ||
precondition(self.ourRole.isClient, "Only clients may initiate the client side key exchange!") | ||
|
||
// The largest key we're likely to end up with here is 256 bytes. | ||
var buffer = allocator.buffer(capacity: 256) | ||
self.ourKey.publicKey.write(to: &buffer) | ||
|
||
return .init(publicKey: buffer) | ||
return buffer | ||
} | ||
|
||
/// Handles receiving the client key exchange payload on the server side. | ||
|
@@ -77,15 +92,15 @@ extension EllipticCurveKeyExchange { | |
/// - initialExchangeBytes: The initial bytes of the exchange, suitable for writing into the exchange hash. | ||
/// - allocator: A `ByteBufferAllocator` suitable for this connection. | ||
/// - expectedKeySizes: The sizes of the keys we need to generate. | ||
mutating func completeKeyExchangeServerSide(clientKeyExchangeMessage message: SSHMessage.KeyExchangeECDHInitMessage, | ||
mutating func completeKeyExchangeServerSide(clientKeyExchangeMessage message: ByteBuffer, | ||
serverHostKey: NIOSSHPrivateKey, | ||
initialExchangeBytes: inout ByteBuffer, | ||
allocator: ByteBufferAllocator, | ||
expectedKeySizes: ExpectedKeySizes) throws -> (KeyExchangeResult, SSHMessage.KeyExchangeECDHReplyMessage) { | ||
expectedKeySizes: ExpectedKeySizes) throws -> (KeyExchangeResult, NIOSSHKeyExchangeServerReply) { | ||
precondition(self.ourRole.isServer, "Only servers may receive a client key exchange packet!") | ||
|
||
// With that, we have enough to finalize the key exchange. | ||
let kexResult = try self.finalizeKeyExchange(theirKeyBytes: message.publicKey, | ||
let kexResult = try self.finalizeKeyExchange(theirKeyBytes: message, | ||
initialExchangeBytes: &initialExchangeBytes, | ||
serverHostKey: serverHostKey.publicKey, | ||
allocator: allocator, | ||
|
@@ -100,9 +115,9 @@ extension EllipticCurveKeyExchange { | |
self.ourKey.publicKey.write(to: &publicKeyBytes) | ||
|
||
// Now we have all we need. | ||
let responseMessage = SSHMessage.KeyExchangeECDHReplyMessage(hostKey: serverHostKey.publicKey, | ||
publicKey: publicKeyBytes, | ||
signature: exchangeHashSignature) | ||
let responseMessage = NIOSSHKeyExchangeServerReply(hostKey: serverHostKey.publicKey, | ||
publicKey: publicKeyBytes, | ||
signature: exchangeHashSignature) | ||
|
||
return (KeyExchangeResult(kexResult), responseMessage) | ||
} | ||
|
@@ -116,12 +131,14 @@ extension EllipticCurveKeyExchange { | |
/// - initialExchangeBytes: The initial bytes of the exchange, suitable for writing into the exchange hash. | ||
/// - allocator: A `ByteBufferAllocator` suitable for this connection. | ||
/// - expectedKeySizes: The sizes of the keys we need to generate. | ||
mutating func receiveServerKeyExchangePayload(serverKeyExchangeMessage message: SSHMessage.KeyExchangeECDHReplyMessage, | ||
initialExchangeBytes: inout ByteBuffer, | ||
allocator: ByteBufferAllocator, | ||
expectedKeySizes: ExpectedKeySizes) throws -> KeyExchangeResult { | ||
mutating func receiveServerKeyExchangePayload( | ||
serverKeyExchangeMessage: NIOSSHKeyExchangeServerReply, | ||
initialExchangeBytes: inout ByteBuffer, | ||
allocator: ByteBufferAllocator, | ||
expectedKeySizes: ExpectedKeySizes | ||
) throws -> KeyExchangeResult { | ||
precondition(self.ourRole.isClient, "Only clients may receive a server key exchange packet!") | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we remove this whitespace change? |
||
// Ok, we have a few steps here. Firstly, we need to extract the server's public key and generate our shared | ||
// secret. Then we need to validate that we didn't generate a weak shared secret (possible under some cases), | ||
// as this must fail the key exchange process. | ||
|
@@ -131,14 +148,14 @@ extension EllipticCurveKeyExchange { | |
// | ||
// Finally, we return our generated keys to the state machine. | ||
|
||
let kexResult = try self.finalizeKeyExchange(theirKeyBytes: message.publicKey, | ||
let kexResult = try self.finalizeKeyExchange(theirKeyBytes: serverKeyExchangeMessage.publicKey, | ||
initialExchangeBytes: &initialExchangeBytes, | ||
serverHostKey: message.hostKey, | ||
serverHostKey: serverKeyExchangeMessage.hostKey, | ||
allocator: allocator, | ||
expectedKeySizes: expectedKeySizes) | ||
|
||
// We can now verify signature over the exchange hash. | ||
guard message.hostKey.isValidSignature(message.signature, for: kexResult.exchangeHash) else { | ||
guard serverKeyExchangeMessage.hostKey.isValidSignature(serverKeyExchangeMessage.signature, for: kexResult.exchangeHash) else { | ||
throw NIOSSHError.invalidExchangeHashSignature | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this
var
?