diff --git a/Package.swift b/Package.swift index 5b09982..5e84241 100644 --- a/Package.swift +++ b/Package.swift @@ -1,9 +1,9 @@ -// swift-tools-version:5.4 +// swift-tools-version:5.6 //===----------------------------------------------------------------------===// // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -30,6 +30,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-nio.git", from: "2.32.0"), .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"), .package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"), + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), ], targets: [ .target( diff --git a/Package@swift-5.4.swift b/Package@swift-5.4.swift new file mode 100644 index 0000000..5b09982 --- /dev/null +++ b/Package@swift-5.4.swift @@ -0,0 +1,83 @@ +// swift-tools-version:5.4 +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import PackageDescription + +let package = Package( + name: "swift-nio-ssh", + platforms: [ + .macOS(.v10_15), + .iOS(.v13), + .watchOS(.v6), + .tvOS(.v13), + ], + products: [ + .library(name: "NIOSSH", targets: ["NIOSSH"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-nio.git", from: "2.32.0"), + .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"), + .package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"), + ], + targets: [ + .target( + name: "NIOSSH", + dependencies: [ + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + .product(name: "Crypto", package: "swift-crypto"), + .product(name: "Atomics", package: "swift-atomics"), + ] + ), + .executableTarget( + name: "NIOSSHClient", + dependencies: [ + "NIOSSH", + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOPosix", package: "swift-nio"), + .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), + ] + ), + .executableTarget( + name: "NIOSSHServer", + dependencies: [ + "NIOSSH", + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOPosix", package: "swift-nio"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + .product(name: "Crypto", package: "swift-crypto"), + ] + ), + .executableTarget( + name: "NIOSSHPerformanceTester", + dependencies: [ + "NIOSSH", + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOEmbedded", package: "swift-nio"), + .product(name: "Crypto", package: "swift-crypto"), + ] + ), + .testTarget( + name: "NIOSSHTests", + dependencies: [ + "NIOSSH", + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOEmbedded", package: "swift-nio"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + ] + ), + ] +) diff --git a/Package@swift-5.5.swift b/Package@swift-5.5.swift new file mode 100644 index 0000000..6b08f81 --- /dev/null +++ b/Package@swift-5.5.swift @@ -0,0 +1,83 @@ +// swift-tools-version:5.5 +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import PackageDescription + +let package = Package( + name: "swift-nio-ssh", + platforms: [ + .macOS(.v10_15), + .iOS(.v13), + .watchOS(.v6), + .tvOS(.v13), + ], + products: [ + .library(name: "NIOSSH", targets: ["NIOSSH"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-nio.git", from: "2.32.0"), + .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"), + .package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"), + ], + targets: [ + .target( + name: "NIOSSH", + dependencies: [ + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + .product(name: "Crypto", package: "swift-crypto"), + .product(name: "Atomics", package: "swift-atomics"), + ] + ), + .executableTarget( + name: "NIOSSHClient", + dependencies: [ + "NIOSSH", + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOPosix", package: "swift-nio"), + .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), + ] + ), + .executableTarget( + name: "NIOSSHServer", + dependencies: [ + "NIOSSH", + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOPosix", package: "swift-nio"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + .product(name: "Crypto", package: "swift-crypto"), + ] + ), + .executableTarget( + name: "NIOSSHPerformanceTester", + dependencies: [ + "NIOSSH", + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOEmbedded", package: "swift-nio"), + .product(name: "Crypto", package: "swift-crypto"), + ] + ), + .testTarget( + name: "NIOSSHTests", + dependencies: [ + "NIOSSH", + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOEmbedded", package: "swift-nio"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + ] + ), + ] +) diff --git a/Sources/NIOSSH/Child Channels/ChildChannelOptions.swift b/Sources/NIOSSH/Child Channels/ChildChannelOptions.swift index 8a7e232..0dad7e7 100644 --- a/Sources/NIOSSH/Child Channels/ChildChannelOptions.swift +++ b/Sources/NIOSSH/Child Channels/ChildChannelOptions.swift @@ -18,46 +18,47 @@ import NIOCore /// /// Please note that some of NIO's regular `ChannelOptions` are valid on `SSHChildChannel`s. public struct SSHChildChannelOptions { - /// - seealso: `LocalChannelIdentifierOption`. + /// See: ``SSHChildChannelOptions/Types/LocalChannelIdentifierOption``. public static let localChannelIdentifier: SSHChildChannelOptions.Types.LocalChannelIdentifierOption = .init() - /// - seealso: `RemoteChannelIdentifierOption`. + /// See: ``SSHChildChannelOptions/Types/RemoteChannelIdentifierOption``. public static let remoteChannelIdentifier: SSHChildChannelOptions.Types.RemoteChannelIdentifierOption = .init() - /// - seealso: `SSHChannelTypeOption`. + /// See: ``SSHChildChannelOptions/Types/SSHChannelTypeOption``. public static let sshChannelType: SSHChildChannelOptions.Types.SSHChannelTypeOption = .init() - /// - seealso: `PeerMaximumMessageLengthOption`. + /// See: ``SSHChildChannelOptions/Types/PeerMaximumMessageLengthOption``. public static let peerMaximumMessageLength: SSHChildChannelOptions.Types.PeerMaximumMessageLengthOption = .init() } extension SSHChildChannelOptions { + /// Types for the ``SSHChildChannelOptions``. public enum Types {} } extension SSHChildChannelOptions.Types { - /// `LocalChannelIdentifierOption` allows users to query the channel number assigned locally for a given channel. + /// ``SSHChildChannelOptions/Types/LocalChannelIdentifierOption`` allows users to query the channel number assigned locally for a given channel. public struct LocalChannelIdentifierOption: ChannelOption, NIOSSHSendable { public typealias Value = UInt32 public init() {} } - /// `RemoteChannelIdentifierOption` allows users to query the channel number assigned by the remote peer for a given channel. + /// ``SSHChildChannelOptions/Types/RemoteChannelIdentifierOption`` allows users to query the channel number assigned by the remote peer for a given channel. public struct RemoteChannelIdentifierOption: ChannelOption, NIOSSHSendable { public typealias Value = UInt32? public init() {} } - /// `SSHChannelTypeOption` allows users to query the type of the channel they're currently using. + /// ``SSHChildChannelOptions/Types/SSHChannelTypeOption`` allows users to query the type of the channel they're currently using. public struct SSHChannelTypeOption: ChannelOption, NIOSSHSendable { public typealias Value = SSHChannelType public init() {} } - /// `PeerMaximumMessageLengthOption` allows users to query the maximum packet size value reported by the remote peer for a given channel. + /// ``SSHChildChannelOptions/Types/PeerMaximumMessageLengthOption`` allows users to query the maximum packet size value reported by the remote peer for a given channel. public struct PeerMaximumMessageLengthOption: ChannelOption, NIOSSHSendable { public typealias Value = UInt32 diff --git a/Sources/NIOSSH/Child Channels/ChildChannelUserEvents.swift b/Sources/NIOSSH/Child Channels/ChildChannelUserEvents.swift index 9f76705..14758ee 100644 --- a/Sources/NIOSSH/Child Channels/ChildChannelUserEvents.swift +++ b/Sources/NIOSSH/Child Channels/ChildChannelUserEvents.swift @@ -16,6 +16,7 @@ import NIOCore /// A namespace for SSH channel request events. public enum SSHChannelRequestEvent { + /// A request for the peer to allocate a pseudo-terminal. public struct PseudoTerminalRequest: Hashable, NIOSSHSendable { /// Whether a reply to this PTY request is desired. public var wantReply: Bool @@ -91,7 +92,7 @@ public enum SSHChannelRequestEvent { } } - /// An EnvironmentRequest communicates a single environment variable the peer wants set. + /// An ``EnvironmentRequest`` communicates a single environment variable the peer wants set. public struct EnvironmentRequest: Hashable, NIOSSHSendable { /// The name of the environment variable. public var name: String @@ -202,6 +203,9 @@ public enum SSHChannelRequestEvent { } } + /// A notification that the user has changed the size of the window. + /// + /// Only useful if a pseudo-terminal has been allocated. public struct WindowChangeRequest: Hashable, NIOSSHSendable { /// Whether a reply to this window change request is desired. public var wantReply: Bool { diff --git a/Sources/NIOSSH/Child Channels/SSHChannelData.swift b/Sources/NIOSSH/Child Channels/SSHChannelData.swift index 59e3cd9..db1aaba 100644 --- a/Sources/NIOSSH/Child Channels/SSHChannelData.swift +++ b/Sources/NIOSSH/Child Channels/SSHChannelData.swift @@ -18,14 +18,16 @@ import NIOCore #endif // swift(>=5.6) -/// `SSHChannelData` is the data type that is passed around in `SSHChildChannel` objects. +/// ``SSHChannelData`` is the data type that is passed around in `SSHChildChannel` objects. /// /// This is the baseline kind of data available for `SSHChildChannel` objects. It encapsulates /// the difference between `SSH_MSG_CHANNEL_DATA` and `SSH_MSG_CHANNEL_EXTENDED_DATA` by storing /// them both in a single data type that marks this difference. public struct SSHChannelData { + /// The type of this data. public var type: DataType + /// The data in this message. public var data: IOData public init(type: DataType, data: IOData) { @@ -39,8 +41,8 @@ extension SSHChannelData: Equatable {} extension SSHChannelData: NIOSSHSendable {} extension SSHChannelData { - /// The type of this channel data. Regular `.channel` data is the standard type of data on an `SSHChannel`, - /// but extended data types (such as `.stderr`) are available as well. + /// The type of this channel data. Regular ``SSHChannelData/DataType/channel`` data is the standard type of data on an `SSHChannel`, + /// but extended data types (such as ``SSHChannelData/DataType/stdErr``) are available as well. public struct DataType { internal var _baseType: UInt32 @@ -50,7 +52,7 @@ extension SSHChannelData { /// Extended data associated with stderr. public static let stdErr = DataType(_baseType: 1) - /// Construct an `SSHChannelData` for an unknown type of extended data. + /// Construct an ``SSHChannelData`` for an unknown type of extended data. public init(extended: Int) { precondition(extended != 0) self._baseType = UInt32(extended) diff --git a/Sources/NIOSSH/Child Channels/SSHChannelType.swift b/Sources/NIOSSH/Child Channels/SSHChannelType.swift index 249f50f..9dbe502 100644 --- a/Sources/NIOSSH/Child Channels/SSHChannelType.swift +++ b/Sources/NIOSSH/Child Channels/SSHChannelType.swift @@ -14,9 +14,9 @@ import NIOCore -/// `SSHChannelType` represents the type of a single SSH channel. +/// ``SSHChannelType`` represents the type of a single SSH channel. /// -/// SSH Channels are always one of a number of types. The most common type is "session", which +/// SSH Channels are always one of a number of types. The most common type is ``SSHChannelType/session``, which /// encompasses remote execution of a program, whether that be a single binary, a shell, a subsystem, /// or something else. /// @@ -38,6 +38,7 @@ public enum SSHChannelType: Equatable, NIOSSHSendable { } extension SSHChannelType { + /// ``SSHChannelType/DirectTCPIP`` is a request from the client to the server to open an outbound connection. public struct DirectTCPIP: Equatable, NIOSSHSendable { /// The target host for the forwarded TCP connection. public var targetHost: String @@ -72,6 +73,8 @@ extension SSHChannelType { } extension SSHChannelType { + /// ``SSHChannelType/ForwardedTCPIP`` is a connection that was accepted from a listening socket on the + /// server and is being forwarded to the client. public struct ForwardedTCPIP: Equatable, NIOSSHSendable { /// The host the remote peer connected to. This should be identical to the one that was requested. public var listeningHost: String diff --git a/Sources/NIOSSH/Connection State Machine/Operations/AcceptsUserAuthMessages.swift b/Sources/NIOSSH/Connection State Machine/Operations/AcceptsUserAuthMessages.swift index 6bbb5d8..70344dc 100644 --- a/Sources/NIOSSH/Connection State Machine/Operations/AcceptsUserAuthMessages.swift +++ b/Sources/NIOSSH/Connection State Machine/Operations/AcceptsUserAuthMessages.swift @@ -20,10 +20,10 @@ protocol AcceptsUserAuthMessages { /// This event indicates that server wants us to display the following message to the end user. public struct NIOUserAuthBannerEvent: Hashable { - /// message to be displayed to end user + /// The message to be displayed to end user public var message: String - /// tag identifying the language used for `message`, following RFC 3066 + /// The tag identifying the language used for `message`, following RFC 3066 public var languageTag: String public init(message: String, languageTag: String) { @@ -32,7 +32,7 @@ public struct NIOUserAuthBannerEvent: Hashable { } } -/// This event indicates that server accepted our response to authentication challenge. SSH session can be considered active after that. +/// This event indicates that server accepted our response to authentication challenge. The SSH session can be considered active after this point. public struct UserAuthSuccessEvent: Hashable { public init() {} } diff --git a/Sources/NIOSSH/Docs.docc/index.md b/Sources/NIOSSH/Docs.docc/index.md new file mode 100644 index 0000000..c2c55b7 --- /dev/null +++ b/Sources/NIOSSH/Docs.docc/index.md @@ -0,0 +1,189 @@ +# ``NIOSSH`` + +This project contains SSH support using SwiftNIO. + +## What is SwiftNIO SSH? + +SwiftNIO SSH is a programmatic implementation of SSH: that is, it is a collection of APIs that allow programmers to implement SSH-speaking endpoints. Critically, this means it is more like libssh2 than openssh. SwiftNIO SSH does not ship production-ready SSH clients and servers, but instead provides the building blocks for building this kind of client and server. + +There are a number of reasons to provide a programmatic SSH implementation. One is that SSH has a unique relationship to user interactivity. Technical users are highly accustomed to interacting with SSH interactively, either to run commands on remote machines or to run interactive shells. Having the ability to programmatically respond to these requests enables interesting alternative modes of interaction. As prior examples, we can point to Twisted's Manhole, which uses [a programmatic SSH implementation called `conch`](https://twistedmatrix.com/trac/wiki/TwistedConch) to provide an interactive Python interpreter within a running Python server, or [ssh-chat](https://github.com/shazow/ssh-chat), a SSH server that provides a chat room instead of regular SSH shell functionality. Innovative uses can also be imagined for TCP forwarding. + +Another good reason to provide programmatic SSH is that it is not uncommon for services to need to interact with other services in a way that involves running commands. While `Process` solves this for the local use-case, sometimes the commands that need to be invoked are remote. While `Process` could launch an `ssh` client as a sub-process in order to run this invocation, it can be substantially more straightforward to simply invoke SSH directly. This is [`libssh2`](https://www.libssh2.org)'s target use-case. SwiftNIO SSH provides the equivalent of the networking and cryptographic layer of libssh2, allowing motivated users to drive SSH sessions directly from within Swift services. + +SwiftNIO SSH requires Swift 5.4 and newer. 0.3.x supports Swift 5.2 and 5.3, 0.2.x supports Swift 5.1. + +## What does SwiftNIO SSH support? + +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 ECDSA 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 + +## How do I use SwiftNIO SSH? + +SwiftNIO SSH provides a SwiftNIO `ChannelHandler`, `NIOSSHHandler`. This handler implements the bulk of the SSH protocol directly. Users are not expected to generate SSH messages directly: instead, they interact with the ``NIOSSHHandler`` through child channels and delegates. + +SSH is a multiplexed protocol: each SSH connection is subdivided into multiple bidirectional communication channels called, appropriately enough, channels. SwiftNIO SSH reflects this construction by using a "child channel" abstraction. When a peer creates a new SSH channel, SwiftNIO SSH will create a new NIO `Channel` that is used to represent all traffic on that SSH channel. Within this child `Channel` all events are strictly ordered with respect to one another: however, events in different `Channel`s may be interleaved freely by the implementation. + +An active SSH connection therefore looks like this: + +``` +┌ ─ NIO Channel ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ + +│ ┌────────────────────────────────┐ │ + │ │ +│ │ │ │ + │ │ +│ │ │ │ + │ NIOSSHHandler │───────────────────────┐ +│ │ │ │ │ + │ │ │ +│ │ │ │ │ + │ │ │ +│ └────────────────────────────────┘ │ │ + │ +└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ + │ + │ + │ + │ + ▼ + ┌── SSH Child Channel ─────────────────────────────────────────────────────────────┐ + │ │ + │ ┌────────────────────────────────┐ ┌────────────────────────────────┐ ├───┐ + │ │ │ │ │ │ │ + │ │ │ │ │ │ ├───┐ + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ + │ │ User Handler │ │ User Handler │ │ │ │ + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ + │ └────────────────────────────────┘ └────────────────────────────────┘ │ │ │ + │ │ │ │ + └───┬──────────────────────────────────────────────────────────────────────────────┘ │ │ + │ │ │ + └───┬──────────────────────────────────────────────────────────────────────────────┘ │ + │ │ + └──────────────────────────────────────────────────────────────────────────────────┘ +``` + +An SSH channel is invoked with a channel type. NIOSSH supports three: `session`, `directTCPIP`, and `forwardedTCPIP`. The most common channel type is `session`: `session` is used to represent the invocation of a program, whether a specific named program or a shell. The other two channel types are related to TCP port forwarding, and will be discussed later. + +An SSH channel operates on a single data type: ``SSHChannelData``. This structure encapsulates the fact that SSH supports both regular and "extended" channel data. The regular channel data (``SSHChannelData/DataType/channel``) is used for the vast majority of core data. In ``SSHChannelType/session`` channels the ``SSHChannelData/DataType/channel`` data type is used for standard input and standard output: the ``SSHChannelData/DataType/stdErr`` data type is used for standard error (naturally). In TCP forwarding channels, the ``SSHChannelData/DataType/channel`` data type is the only kind used, and represents the forwarded data. + +### Channel Events + +A ``SSHChannelType/session`` channel represents an invocation of a command. Exactly how the channel operates is communicated in a number of inbound user events. The following events are important: + +- ``SSHChannelRequestEvent/PseudoTerminalRequest``: Requests the allocation of a pseudo-terminal. +- ``SSHChannelRequestEvent/EnvironmentRequest``: Requests a single environment variable for the command invocation. Always sent before the command itself. +- ``SSHChannelRequestEvent/ShellRequest``: Requests that the command to be invoked is the authenticated user's shell. +- ``SSHChannelRequestEvent/ExecRequest``: Requests the invocation of a specific command. +- ``SSHChannelRequestEvent/ExitStatus``: Used to signal that the remote command has exited, and communicates the exit code. +- ``SSHChannelRequestEvent/ExitSignal``: Used to indicate that the remote command was terminated in response to a signal, and what that signal was. +- ``SSHChannelRequestEvent/SignalRequest``: Used to send a signal to the remote command. +- ``SSHChannelRequestEvent/LocalFlowControlRequest``: Used to indicate whether the client is capable of performing Ctrl-Q/Ctrl-S flow control itself. +- ``SSHChannelRequestEvent/WindowChangeRequest``: Used to communicate a change in the size of the terminal window on the client to the allocated peudo-terminal. +- ``SSHChannelRequestEvent/SubsystemRequest``: Used to request invocation of a specific subsystem. The meaning of this is specific to individual use-cases. + +These events are unused in port forwarding messages. SSH implementations that support ``SSHChannelType/session`` type channels need to be prepared to handle most or all of these in various ways. + +Each of these events also has a `wantReply` field. This indicates whether the request need a reply to indicate success or failure. If it does, the following two events are used: + +- ``ChannelSuccessEvent``, to communicate success. +- ``ChannelFailureEvent``, to communicate failure. + +### Half Closure + +The SSH network protocol pervasively uses half-closure in the child channels. NIO `Channel`s typically have half-closure support disabled by default, and SwiftNIO SSH respects this default in its child channels as well. However, if you leave this setting at its default value the SSH child channels will behave extremely unexpectedly. For this reason, it is strongly recommended that all child channels have half closure support enabled: + +```swift +channel.setOption(ChannelOptions.allowRemoteHalfClosure, true) +``` + +This then uses standard NIO half-closure support. The remote peer sending EOF will be communicated with an inbound user event, `ChannelEvent.inputClosed`. To send EOF yourself, call `close(mode: .output)`. + +### User Authentication + +User authentication is a vital part of SSH. To manage it, SwiftNIO SSH uses a pair of delegate protocols: ``NIOSSHClientUserAuthenticationDelegate`` and ``NIOSSHServerUserAuthenticationDelegate``. Clients and servers should provide implementations of these delegate protocols to manage user authentication. + +The client protocol is straightforward: SwiftNIO SSH will invoke the method ``NIOSSHClientUserAuthenticationDelegate/nextAuthenticationType(availableMethods:nextChallengePromise:)`` on the delegate. The `availableMethods` will be an instance of ``NIOSSHAvailableUserAuthenticationMethods`` communicating which authentication methods the server has suggested will be acceptable. The delegate can then complete `nextChallengePromise` with either a new authentication request, or with `nil` to indicate that the client has run out of things to try. + +The server protocol is more complex. The delegate must provide a ``NIOSSHServerUserAuthenticationDelegate/supportedAuthenticationMethods`` property that communicates which authentication methods are supported by the delegate. Then, each time the client sends a user auth request, the ``NIOSSHServerUserAuthenticationDelegate/requestReceived(request:responsePromise:)`` method will be invoked. This may be invoked multiple times in parallel, as clients are allowed to issue auth requests in parallel. The `responsePromise` should be succeeded with the result of the authentication. There are three results: ``NIOSSHUserAuthenticationOutcome/success`` and ``NIOSSHUserAuthenticationOutcome/failure`` are straightforward, but in principle the server can require multiple challenges using ``NIOSSHUserAuthenticationOutcome/partialSuccess(remainingMethods:)``. + +### Direct Port Forwarding + +Direct port forwarding is port forwarding from client to server. In this mode traditionally the client will listen on a local port, and will forward inbound connections to the server. It will ask that the server forward these connections as outbound connections to a specific host and port. + +These channels can be directly opened by clients by using the ``SSHChannelType/directTCPIP(_:)`` channel type. + +### Remote Port Forwarding and Global Requests + +Remote port forwarding is a less-common situation where the client asks the server to listen on a specific address and port, and to forward all inbound connections to the client. As the client needs to request this behaviour, it does so using global requests. + +Global requests are initiated using ``NIOSSHHandler/sendTCPForwardingRequest(_:promise:)`` and are received and handled by way of a ``GlobalRequestDelegate``. There are two global requests supported today: + +- ``GlobalRequest/TCPForwardingRequest/listen(host:port:)``: a request for the server to listen on a given host and port. +- ``GlobalRequest/TCPForwardingRequest/cancel(host:port:)``: a request to cancel the listening on the given host and port. + +Servers may be notified of and respond to these requests using a ``GlobalRequestDelegate``. The method to implement here is ``GlobalRequestDelegate/tcpForwardingRequest(_:handler:promise:)-eq68``. This delegate method will be invoked any time a global request is received. The response to the request is passed into `promise`. + +Forwarded channels are then sent from server to client using the ``SSHChannelType/forwardedTCPIP(_:)`` channel type. + +## Topics + +### Connections + +- ``NIOSSHHandler`` +- ``SSHConnectionRole`` +- ``SSHClientConfiguration`` +- ``SSHServerConfiguration`` +- ``NIOUserAuthBannerEvent`` +- ``UserAuthSuccessEvent`` + +### Port Forwarding + +- ``GlobalRequestDelegate`` +- ``GlobalRequest`` + +### User Authentication + +- ``NIOSSHClientUserAuthenticationDelegate`` +- ``NIOSSHServerUserAuthenticationDelegate`` +- ``SimplePasswordDelegate`` +- ``DenyAllServerAuthDelegate`` +- ``NIOSSHAvailableUserAuthenticationMethods`` +- ``NIOSSHUserAuthenticationRequest`` +- ``NIOSSHUserAuthenticationOffer`` +- ``NIOSSHUserAuthenticationOutcome`` + +### Host Key Verification + +- ``NIOSSHClientServerAuthenticationDelegate`` + +### Child Channels + +- ``SSHChannelType`` +- ``SSHChannelData`` +- ``SSHChildChannelOptions`` +- ``SSHChannelRequestEvent`` +- ``ChannelSuccessEvent`` +- ``ChannelFailureEvent`` +- ``SSHTerminalModes`` + +### Keys and Signatures + +- ``NIOSSHPublicKey`` +- ``NIOSSHPrivateKey`` +- ``NIOSSHCertifiedPublicKey`` +- ``NIOSSHSignature`` +- ``NIOSSHEncryptablePayload`` + +### Errors + +- ``NIOSSHError`` diff --git a/Sources/NIOSSH/GlobalRequestDelegate.swift b/Sources/NIOSSH/GlobalRequestDelegate.swift index 19347bf..3982061 100644 --- a/Sources/NIOSSH/GlobalRequestDelegate.swift +++ b/Sources/NIOSSH/GlobalRequestDelegate.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import NIOCore -/// A `GlobalRequestDelegate` is used by an SSH server to handle SSH global requests. +/// A ``GlobalRequestDelegate`` is used by an SSH server to handle SSH global requests. /// /// These are requests for connection-wide SSH resources. Today the only global requests /// available are for managing TCP port forwarding: specifically, they allow clients to @@ -23,6 +23,8 @@ import NIOCore /// all requests of a given type. public protocol GlobalRequestDelegate { /// The client wants to manage TCP port forwarding. + /// + /// The default implementation rejects all requests to establish TCP port forwarding. func tcpForwardingRequest(_: GlobalRequest.TCPForwardingRequest, handler: NIOSSHHandler, promise: EventLoopPromise) } @@ -33,7 +35,7 @@ extension GlobalRequestDelegate { } } -/// A namespace of `GlobalRequest` objects that delegates may be asked to handle. +/// A namespace of ``GlobalRequest`` objects that implementations of ``GlobalRequestDelegate`` may be asked to handle. public enum GlobalRequest { /// A request from a client to a server for the server to listen on a port on the client's behalf. If accepted, /// the server will listen on a port, and will forward accepted connections to the client using the "forwarded-tcpip" diff --git a/Sources/NIOSSH/Keys And Signatures/ClientServerAuthenticationDelegate.swift b/Sources/NIOSSH/Keys And Signatures/ClientServerAuthenticationDelegate.swift index 401f9e6..6244102 100644 --- a/Sources/NIOSSH/Keys And Signatures/ClientServerAuthenticationDelegate.swift +++ b/Sources/NIOSSH/Keys And Signatures/ClientServerAuthenticationDelegate.swift @@ -14,7 +14,7 @@ import NIOCore -/// A `NIOSSHClientSErverAuthenticationDelegate` is an object that can validate whether +/// A ``NIOSSHClientServerAuthenticationDelegate`` is an object that can validate whether /// a server host key is trusted. /// /// When an SSH connection is performing key exchange, the SSH server will send over its host @@ -23,5 +23,9 @@ import NIOCore public protocol NIOSSHClientServerAuthenticationDelegate { /// Invoked to validate a specific host key. Implementations should succeed the `validationCompletePromise` /// if they trust the host key, or fail it if they do not. + /// + /// - parameters: + /// - hostKey: The host key presented by the server + /// - validationCompletePromise: A promise that must be succeeded or failed based on whether the host key is trusted. func validateHostKey(hostKey: NIOSSHPublicKey, validationCompletePromise: EventLoopPromise) } diff --git a/Sources/NIOSSH/Keys And Signatures/NIOSSHCertifiedPublicKey.swift b/Sources/NIOSSH/Keys And Signatures/NIOSSHCertifiedPublicKey.swift index 02554a1..42f3598 100644 --- a/Sources/NIOSSH/Keys And Signatures/NIOSSHCertifiedPublicKey.swift +++ b/Sources/NIOSSH/Keys And Signatures/NIOSSHCertifiedPublicKey.swift @@ -21,7 +21,7 @@ import Darwin import Glibc #endif // os(macOS) || os(iOS) || os(watchOS) || os(tvOS) -/// A `NIOSSHCertifiedPublicKey` is an SSH public key combined with an SSH certificate. +/// A ``NIOSSHCertifiedPublicKey`` is an SSH public key combined with an SSH certificate. /// /// SSH has a non-standard interface for using a very basic form of certificate-based authentication. /// This is largely defined by OpenSSH, with the specification written down in [`PROTOCOL.certkeys`](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD) in the @@ -38,11 +38,11 @@ import Glibc /// enterprises. /// /// In the SSH protocol itself, certificates fit into a slightly strange space. They are essentially a (weird) -/// key type on the wire, with more information available to them. To mirror this use-case, a `NIOSSHCertifiedPublicKey` +/// key type on the wire, with more information available to them. To mirror this use-case, a ``NIOSSHCertifiedPublicKey`` /// is never directly passed to or from any of the interfaces in SwiftNIO SSH. Instead, it has an optional constructor -/// from `NIOSSHPublicKey` and `NIOSSHPublicKey` has a non-failing constructor from this key type. This allows users -/// to check at runtime whether a given `NIOSSHPublicKey` is _actually_ a `NIOSSHCertifiedPublicKey`, and allows -/// users that have a `NIOSSHCertifiedPublicKey` to use it as though it were a `NIOSSHPublicKey`. +/// from ``NIOSSHPublicKey`` and ``NIOSSHPublicKey`` has a non-failing constructor from this key type. This allows users +/// to check at runtime whether a given ``NIOSSHPublicKey`` is _actually_ a ``NIOSSHCertifiedPublicKey``, and allows +/// users that have a ``NIOSSHCertifiedPublicKey`` to use it as though it were a ``NIOSSHPublicKey``. public struct NIOSSHCertifiedPublicKey { /// A CA-provided random bitstring of arbitrary length (typically 16 or 32 bytes). This defends against /// hash-collision attacks. @@ -102,8 +102,10 @@ public struct NIOSSHCertifiedPublicKey { } } - /// The principals for which this certified key is valid. For keys with `.type == .host`, these will be hostnames. - /// For keys with `.type == .user`, these will be usernames. + /// The principals for which this certified key is valid. For keys where ``NIOSSHCertifiedPublicKey/type`` + /// is ``NIOSSHCertifiedPublicKey/CertificateType/host``, these will be hostnames. For keys where + /// ``NIOSSHCertifiedPublicKey/type`` is ``NIOSSHCertifiedPublicKey/CertificateType/user``, these will be + /// usernames. /// /// If this is empty, the certificate is valid for _any_ principal of the given type. public var validPrincipals: [String] { @@ -141,14 +143,15 @@ public struct NIOSSHCertifiedPublicKey { } /// Critical options are extensions that indicate restrictions on the default permissions of a certificate. These - /// are most commonly used for certificates of `.user` type. + /// are most commonly used for certificates of ``NIOSSHCertifiedPublicKey/CertificateType/user`` type. /// /// These options are critical in the sense that critical options not recognised by an implementation must lead to the /// certificate being not trusted. SwiftNIO does not police these options itself, and relies on users to do so. The /// certificate validation APIs provide hooks to allow implementations to identify which options they are willing to /// police. /// - /// The two critical options defined in the specification are only usable for `.user` certificates, and are: + /// The two critical options defined in the specification are only usable for + /// ``NIOSSHCertifiedPublicKey/CertificateType/user`` certificates, and are: /// /// - `force-command`: Specifies a command that will be executed whenever this certificate is used for auth, replacing /// anything the user selected. @@ -176,7 +179,7 @@ public struct NIOSSHCertifiedPublicKey { } } - /// The public key corresponding to the private key used to sign this `NIOSSHCertifiedPublicKey`. + /// The public key corresponding to the private key used to sign this ``NIOSSHCertifiedPublicKey``. public var signatureKey: NIOSSHPublicKey { get { self.backing.signatureKey @@ -227,7 +230,7 @@ public struct NIOSSHCertifiedPublicKey { signature: signature) } - /// Attempt to unwrap a `NIOSSHPublicKey` that may contain a `NIOSSHCertifiedPublicKey`. + /// Attempt to unwrap a ``NIOSSHPublicKey`` that may contain a ``NIOSSHCertifiedPublicKey``. /// /// Not all public keys are certified, so this method will fail if the key is not. public init?(_ key: NIOSSHPublicKey) { @@ -388,10 +391,12 @@ extension NIOSSHCertifiedPublicKey: CustomDebugStringConvertible { } extension NIOSSHCertifiedPublicKey { - /// A `NIOSSHCertifiedPublicKey.CertificateType` defines the type of a given certificate. + /// A ``NIOSSHCertifiedPublicKey/CertificateType`` defines the type of a given certificate. /// - /// In SSH there are essentially two types in standard use: `.user` and `.host`. Certificates of type - /// `.user` identify a user, while certificates of type `.host` identify a host. + /// In SSH there are essentially two types in standard use: ``NIOSSHCertifiedPublicKey/CertificateType/user`` + /// and ``NIOSSHCertifiedPublicKey/CertificateType/host``. Certificates of type + /// ``NIOSSHCertifiedPublicKey/CertificateType/user`` identify a user, while certificates of + /// type ``NIOSSHCertifiedPublicKey/CertificateType/host`` identify a host. /// /// For extensibility purposes this is not defined as an enumeration, but instead as a `RawRepresentable` type /// wrapping the base type. diff --git a/Sources/NIOSSH/Keys And Signatures/NIOSSHPublicKey.swift b/Sources/NIOSSH/Keys And Signatures/NIOSSHPublicKey.swift index efa6206..820d0d2 100644 --- a/Sources/NIOSSH/Keys And Signatures/NIOSSHPublicKey.swift +++ b/Sources/NIOSSH/Keys And Signatures/NIOSSHPublicKey.swift @@ -31,7 +31,7 @@ public struct NIOSSHPublicKey: Hashable { self.backingKey = backingKey } - /// Create a `NIOSSHPublicKey` from the OpenSSH public key string. + /// Create a ``NIOSSHPublicKey`` from the OpenSSH public key string. public init(openSSHPublicKey: String) throws { // The OpenSSH public key format is like this: "algorithm-id base64-encoded-key comments" // @@ -56,7 +56,10 @@ public struct NIOSSHPublicKey: Hashable { self = key } - /// Encapsulate a `NIOSSHCertifiedPublicKey` in a `NIOSSHPublicKey`. + /// Encapsulate a ``NIOSSHCertifiedPublicKey`` in a ``NIOSSHPublicKey``. + /// + /// This initializer can be used to "wrap" a ``NIOSSHCertifiedPublicKey`` into the interface of ``NIOSSHPublicKey``. + /// It is typically used in cases where the fact that the key is certified is not relevant. public init(_ certifiedKey: NIOSSHCertifiedPublicKey) { self.backingKey = .certified(certifiedKey) } diff --git a/Sources/NIOSSH/NIOSSHError.swift b/Sources/NIOSSH/NIOSSHError.swift index 28aab9f..81ecfac 100644 --- a/Sources/NIOSSH/NIOSSHError.swift +++ b/Sources/NIOSSH/NIOSSHError.swift @@ -14,15 +14,16 @@ /// An error thrown by NIOSSH. /// -/// For extensibility purposes, `NIOSSHError`s are composed of two parts. The first part is an +/// For extensibility purposes, ``NIOSSHError``s are composed of two parts. The first part is an /// error type. This is like an enum, but extensible, and identifies the kind of error programmatically. /// The second part is some opaque diagnostic data. This is not visible to your code, but is used to /// help provide extra information for diagnostic purposes when logging this error. /// -/// Note that due to this construction `NIOSSHError` is not equatable: only the `type` is. This is deliberate, +/// Note that due to this construction ``NIOSSHError`` is not equatable: only the ``NIOSSHError/type`` is. This is deliberate, /// as it is possible two errors have the same type but a different underlying cause or diagnostic data. For -/// this reason, if you need to compare two `NIOSSHError` values you should explicitly compare their `type`. +/// this reason, if you need to compare two ``NIOSSHError`` values you should explicitly compare their ``NIOSSHError/type``. public struct NIOSSHError: Error { + /// The type of this error, used to identify the kind of error that has been thrown. public var type: ErrorType private var diagnostics: String? diff --git a/Sources/NIOSSH/NIOSSHHandler.swift b/Sources/NIOSSH/NIOSSHHandler.swift index 0428851..e7affe3 100644 --- a/Sources/NIOSSH/NIOSSHHandler.swift +++ b/Sources/NIOSSH/NIOSSHHandler.swift @@ -57,6 +57,12 @@ public final class NIOSSHHandler { private var pendingGlobalRequestResponses: CircularBuffer + /// Construct a new ``NIOSSHHandler``. + /// + /// - parameters: + /// - role: The role of this channel in the connection, client or server. + /// - allocator: An allocator for `ByteBuffer`s + /// - inboundChildChannelInitializer: A callback that will be invoked whenever the remote peer attempts to construct a new SSH channel in a connection. public init(role: SSHConnectionRole, allocator: ByteBufferAllocator, inboundChildChannelInitializer: ((Channel, SSHChannelType) -> EventLoopFuture)?) { self.stateMachine = SSHConnectionStateMachine(role: role) self.pendingWrite = false @@ -231,7 +237,7 @@ extension NIOSSHHandler { /// /// - parameters: /// - promise: An `EventLoopPromise` that will be fulfilled with the channel when it becomes active. - /// - channelType: The type of the channel to create. Defaults to `.session` for running remote processes. + /// - channelType: The type of the channel to create. Defaults to ``SSHChannelType/session`` for running remote processes. /// - channelInitializer: A callback that will be invoked to initialize the channel. public func createChannel(_ promise: EventLoopPromise? = nil, channelType: SSHChannelType = .session, _ channelInitializer: ((Channel, SSHChannelType) -> EventLoopFuture)?) { self.pendingChannelInitializations.append((promise: promise, channelType: channelType, initializer: channelInitializer)) diff --git a/Sources/NIOSSH/Role.swift b/Sources/NIOSSH/Role.swift index 2af2818..d118ab0 100644 --- a/Sources/NIOSSH/Role.swift +++ b/Sources/NIOSSH/Role.swift @@ -14,7 +14,10 @@ /// The role of a given party in an SSH connection. public enum SSHConnectionRole { + /// This entity is an SSH client. case client(SSHClientConfiguration) + + /// This entity is an SSH server. case server(SSHServerConfiguration) internal var isClient: Bool { diff --git a/Sources/NIOSSH/SSHServerConfiguration.swift b/Sources/NIOSSH/SSHServerConfiguration.swift index fcd8ed0..5c8f7b6 100644 --- a/Sources/NIOSSH/SSHServerConfiguration.swift +++ b/Sources/NIOSSH/SSHServerConfiguration.swift @@ -41,17 +41,13 @@ public struct SSHServerConfiguration { // MARK: - UserAuthBanner extension SSHServerConfiguration { - /** - Server sends `UserAuthBanner` to client some time during authentication. - Client is obligated to display this banner to the end user, unless explicitely told - to ignore banners. - */ + /// A server sends a ``UserAuthBanner`` to the client at some point during authentication. + /// A client is obligated to display this banner to the end user, unless explicitely told + /// to ignore banners. public struct UserAuthBanner { - /** - The message to be displayed by client to end user during authentication. - Note that control characters contained in message might be filtered by - client in accordance with RFC 4252. - */ + // The message to be displayed by the client to the end user during authentication. + // Note that control characters contained in the message might be filtered by + // the client in accordance with RFC 4252. public var message: String /// Tag describing the language used for message. Must obey RFC 3066 diff --git a/Sources/NIOSSH/SSHTerminalModes.swift b/Sources/NIOSSH/SSHTerminalModes.swift index 3d7c9cb..84380aa 100644 --- a/Sources/NIOSSH/SSHTerminalModes.swift +++ b/Sources/NIOSSH/SSHTerminalModes.swift @@ -19,6 +19,7 @@ import NIOCore /// expected to express all modes it knows about, with the server ignoring anything that it /// does not know anything about. public struct SSHTerminalModes { + /// The set ``Opcode``s and their ``OpcodeValue``s. public var modeMapping: [Opcode: OpcodeValue] public init(_ modeMapping: [Opcode: OpcodeValue]) { diff --git a/Sources/NIOSSH/User Authentication/ClientUserAuthenticationDelegate.swift b/Sources/NIOSSH/User Authentication/ClientUserAuthenticationDelegate.swift index 53070ec..58f81e4 100644 --- a/Sources/NIOSSH/User Authentication/ClientUserAuthenticationDelegate.swift +++ b/Sources/NIOSSH/User Authentication/ClientUserAuthenticationDelegate.swift @@ -14,7 +14,7 @@ import NIOCore -/// A `NIOSSHClientUserAuthenticationDelegate` is an object that can provide a sequence of +/// A ``NIOSSHClientUserAuthenticationDelegate`` is an object that can provide a sequence of /// SSH user authentication methods based on the the acceptable list from the server. /// /// This protocol defines the interface that will be used by the user authentication state @@ -24,5 +24,15 @@ import NIOCore /// enabled by allowing implementers to satisfy a promise, rather than requiring that they /// synchronously provide a response. public protocol NIOSSHClientUserAuthenticationDelegate { + /// Called when ``NIOSSH`` would like to attempt to offer a new authentication method. + /// + /// The callback is provided the authentictation methods that the server is willing to accept in + /// `availableMethods`. The delegate needs to provide an authentication offer by completing + /// `nextChallengePromise`. If no further authentication offers are available (perhaps because the server + /// has rejected them all) then this promise should be failed, which will terminate connection establishment. + /// + /// - parameters: + /// - availableMethods: The authentication methods the server is willing to accept. + /// - nextChallengePromise: An `EventLoopPromise` to be fulfilled with the next authentication offer. func nextAuthenticationType(availableMethods: NIOSSHAvailableUserAuthenticationMethods, nextChallengePromise: EventLoopPromise) } diff --git a/Sources/NIOSSH/User Authentication/ServerUserAuthenticationDelegate.swift b/Sources/NIOSSH/User Authentication/ServerUserAuthenticationDelegate.swift index 5512273..c6186a1 100644 --- a/Sources/NIOSSH/User Authentication/ServerUserAuthenticationDelegate.swift +++ b/Sources/NIOSSH/User Authentication/ServerUserAuthenticationDelegate.swift @@ -14,7 +14,7 @@ import NIOCore -/// A `NIOSSHServerUserAuthenticationDelegate` is an object that can authorize users. +/// A ``NIOSSHServerUserAuthenticationDelegate`` is an object that can authorize users. /// /// This protocol defines the interface that will be used by the user authentication state /// machine to move forward with challenges. Implementers of this protocol are free to take @@ -25,11 +25,14 @@ import NIOCore /// Implementers should be aware that multiple authentication requests may be in flight at once: /// they must be responded to in order. It is up to implementers to meet this requirement, it is /// not enforced by the implementation. -/// -/// This is currently an internal protocol, but we may make it available to users in future once -/// we feel confident that it covers our needs. For now, all implementers are internal. public protocol NIOSSHServerUserAuthenticationDelegate { + /// The authentication methods this delegate is willing to receive. var supportedAuthenticationMethods: NIOSSHAvailableUserAuthenticationMethods { get } + /// A user authentication request has been received. + /// + /// - parameters: + /// - request: The received user authentication request + /// - responsePromise: An `EventLoopPromise` that must be completed with the outcome of the user auth attempt. func requestReceived(request: NIOSSHUserAuthenticationRequest, responsePromise: EventLoopPromise) } diff --git a/Sources/NIOSSH/User Authentication/SimplePasswordDelegate.swift b/Sources/NIOSSH/User Authentication/SimplePasswordDelegate.swift index 7e8be80..d956d5b 100644 --- a/Sources/NIOSSH/User Authentication/SimplePasswordDelegate.swift +++ b/Sources/NIOSSH/User Authentication/SimplePasswordDelegate.swift @@ -14,7 +14,7 @@ import NIOCore -/// A straightforward `NIOSSHUserAuthenticationDelegate` that makes one attempt to sign in with a single username and password combination. +/// A straightforward ``NIOSSHServerUserAuthenticationDelegate`` that makes one attempt to sign in with a single username and password combination. public final class SimplePasswordDelegate { private var authRequest: NIOSSHUserAuthenticationOffer? diff --git a/Sources/NIOSSH/User Authentication/UserAuthenticationMethod.swift b/Sources/NIOSSH/User Authentication/UserAuthenticationMethod.swift index c8f28e3..a03c3b1 100644 --- a/Sources/NIOSSH/User Authentication/UserAuthenticationMethod.swift +++ b/Sources/NIOSSH/User Authentication/UserAuthenticationMethod.swift @@ -14,6 +14,9 @@ import NIOCore /// The user authentication modes available at this point in time. +/// +/// User authentication in SSH proceeds in a dynamic fashion, and it is possible to require multiple forms +/// of authentication sequentially, or to be able to accept one of many forms. public struct NIOSSHAvailableUserAuthenticationMethods: OptionSet { public var rawValue: UInt8 @@ -21,10 +24,16 @@ public struct NIOSSHAvailableUserAuthenticationMethods: OptionSet { self.rawValue = rawValue } + /// Public key authentication is acceptable. public static let publicKey: NIOSSHAvailableUserAuthenticationMethods = .init(rawValue: 1 << 0) + + /// Password-based authentication is acceptable. public static let password: NIOSSHAvailableUserAuthenticationMethods = .init(rawValue: 1 << 1) + + /// Host-based authentication is acceptable. public static let hostBased: NIOSSHAvailableUserAuthenticationMethods = .init(rawValue: 1 << 2) + /// A short-hand for all supported authentication types. public static let all: NIOSSHAvailableUserAuthenticationMethods = [.publicKey, .password, .hostBased] } @@ -73,10 +82,12 @@ extension NIOSSHAvailableUserAuthenticationMethods { extension NIOSSHAvailableUserAuthenticationMethods: Hashable {} /// A specific request for user authentication. This type is the one observed from the server side. The -/// associated client side type is `NIOSSHUserAuthenticationOffer`. +/// associated client side type is ``NIOSSHUserAuthenticationOffer``. public struct NIOSSHUserAuthenticationRequest { + /// The username for which the client would like to authenticate. public var username: String + /// The specific authentication request. public var request: Request public init(username: String, serviceName: String, request: Request) { @@ -86,16 +97,28 @@ public struct NIOSSHUserAuthenticationRequest { } extension NIOSSHUserAuthenticationRequest { + /// ``NIOSSHUserAuthenticationRequest/Request-swift.enum`` describes the kind of authentication attempt the client is making. public enum Request { + /// The client would like to perform public key authentication. case publicKey(PublicKey) + + /// The client would like to perform password authentication. case password(Password) + + /// The client would like to perform host-based authentication. + /// + /// This method is currently unsupported by ``NIOSSH``. case hostBased(HostBased) + + /// The client believes it does not need authentication. case none } } extension NIOSSHUserAuthenticationRequest.Request { + /// Information provided by the client when attempting to perform a public-key authentication. public struct PublicKey { + /// The user's public key. public var publicKey: NIOSSHPublicKey public init(publicKey: NIOSSHPublicKey) { @@ -103,7 +126,9 @@ extension NIOSSHUserAuthenticationRequest.Request { } } + /// Information provided by the client when attempting to perform password-based authentication. public struct Password { + /// The user's password. public var password: String public init(password: String) { @@ -111,6 +136,9 @@ extension NIOSSHUserAuthenticationRequest.Request { } } + /// Information provided by the client when attempting to perform host-based authentication. + /// + /// This method is currently unsupported by ``NIOSSH``. public struct HostBased { init() { fatalError("PublicKeyRequest is currently unimplemented") @@ -129,10 +157,12 @@ extension NIOSSHUserAuthenticationRequest.Request.Password: Hashable {} extension NIOSSHUserAuthenticationRequest.Request.HostBased: Hashable {} /// A specific offer of user authentication. This type is the one used on the client side. The -/// associated server side type is `NIOSSHUserAuthenticationRequest`. +/// associated server side type is ``NIOSSHUserAuthenticationRequest``. public struct NIOSSHUserAuthenticationOffer { + /// The username for which the client would like to authenticate. public var username: String + /// The specific authentication offer. public var offer: Offer public init(username: String, serviceName: String, offer: Offer) { @@ -142,17 +172,35 @@ public struct NIOSSHUserAuthenticationOffer { } extension NIOSSHUserAuthenticationOffer { + /// ``NIOSSHUserAuthenticationOffer/Offer-swift.enum`` describes the kind of authentication offer the client is making. public enum Offer { + /// The client would like to perform private key authentication. case privateKey(PrivateKey) + + /// The client would like to perform password authentication. case password(Password) + + /// The client would like to perform host-based authentication. + /// + /// This method is currently unsupported by ``NIOSSH``. case hostBased(HostBased) + + /// The client believes it does not need authentication. case none } } extension NIOSSHUserAuthenticationOffer.Offer { + /// Information provided by the client when attempting to perform private key authentication. public struct PrivateKey { + /// The client's private key. + /// + /// This is not sent to the server, but is used by ``NIOSSH`` to respond to auth challenges. public var privateKey: NIOSSHPrivateKey + + /// The client's public key. + /// + /// This is sent to the server. public var publicKey: NIOSSHPublicKey public init(privateKey: NIOSSHPrivateKey) { @@ -166,7 +214,9 @@ extension NIOSSHUserAuthenticationOffer.Offer { } } + /// Information provided by the client when attempting to perform password-based authentication. public struct Password { + /// The client's password. public var password: String public init(password: String) { @@ -174,6 +224,9 @@ extension NIOSSHUserAuthenticationOffer.Offer { } } + /// Information provided by the client when attempting to perform host-based authentication. + /// + /// This method is currently unsupported by ``NIOSSH``. public struct HostBased { init() { fatalError("PublicKeyRequest is currently unimplemented") @@ -209,8 +262,15 @@ extension SSHMessage.UserAuthRequestMessage { /// The outcome of a user authentication attempt. public enum NIOSSHUserAuthenticationOutcome { + /// The authentication attempt succeeded and the client is authenticated. case success + + /// The authentication attempt partially succeeded, but additional authentication is required. + /// + /// The additional authentication requirements are described in `remainingMethods`. case partialSuccess(remainingMethods: NIOSSHAvailableUserAuthenticationMethods) + + /// The authentication attempt failed. case failure } diff --git a/scripts/soundness.sh b/scripts/soundness.sh index 862af4b..49706d1 100755 --- a/scripts/soundness.sh +++ b/scripts/soundness.sh @@ -65,7 +65,7 @@ for language in swift-or-c bash dtrace; do matching_files=( -name '*' ) case "$language" in swift-or-c) - exceptions=( -name c_nio_http_parser.c -o -name c_nio_http_parser.h -o -name cpp_magic.h -o -name Package.swift -o -name CNIOSHA1.h -o -name c_nio_sha1.c -o -name ifaddrs-android.c -o -name ifaddrs-android.h) + exceptions=( -name c_nio_http_parser.c -o -name c_nio_http_parser.h -o -name cpp_magic.h -o -name Package.swift -o -name CNIOSHA1.h -o -name c_nio_sha1.c -o -name ifaddrs-android.c -o -name ifaddrs-android.h -o -name 'Package@swift*.swift' ) matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' ) cat > "$tmp" <<"EOF" //===----------------------------------------------------------------------===//