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 |
Lukasa
left a comment
There was a problem hiding this comment.
Great, this is a really solid start! I've left some notes in the diff.
| private var state: State | ||
|
|
||
| private static let defaultTransportProtectionSchemes: [NIOSSHTransportProtection.Type] = [ | ||
| internal static var defaultTransportProtectionSchemes: [NIOSSHTransportProtection.Type] = [ |
| try state.writeUserAuthRequest(message, into: &buffer) | ||
| self.state = .userAuthentication(state) | ||
|
|
||
There was a problem hiding this comment.
Can we remove the whitespace changes in this file?
| ) throws -> KeyExchangeResult { | ||
| precondition(self.ourRole.isClient, "Only clients may receive a server key exchange packet!") | ||
|
|
||
There was a problem hiding this comment.
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.
Do these need to be public?
There was a problem hiding this comment.
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.
It's necessary to write the host key in that context?
There was a problem hiding this comment.
There was a problem hiding this comment.
Can we remove writeWithoutHeader then? It'd be good to avoid public API surface we're not sure we need.
| private var state: State | ||
|
|
||
| private static let defaultTransportProtectionSchemes: [NIOSSHTransportProtection.Type] = [ | ||
| public static let bundledTransportProtectionSchemes: [NIOSSHTransportProtection.Type] = [ |
There was a problem hiding this comment.
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.
Is there any reason to default the keyExchangeAlgorithms and transportProtectionSchemes here?
| case .server: | ||
| clientAlgorithms = peerKeyExchangeAlgorithms | ||
| serverAlgorithms = Self.supportedKeyExchangeAlgorithms | ||
| serverAlgorithms = role.keyExchangeAlgorithms.flatMap { $0.keyExchangeAlgorithmNames } |
There was a problem hiding this comment.
| serverAlgorithms = role.keyExchangeAlgorithms.flatMap { $0.keyExchangeAlgorithmNames } | |
| serverAlgorithms = role.keyExchangeAlgorithmNames |
| switch self.role { | ||
| case .client: | ||
| clientAlgorithms = Self.supportedKeyExchangeAlgorithms | ||
| clientAlgorithms = role.keyExchangeAlgorithms.flatMap { $0.keyExchangeAlgorithmNames } |
There was a problem hiding this comment.
| 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.
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.
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.
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.
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.
Nit:
| keyExchangeAlgorithms.flatMap { $0.keyExchangeAlgorithmNames } | |
| self.keyExchangeAlgorithms.flatMap { $0.keyExchangeAlgorithmNames } |
| 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.
Nit:
| N throw NIOSSHError.invalidKeySize | |
| throw NIOSSHError.invalidKeySize |
|
Can one of the admins verify this patch? |
719f5ce to
2a85b45
Compare
Lukasa
left a comment
There was a problem hiding this comment.
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.
|
|
||
| extension SSHChildChannelOptions { | ||
| public enum Types {} | ||
| public extension SSHChildChannelOptions { |
There was a problem hiding this comment.
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.
NIO style is to place the access modifiers within the extensions, not on the extension itself.
|
|
||
| extension ChildChannelStateMachine { | ||
| fileprivate enum State: Hashable { | ||
| private extension ChildChannelStateMachine { |
There was a problem hiding this comment.
NIO style is to place the access modifiers within the extensions, not on the extension itself.
| /// | ||
| /// 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.
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.
Can you fix up this copyright header to read 2022?
There was a problem hiding this comment.
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.
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.
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.
@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.
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.
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
|
@Lukasa please continue on the new track |
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.