Open
Conversation
This allows delegating signing to an agent in cases where the private key is not available to the client and signing must be performed through, e.g. a socket connected to a forwarded ssh agent.
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Add SSH Agent support
This adds support for delegated signing in
NIOSSHPrivateKey.Closes #189
Background
NIOSSHPrivateKeycurrently requires direct access to the private key at construction time.Every initialiser takes a concrete
CryptoKitkey (Curve25519.Signing.PrivateKey,P256.Signing.PrivateKey, etc.) and all signing methods operate on that in-memory key.This makes it impossible to implement public-key authentication through, say, an SSH agent,
hardware security module, or any other system where the private key is not directly accessible
to the caller. A
NIOSSHClientUserAuthenticationDelegatethat wants to use an agent-held keyhas no way to produce a valid
NIOSSHPrivateKey. This is because the agent only exposes apublic key and a sign operation — it never reveals the private key bytes.
NIOSSHPrivateKey.BackingKeyis a closed enum with one case per concrete key type. There isno case that pairs a public key with an external signing operation, and no public API on
NIOSSHSignatureto construct a signature from raw bytes returned by an agent.Internally, two signing paths exist:
sign(digest:)— used during key exchange (host key proof); operates on a pre-hashed digest.sign(_: UserAuthSignablePayload)— used during user authentication; operates on the raw signable payload bytes.An external signer only needs to support path (2), since it is the client user-auth path.
Path (1) is server-side host key proving and does not apply to agent-held keys.
Relationship to PR #220
These two proposals were developed independently in parallel and I only saw it just now,
before submitting this PR. Both approaches have their pros and cons.
PR #220 proposes an
NIOSSHExternalSignerprotocol for the same problem. This PR takes adeliberately simpler approach — a single
@Sendableclosure instead of a protocol — tominimise the API surface and avoid introducing new types. The two approaches are functionally
equivalent; this one is smaller and requires no new protocol conformances from callers.
Modifications
NIOSSHPrivateKey.signingDelegate(@Sendable (ByteBufferView) throws -> NIOSSHSignature, NIOSSHPublicKey)case toBackingKey.public init(publicKey:signingCallback:)so callers can create a private key backed by an external signer.hostKeyAlgorithms,publicKey, and bothsignmethods:sign(_: UserAuthSignablePayload)delegates to the callback.sign(digest:)throws — this path is for host key exchange and does not apply to agent-held client keys.BackingKeyasSendable(required by the@Sendableclosure).NIOSSHSignatureNIOSSHSignaturefrom raw signature bytes:.ed25519(signature:).ecdsaP256(signature:).ecdsaP384(signature:).ecdsaP521(signature:)Status
Callers can authenticate via public-key auth without holding private key material. For
example, an SSH agent client can list the agent's keys, wrap each in an
NIOSSHPrivateKeywith a signing callback that forwards the sign request to the agent, and pass it to
NIOSSHClientUserAuthenticationDelegateas normal.I have tried to keep this a minimal, non-breaking change without altering existing API
surface.
Limitations
Delegated signing only works with key types that NIOSSH already supports — Ed25519, ECDSA
P-256, P-384, and P-521 — because
NIOSSHPublicKeyandNIOSSHSignatureare also closedenums over the same set. An SSH agent may hold (or an SSH server may request) RSA keys,
but NIOSSH has no RSA support (swift-crypto does not provide RSA signing), so those keys
cannot be represented as an
NIOSSHPublicKeyin the first place.Adding RSA would require broader changes across the key, signature, and
algorithm-negotiation layers, which is out of scope here.
Examples
Example usage
Example project
This has been tested with a simple Swift ssh client CLI tool that replicates the
corresponding functionality of the ssh client:
https://github.com/rhx/swift-ssh-client