-
Notifications
You must be signed in to change notification settings - Fork 51
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
base: main
Are you sure you want to change the base?
Conversation
@Lukasa please do review once you'e back from a well deserved vacation. |
This seems like a great start! The big missing link here seems to be user authentication, which I'm fine to omit for now as dealing simply with user and host keys will solve the 90% use-case handily. As this PR demonstrates, it's also much easier to implement! I'd be entirely happy with you productising this PR. |
@Lukasa I've updated it all except the docs. Before I add the docs, what do you think of registering SSH public keys? |
I think that's going to be necessary. We can add a thread-safe registry allowing what amount to "plugins" for SSH key types. |
I appearantly forgot to push my commit & ping you. Is this sufficient? |
# Conflicts: # Package.swift
…ther symmetric key algorithms can be used
Hello, any plan to merge the RSA keys support? |
@waylybaye This PR sort of got lost in the shuffle, but I'd happily review a version of this brought up-to-date with the current |
@Lukasa Great! The only thing blocking me to use nio-ssh is the lack of RSA support. Thank you and Joannis. |
This conflicts with the changes made in #98, can we clean that up? |
@Lukasa is that the only requested/required change? |
Nope, it just cleans the diff up enough that I can do a proper review. 😄 |
# Conflicts: # Sources/NIOSSH/SSHMessages.swift
…frankenstein algorithms that test the correctness of SwiftNIO's agnostic approach to cryptograhic choices
Done @Lukasa |
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.
Great, this is a really solid start! I've left some notes in the diff.
@@ -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] = [ |
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
?
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove the whitespace changes in this file?
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove this whitespace change?
|
||
@discardableResult | ||
public func writeWithoutHeader(to buffer: inout ByteBuffer) -> Int { | ||
return buffer.writeSSHHostKeyWithoutHeader(self) |
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.
Do these need to be public
?
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.
I don't quite need writeWithoutHeader
in my use case, but the write(to: )
function is required for implementing DH Group 14 SHA1.
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.
It's necessary to write the host key in that context?
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.
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.
Can we remove writeWithoutHeader
then? It'd be good to avoid public API surface we're not sure we need.
@@ -60,12 +60,12 @@ struct SSHConnectionStateMachine { | |||
/// The state of this state machine. | |||
private var state: State | |||
|
|||
private static let defaultTransportProtectionSchemes: [NIOSSHTransportProtection.Type] = [ | |||
public static let bundledTransportProtectionSchemes: [NIOSSHTransportProtection.Type] = [ |
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.
This should be internal
, as the type is not public
.
private var previousSessionIdentifier: ByteBuffer? | ||
|
||
init(allocator: ByteBufferAllocator, loop: EventLoop, role: SSHConnectionRole, remoteVersion: String, protectionSchemes: [NIOSSHTransportProtection.Type], previousSessionIdentifier: ByteBuffer?) { | ||
init(allocator: ByteBufferAllocator, loop: EventLoop, role: SSHConnectionRole, remoteVersion: String, keyExchangeAlgorithms: [NIOSSHKeyExchangeAlgorithmProtocol.Type] = SSHKeyExchangeStateMachine.bundledKeyExchangeImplementations, transportProtectionSchemes: [NIOSSHTransportProtection.Type] = SSHConnectionStateMachine.bundledTransportProtectionSchemes, previousSessionIdentifier: ByteBuffer?) { |
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.
Is there any reason to default the keyExchangeAlgorithms and transportProtectionSchemes here?
serverAlgorithms = peerKeyExchangeAlgorithms | ||
clientHostKeyAlgorithms = self.supportedHostKeyAlgorithms | ||
serverHostKeyAlgorithms = peerHostKeyAlgorithms | ||
case .server: | ||
clientAlgorithms = peerKeyExchangeAlgorithms | ||
serverAlgorithms = Self.supportedKeyExchangeAlgorithms | ||
serverAlgorithms = role.keyExchangeAlgorithms.flatMap { $0.keyExchangeAlgorithmNames } |
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.
serverAlgorithms = role.keyExchangeAlgorithms.flatMap { $0.keyExchangeAlgorithmNames } | |
serverAlgorithms = role.keyExchangeAlgorithmNames |
@@ -369,13 +377,13 @@ struct SSHKeyExchangeStateMachine { | |||
|
|||
switch self.role { | |||
case .client: | |||
clientAlgorithms = Self.supportedKeyExchangeAlgorithms | |||
clientAlgorithms = role.keyExchangeAlgorithms.flatMap { $0.keyExchangeAlgorithmNames } |
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.
clientAlgorithms = role.keyExchangeAlgorithms.flatMap { $0.keyExchangeAlgorithmNames } | |
clientAlgorithms = role.keyExchangeAlgorithmNames |
fileprivate var _customTransportProtectionSchemes = [NIOSSHTransportProtection.Type]() | ||
fileprivate var _customKeyExchangeAlgorithms = [NIOSSHKeyExchangeAlgorithmProtocol.Type]() | ||
fileprivate var _customPublicKeyAlgorithms: [NIOSSHPublicKeyProtocol.Type] = [] | ||
fileprivate var _customSignatures: [NIOSSHSignatureProtocol.Type] = [] |
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.
Can we nest these inside an enum
without cases just to give them a namespace?
return _customKeyExchangeAlgorithms | ||
} | ||
|
||
internal let customAlgorithmsLock = NSLock() |
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.
We should probably use fine-grained locking here, so can we have one lock per field? It'll reduce contention on the locks.
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.
Additionally, instead of NSLock
can we add a dependency on NIOConcurrencyHelpers
and use that Lock
?
|
||
@discardableResult | ||
public func writeWithoutHeader(to buffer: inout ByteBuffer) -> Int { | ||
return buffer.writeSSHHostKeyWithoutHeader(self) |
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.
Can we remove writeWithoutHeader
then? It'd be good to avoid public API surface we're not sure we need.
Sources/NIOSSH/Role.swift
Outdated
} | ||
|
||
internal var keyExchangeAlgorithmNames: [Substring] { | ||
keyExchangeAlgorithms.flatMap { $0.keyExchangeAlgorithmNames } |
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.
Nit:
keyExchangeAlgorithms.flatMap { $0.keyExchangeAlgorithmNames } | |
self.keyExchangeAlgorithms.flatMap { $0.keyExchangeAlgorithmNames } |
@@ -43,7 +43,7 @@ internal class AESGCMTransportProtection { | |||
required init(initialKeys: NIOSSHSessionKeys) throws { | |||
guard initialKeys.outboundEncryptionKey.bitCount == Self.keySizes.encryptionKeySize * 8, | |||
initialKeys.inboundEncryptionKey.bitCount == Self.keySizes.encryptionKeySize * 8 else { | |||
throw NIOSSHError.invalidKeySize | |||
N throw NIOSSHError.invalidKeySize |
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.
Nit:
N throw NIOSSHError.invalidKeySize | |
throw NIOSSHError.invalidKeySize |
Can one of the admins verify this patch? |
719f5ce
to
2a85b45
Compare
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.
I started reviewing this, but the change is massive because it has reformatted the codebase. Can you please use the exact version of swift-format that CI is using and re-run to clean up the format commit? Our Dockerfile can assist with that.
@@ -31,34 +31,34 @@ public struct SSHChildChannelOptions { | |||
public static let peerMaximumMessageLength: SSHChildChannelOptions.Types.PeerMaximumMessageLengthOption = .init() | |||
} | |||
|
|||
extension SSHChildChannelOptions { | |||
public enum Types {} | |||
public extension SSHChildChannelOptions { |
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.
NIO style is to place the access modifiers within the extensions, not on the extension itself.
} | ||
|
||
extension SSHChildChannelOptions.Types { | ||
public extension SSHChildChannelOptions.Types { |
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.
NIO style is to place the access modifiers within the extensions, not on the extension itself.
@@ -24,8 +24,8 @@ struct ChildChannelStateMachine { | |||
} | |||
} | |||
|
|||
extension ChildChannelStateMachine { | |||
fileprivate enum State: Hashable { | |||
private extension ChildChannelStateMachine { |
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.
NIO style is to place the access modifiers within the extensions, not on the extension itself.
@@ -299,7 +299,7 @@ extension SSHChannelRequestEvent { | |||
/// Constructs a channel request event and wraps it up in an Any. | |||
/// | |||
/// This is usually used just prior to firing this into the pipeline. | |||
internal static func fromMessage(_ message: SSHMessage.ChannelRequestMessage) -> Any? { | |||
static func fromMessage(_ message: SSHMessage.ChannelRequestMessage) -> Any? { |
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.
This change is unnecessary, please remove.
2a85b45
to
d1fc273
Compare
@Lukasa Rebased out all the |
// | ||
// This source file is part of the SwiftNIO open source project | ||
// | ||
// Copyright (c) YEARS Apple Inc. and the SwiftNIO project authors |
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.
Can you fix up this copyright header to read 2022
?
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.
Fixed. Is there a reason not to be using swiftformat
's fileHeader
rule for this? AFAIK the version in use supports it.
} | ||
} | ||
|
||
public enum NIOSSHAlgorithms { |
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.
I remain uncertain about doing this with global state. Can we consider placing this on per connection configuration instead of using this global config?
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.
@Joannis Can you weigh in on this?
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.
I agree that a per-connection basis is a better solution. But we will need a registry for sure, since we can have a variety of Host Keys for clients, and accepted public keys for servers
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.
@Lukasa @gwynne While I was picking this up I was discussing this with @Joannis and want to introduce a per connection solution. But while discussing, with the way the custom keys currently work we could get rid of the BackingKey enums that are currently internal in the library, by making the bundled keys conform to NIOSSHPrivateKeyProtocol
and similar protocols. This would eliminate a lot of switch statements and simplify a lot of methods quite a bit. We don't immediately see any issues, but I wanted to check in with you, what are your thoughts on this?
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.
I don't think the switch statements are that big a deal: they provide fast-paths for the most common use-cases where the compiler can be aware of exactly what the implementation is. The extensible backing pattern should certainly run this through a protocol though.
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.
That's true it would make the whole implementation more consistent however and remove a lot of boilerplate/duplication. So not something the user benefits from perse but still could be interesting in terms of maintainability/accessible for new devs checking out the repo? I put in quite some work to get it towards that end. Would you be interested in seeing a PR?
(it still needs a little work but if you're not interested in merging that kind of work in to the repo I'll probably leave it as is and just finish up your last remarks regarding not using global state for registering algorithms, which btw already needs quite a bit of moving around of methods in order to pass the registered algorithms down to the right ByteBuffer extensions)
I'm hoping to finish up the work and then ideally divide it into a few PR's so it's a bit more manageable for you to review. Anyway let me know! :)
64deede
to
4b0e7ec
Compare
Notes:
I've implemented RSA in a separate module within this repo, to be split out of this repository before merging. It serves as an example implementation for this PR.The RSA implementation now resides in Citadel
I believe there are a couple of API design choices, like protocol names, that should be improved. I'm happy to take any feedback, of course.