Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/openssh-compatibility-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ This ledger records contracts still requiring implementation or verification, no
| Percent/environment | Directive-specific tokens and environment expansion, trusted `Match exec`, explicitly permitted shell `LocalCommand` | Historical `percent` fails on both platforms. Shell semantics must retain `PermitLocalCommand` gating and untrusted-input boundaries. |
| Rekey | Actual byte/time-triggered exchange and truthful KEX/NEWKEYS diagnostics | `rekey` passes on Linux (114.3 s candidate, 114.5 s reference) after the client stopped padding every connection by 0.6 s; the manifest minimum is 180 s. macOS remains environmental (the reference client also exceeds the limit there). |
| Agent | Destination-constrained identities (`session-bind@openssh.com`) | Every `agent-restrict` phase that does not need a constrained key passes on Linux; the eight constrained phases fail until russh exposes the binding tuple. Tracked in #327. |
| Security keys | `webauthn-sk-*` signatures, resident-key and enrollment tooling | `hostkey-agent`, `keytype`, `limit-keytype` and `agent` pass on Linux with the regress `sk-dummy` middleware. The built-in `SecurityKeyProvider internal` (the default) signs over USB HID on Linux and macOS (#331) and is covered by mock-transport tests; a check against a physical authenticator is pending user validation. `webauthn-sk-ecdsa-sha2-nistp256@openssh.com` is not advertised because bssh cannot verify it (#345), and `bssh-keygen` cannot enroll or list resident keys. |
| Security keys | `webauthn-sk-*` signatures and physical-authenticator validation | `hostkey-agent`, `keytype`, `limit-keytype` and `agent` pass on Linux with the regress `sk-dummy` middleware. The built-in `SecurityKeyProvider internal` (the default) signs over USB HID on Linux and macOS (#331) and is covered by mock-transport tests; a check against a physical authenticator is pending user validation. `webauthn-sk-ecdsa-sha2-nistp256@openssh.com` is not advertised because bssh cannot verify it (#345). `bssh-keygen` enrolls both SK key types and downloads resident credentials with `-K` through internal and external providers (#337); mock-device, native ABI, and OpenSSH key-file parsing tests cover these paths. |
| Multiplexing | OpenSSH mux-v4 local protocol, raw `-O proxy`, complete channel reporting, allocated remote-port replies | `multiplex` passes on Linux (72.1 s) including TCP and Unix-domain `-O forward`/`-O cancel`, when the regress directory path fits `sockaddr_un`. Control sockets still use framed JSON; passenger reuse is not OpenSSH-master interoperability. |
| Jump hosts | CLI/config parsing, SSH URI hops, `none`, multihop transport/authentication | Historical `proxyjump` fails on both platforms. Parser tests alone do not establish transport behavior. |
| Remote copy | Remote-to-remote copy invocation, transport selection, integrity and exit status | Historical Ubuntu `scp3` fails; macOS environmental. |
Expand All @@ -39,7 +39,7 @@ Host certificates are accepted only after full validation: a `@cert-authority` l

## Security keys

Authenticator-hosted (`sk-ssh-ed25519@openssh.com`, `sk-ecdsa-sha2-nistp256@openssh.com`) keys follow OpenSSH 10.3p1 built with `ENABLE_SK_INTERNAL`, as distribution builds are. `SecurityKeyProvider` defaults to `internal`, the built-in USB HID provider (`-G` prints `securitykeyprovider internal`, as the reference does); a `$`-prefixed value names an environment variable, an unset variable or `none` disables `sk-*` identities (logged at debug level, as `ssh` does), and a path names an external provider. The built-in provider (`src/ssh/security_key/internal`) is `sk-usbhid.c` without libfido2: an in-tree CTAPHID, CTAP2 and U2F stack over hidapi (its pure-Rust hidraw backend on Linux, IOKit on macOS, no new runtime library). It follows `sk_sign` step by step: devices on the FIDO usage page are enumerated (each path once, at most eight, as `MAX_FIDO_DEVICES` caps them) and opened; without a PIN or `verify-required` the device holding the credential is found with a silent assertion per device (`sk_select_by_cred`, skipped for a lone CTAP2 device that reports its `uv` option), otherwise the device the user touches is chosen (`sk_select_by_touch`, 15 s, with the dummy `makeCredential`/`REGISTER` probe libfido2 uses, polling rounds 200 ms apart, and only success or a PIN error counted as a touch, so a device that refuses the probe drops out instead of winning); `getAssertion` carries `up` from the key flags and, for `verify-required` keys, either the CTAP 2.0 `uv` option or a `pinUvAuthParam` from a token obtained through `clientPIN` (PIN protocol 2 when advertised, else 1; PIN-based permission tokens use subcommand `0x09`, while CTAP 2.1 built-in verification uses `0x06` without a PIN hash), and a device without PIN support or built-in verification yields `SSH_SK_ERR_PIN_REQUIRED` so the PIN prompt follows; devices without CBOR support, or whose `getInfo` fails (after a timed-out `getInfo` the channel is allocated again so a late reply cannot answer the first U2F request), fall back to U2F check-only and enforce-presence `AUTHENTICATE` (ECDSA only), and are never sent `CTAPHID_CANCEL`, as libfido2 sends it to CTAP2 devices only. One deliberate deviation from libfido2, which waits without limit: a whole built-in signature is bounded at 120 s (sshd's default `LoginGraceTime`). bssh serializes provider calls process-wide, so a device that streams KEEPALIVE or other channels' frames forever, or a U2F key nobody touches, would otherwise stall authentication to every host; on expiry the pending CTAP2 request is cancelled, the signature fails with `timed out waiting for the security key`, and authentication continues with the next identity. The resulting `flags`, `counter`, and `sig_r`/`sig_s` (minimal big-endian, as `BN_bn2bin` produces) are the same bytes the external provider path hands to `sshsk_sign`. The CTAPHID framing, CBOR, PIN protocols, U2F encoding, device selection, and the end-to-end signature (verified with `ssh-key`) are tested against a mock HID transport; CI has no authenticator, so a manual check with a physical key on Linux and macOS is pending user validation. Enrollment (`ssh-keygen -t ed25519-sk`) and resident-key loading (`ssh-keygen -K`) are not implemented. A U2F-only device cannot take a PIN, so a `verify-required` key on it cannot sign, and Ed25519 keys cannot sign over U2F at all. On Linux the provider reads `/dev/hidraw*` nodes, which are usually not accessible to an ordinary user until a udev rule grants access (the FIDO udev rules distributed with the libfido2 packages on Debian and Ubuntu serve this purpose); when devices are found but none can be opened, the signature fails with `N FIDO device(s) found but none could be opened: <path>: <error>`. On macOS devices are opened non-exclusively (hidapi `macos-shared-device`, as libfido2 does), so a browser can use the key meanwhile. On other platforms (Windows) the built-in provider is not available: a signature fails with `built-in security key support is not available on this platform; set SecurityKeyProvider to an sk-api library`. An external provider is a shared library implementing `sk-api.h` version `0x000a0000` (`sk_api_version`, `sk_enroll`, `sk_sign`, `sk_load_resident_keys`); bssh loads it in process with `dlopen(RTLD_NOW | RTLD_LOCAL)`, as `sshsk_open` does with `RTLD_NOW`, so a library with an unresolved symbol is refused at load time; each library is opened once per canonical path and kept for the life of the process instead of being reopened and closed for every identity and connection, and a bare file name refers to a file relative to the working directory (OpenSSH's `lib_contains_symbol` opens the path directly) rather than one found through the loader search path. bssh calls `sk_sign` on a blocking thread, printing `Confirm user presence for key ...` when the key requires a touch and stderr is a terminal, and prompting once for a PIN when the provider reports `SSH_SK_ERR_PIN_REQUIRED` outside `BatchMode`. OpenSSH runs the same calls in `ssh-sk-helper` to keep the middleware out of its `pledge`d client; bssh has no such sandbox, so a helper would add a private protocol without changing what runs as the user. Running in process has accepted costs: an `sk_sign` call that has started cannot be cancelled, so a hung provider keeps its blocking thread and the process-wide provider lock until it returns and no other security-key signature in the process can proceed meanwhile (connections queued behind it give up at `ConnectTimeout` without reaching the provider); and the provider can read everything bssh holds in memory, including decrypted keys and passwords for other hosts. A key without an application string is refused before the provider is called, as `sshsk_sign` refuses it, and an Ed25519 response must carry exactly 64 bytes in `sig_r` and nothing in `sig_s`. A provider that cannot be opened (missing file, no `sk_api_version`, wrong major version) is reported before the key is offered and authentication continues; a failure inside `sk_sign` after the server has accepted the key (device absent, PIN required in `BatchMode`, provider error) prints `security key signing failed for <path>: ...` and authentication continues with the next identity on the same connection, as after OpenSSH's `sign_and_send_pubkey` failure: the signer hands russh the request back unsigned, russh sends nothing for it, and bssh stops waiting for the reply. Provider calls are serialized process-wide, because sk-api makes no thread-safety promise and bssh may authenticate to many hosts at once. An `sk-*` identity is checked against the provider from its unencrypted public half before any passphrase prompt, and `~/.ssh/id_ed25519_sk` and `~/.ssh/id_ecdsa_sk` are among the default identities. Static musl release builds cannot `dlopen`; they build, and report that limitation when an external provider is configured. Host keys and host certificates of the `sk-*` types are verified by ssh-key's `sk` verifiers (`SHA256(application) || flags || counter || SHA256(message)`), with no flag policy on the client side, as in OpenSSH. Agent-held `sk-*` keys keep signing through the agent. `-Q key-plain`, `-Q key`, `-Q key-cert`, `-Q key-sig`, `HostKeyAlgorithms` and `PubkeyAcceptedAlgorithms` list the `sk-*` names in the reference order; the remaining WebAuthn difference is tracked in [#345](https://github.com/lablup/bssh/issues/345).
Authenticator-hosted (`sk-ssh-ed25519@openssh.com`, `sk-ecdsa-sha2-nistp256@openssh.com`) keys follow OpenSSH 10.3p1 built with `ENABLE_SK_INTERNAL`, as distribution builds are. `SecurityKeyProvider` defaults to `internal`, the built-in USB HID provider (`-G` prints `securitykeyprovider internal`, as the reference does); a `$`-prefixed value names an environment variable, an unset variable or `none` disables `sk-*` identities (logged at debug level, as `ssh` does), and a path names an external provider. The built-in provider (`src/ssh/security_key/internal`) is `sk-usbhid.c` without libfido2: an in-tree CTAPHID, CTAP2 and U2F stack over hidapi (its pure-Rust hidraw backend on Linux, IOKit on macOS, no new runtime library). It follows `sk_sign` step by step: devices on the FIDO usage page are enumerated (each path once, at most eight, as `MAX_FIDO_DEVICES` caps them) and opened; without a PIN or `verify-required` the device holding the credential is found with a silent assertion per device (`sk_select_by_cred`, skipped for a lone CTAP2 device that reports its `uv` option), otherwise the device the user touches is chosen (`sk_select_by_touch`, 15 s, with the dummy `makeCredential`/`REGISTER` probe libfido2 uses, polling rounds 200 ms apart, and only success or a PIN error counted as a touch, so a device that refuses the probe drops out instead of winning); `getAssertion` carries `up` from the key flags and, for `verify-required` keys, either the CTAP 2.0 `uv` option or a `pinUvAuthParam` from a token obtained through `clientPIN` (PIN protocol 2 when advertised, else 1; PIN-based permission tokens use subcommand `0x09`, while CTAP 2.1 built-in verification uses `0x06` without a PIN hash), and a device without PIN support or built-in verification yields `SSH_SK_ERR_PIN_REQUIRED` so the PIN prompt follows; devices without CBOR support, or whose `getInfo` fails (after a timed-out `getInfo` the channel is allocated again so a late reply cannot answer the first U2F request), fall back to U2F check-only and enforce-presence `AUTHENTICATE` (ECDSA only), and are never sent `CTAPHID_CANCEL`, as libfido2 sends it to CTAP2 devices only. One deliberate deviation from libfido2, which waits without limit: a whole built-in signature is bounded at 120 s (sshd's default `LoginGraceTime`). bssh serializes provider calls process-wide, so a device that streams KEEPALIVE or other channels' frames forever, or a U2F key nobody touches, would otherwise stall authentication to every host; on expiry the pending CTAP2 request is cancelled, the signature fails with `timed out waiting for the security key`, and authentication continues with the next identity. The resulting `flags`, `counter`, and `sig_r`/`sig_s` (minimal big-endian, as `BN_bn2bin` produces) are the same bytes the external provider path hands to `sshsk_sign`. The CTAPHID framing, CBOR, PIN protocols, U2F encoding, device selection, and the end-to-end signature (verified with `ssh-key`) are tested against a mock HID transport; CI has no authenticator, so a manual check with a physical key on Linux and macOS is pending user validation. `bssh-keygen -t ed25519-sk` and `bssh-keygen -t ecdsa-sk` enroll credentials, and `bssh-keygen -K` downloads resident keys through either provider. Internal resident enrollment requires PIN-authenticated credential management to avoid replacing an existing application/user credential; protected enrollment requires `credProtect`. Signed packed/U2F registration attestations are verified without a vendor trust policy; unsigned `none` attestations remain permitted as in OpenSSH. Serialized handles are limited to 255 bytes by ssh-key, and `-O write-attestation=FILE` is explicitly unsupported. See [bssh-keygen options](man/bssh-keygen.1) for supported enrollment options and filename handling. A U2F-only device cannot take a PIN, so a `verify-required` key on it cannot sign, and Ed25519 keys cannot sign over U2F at all. On Linux the provider reads `/dev/hidraw*` nodes, which are usually not accessible to an ordinary user until a udev rule grants access (the FIDO udev rules distributed with the libfido2 packages on Debian and Ubuntu serve this purpose); when devices are found but none can be opened, the signature fails with `N FIDO device(s) found but none could be opened: <path>: <error>`. On macOS devices are opened non-exclusively (hidapi `macos-shared-device`, as libfido2 does), so a browser can use the key meanwhile. On other platforms (Windows) the built-in provider is not available: a signature fails with `built-in security key support is not available on this platform; set SecurityKeyProvider to an sk-api library`. An external provider is a shared library implementing `sk-api.h` version `0x000a0000` (`sk_api_version`, `sk_enroll`, `sk_sign`, `sk_load_resident_keys`); bssh loads it in process with `dlopen(RTLD_NOW | RTLD_LOCAL)`, as `sshsk_open` does with `RTLD_NOW`, so a library with an unresolved symbol is refused at load time; each library is opened once per canonical path and kept for the life of the process instead of being reopened and closed for every identity and connection, and a bare file name refers to a file relative to the working directory (OpenSSH's `lib_contains_symbol` opens the path directly) rather than one found through the loader search path. bssh calls `sk_sign` on a blocking thread, printing `Confirm user presence for key ...` when the key requires a touch and stderr is a terminal, and prompting once for a PIN when the provider reports `SSH_SK_ERR_PIN_REQUIRED` outside `BatchMode`. OpenSSH runs the same calls in `ssh-sk-helper` to keep the middleware out of its `pledge`d client; bssh has no such sandbox, so a helper would add a private protocol without changing what runs as the user. Running in process has accepted costs: an `sk_sign` call that has started cannot be cancelled, so a hung provider keeps its blocking thread and the process-wide provider lock until it returns and no other security-key signature in the process can proceed meanwhile (connections queued behind it give up at `ConnectTimeout` without reaching the provider); and the provider can read everything bssh holds in memory, including decrypted keys and passwords for other hosts. A key without an application string is refused before the provider is called, as `sshsk_sign` refuses it, and an Ed25519 response must carry exactly 64 bytes in `sig_r` and nothing in `sig_s`. A provider that cannot be opened (missing file, no `sk_api_version`, wrong major version) is reported before the key is offered and authentication continues; a failure inside `sk_sign` after the server has accepted the key (device absent, PIN required in `BatchMode`, provider error) prints `security key signing failed for <path>: ...` and authentication continues with the next identity on the same connection, as after OpenSSH's `sign_and_send_pubkey` failure: the signer hands russh the request back unsigned, russh sends nothing for it, and bssh stops waiting for the reply. Provider calls are serialized process-wide, because sk-api makes no thread-safety promise and bssh may authenticate to many hosts at once. An `sk-*` identity is checked against the provider from its unencrypted public half before any passphrase prompt, and `~/.ssh/id_ed25519_sk` and `~/.ssh/id_ecdsa_sk` are among the default identities. Static musl release builds cannot `dlopen`; they build, and report that limitation when an external provider is configured. Host keys and host certificates of the `sk-*` types are verified by ssh-key's `sk` verifiers (`SHA256(application) || flags || counter || SHA256(message)`), with no flag policy on the client side, as in OpenSSH. Agent-held `sk-*` keys keep signing through the agent. `-Q key-plain`, `-Q key`, `-Q key-cert`, `-Q key-sig`, `HostKeyAlgorithms` and `PubkeyAcceptedAlgorithms` list the `sk-*` names in the reference order; the remaining WebAuthn difference is tracked in [#345](https://github.com/lablup/bssh/issues/345).

## ssh_config integration

Expand Down