Skip to content

feat: support for external signers in NIOSSHPrivateKey - #220

Open
camunro wants to merge 2 commits into
apple:mainfrom
camunro:pr/ssh-agent-hooks
Open

feat: support for external signers in NIOSSHPrivateKey#220
camunro wants to merge 2 commits into
apple:mainfrom
camunro:pr/ssh-agent-hooks

Conversation

@camunro

@camunro camunro commented Feb 16, 2026

Copy link
Copy Markdown

Support for External Signers in NIOSSHPrivateKey

Motivation

This PR enables hardware-backed authentication and remote identity protocols by allowing NIOSSHPrivateKey to delegate signing operations to external providers (e.g., Secure Enclave, TPM, or ssh-agent) without requiring raw private key material to be loaded into memory.

This is a foundational architectural change that enables several high-level features for the Swift ecosystem:

  • SSH Agent Protocol: Provides the necessary hooks for a client to list keys from ssh-agent and delegate the sign request back to the agent. (Add SSH Agent Support #189)
  • Hardware Security (Secure Enclave / TPM): Allows keys stored in the Apple Secure Enclave or a TPM to sign SSH challenges via Biometrics (TouchID/FaceID).
  • PKCS#11 / Smart Cards: Enables integration with physical security tokens and other non-exportable identities.

Modifications

  • New Protocol: Defined NIOSSHExternalSigner, which defines a standardized interface for delegated signing operations.
  • NIOSSHPrivateKey Evolution: Added an .external(NIOSSHExternalSigner) case to NIOSSHPrivateKey for all supported algorithms:
    • Ed25519
    • ECDSA (NIST P-256, P-384, and P-521)
  • Error Handling: Added NIOSSHError.externalSignerFailure to provide clear feedback and diagnostic info when an external delegate fails.
  • Documentation: Updated Docs.docc to provide guidance on implementing external signers for hardware-backed security.
  • Testing: Added NIOSSHExternalSignerTests.swift covering:
    • Mock external signers for all supported curves.
    • Verification that signatures produced by delegates are correctly handled by the SSH state machine.
    • Validation of error propagation when the external signer throws.

Result

NIOSSH now supports public-key authentication where the signing operation is handled by an external delegate. This provides the critical architectural hook required for modern identity-aware SSH implementations (like #189) on both Apple and Linux platforms.

@polanski13

Copy link
Copy Markdown

Thanks for putting this together - external signing is a need I've been carrying via a fork for a while.

One adjacent case I'm not sure falls under this PR: support for rsa-sha2-512 / rsa-sha2-256 at the public-key-algorithm level (RFC 8332). Today that requires patching NIOSSHAlgorithms to register new public-key + signature protocol pairs, which is separate from delegating the signing call itself.

Would the .external(...) model here cover the algorithm-registration side as well, or is that a second step once this lands?

Motivation:

Enables hardware-backed authentication and remote identity protocols by
allowing NIOSSHPrivateKey to delegate signing operations to external
providers (e.g., Secure Enclave, TPM, or ssh-agent) without requiring
raw private key material in memory.

Modifications:

- Defined NIOSSHExternalSigner protocol for delegated signing.
- Added .external case to NIOSSHPrivateKey for all supported algorithms
  (Ed25519, P-256, P-384, P-521).
- Added NIOSSHError.externalSignerFailure for better error propagation.
- Updated documentation and added comprehensive tests for external
  signing logic.

Result:

NIOSSH now supports public-key authentication where the signing operation
is handled by an external delegate, providing a foundational hook for
ssh-agent and hardware security integrations.
@camunro
camunro force-pushed the pr/ssh-agent-hooks branch from 84567df to 380d619 Compare July 8, 2026 00:52
@camunro

camunro commented Jul 8, 2026

Copy link
Copy Markdown
Author

Hi @Lukasa (and maintainers),

I have rebased this branch onto the latest main to resolve any conflicts. It is ready for the test suite, but it looks like the workflows are currently awaiting a maintainer's approval to run.

To make the review of these SSH Agent hooks as lightweight as possible:

  • Objective: Adds delegate/hook support to allow swift-nio-ssh to integrate with external agent keys (e.g., local ssh-agent , hardware signers) during client handshakes.
  • Backward Compatibility: Fully preserved; public interfaces are non-breaking.
  • Verification: Added unit test coverage for the hook execution paths.

When you have a moment, could you approve the workflow runs and let me know if the overall API design aligns with your preferences?

Thanks!

@camunro

camunro commented Jul 13, 2026

Copy link
Copy Markdown
Author

Thanks for putting this together - external signing is a need I've been carrying via a fork for a while.

One adjacent case I'm not sure falls under this PR: support for rsa-sha2-512 / rsa-sha2-256 at the public-key-algorithm level (RFC 8332). Today that requires patching NIOSSHAlgorithms to register new public-key + signature protocol pairs, which is separate from delegating the signing call itself.

Would the .external(...) model here cover the algorithm-registration side as well, or is that a second step once this lands?

Thanks for raising this. The current #220 does not cover algorithm registration; it delegates signing only for key/signature algorithms that NIOSSH already represents.

RSA requires additional handling because a public key using the ssh-rsa key format may sign using rsa-sha2-256 or rsa-sha2-512. The current implementation derives a single algorithm from publicKey.keyPrefix and does not explicitly pass a selected RSA signature algorithm to the external signer.

Therefore RFC 8332 support would be a separate step, likely built with #219 or equivalent RSA support. If RSA lands, this external-signer hook would need follow-up integration—potentially an explicit algorithm parameter or typed signing request.

I have not tried to solve that speculatively in #220, but I will document the limitation. Maintainer guidance on whether the external-signer API should anticipate multiple algorithms per key would be welcome.

@Lukasa Lukasa added the 🆕 semver/minor Adds new public API. label Jul 20, 2026

@Lukasa Lukasa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this patch, and apologies for the substantial delay in getting around to the review.

Before we go much further, I want to ask a fundamental API question: do we want this API to be asynchronous? My instinct is that if we can do so it would be very valuable, but it does substantially raise the cost of implementation. The major advantage here is that it enables keys to be delegated to substantially more interesting places, such as cloud provider KMSes or other remote locations.

The big downside is that it forces the code path here to be made asynchronous, to tolerate resumption. What are your instincts here @camunro?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🆕 semver/minor Adds new public API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants