Skip to content

ssh-key: make sha2 optional - #602

Merged
tarcieri merged 1 commit into
RustCrypto:masterfrom
cataggar:ssh-key-optional-sha2
Nov 11, 2026
Merged

tarcieri merged 1 commit into
RustCrypto:masterfrom
cataggar:ssh-key-optional-sha2

Conversation

@cataggar

@cataggar cataggar commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

ssh-key always depends on sha2, even with default-features = false, although only a few of its APIs hash with it: key fingerprints, certificate validation, HashAlg::digest, and the sshsig methods that hash the message themselves. Builds that use none of these, or that get SHA-2 from another implementation, still compile and ship sha2.

I ran into this with a russh fork that has a SymCrypt backend (cataggar/russh#27). Its SymCrypt-only build must do all of its cryptography, randomness included, with SymCrypt, and CI fails that build if crates that implement cryptography, such as sha2, are in its normal dependency graph. The build uses ssh-key for its key, certificate and signature types and their encodings, and does its hashing and signing with SymCrypt. Even so, ssh-key still brings in sha2, the only such crate left, so the check needs an exception for it. I've made this change in a fork for that build (cataggar#1), and would like to get it from an ssh-key release instead.

ssh-protocol is in the same position: it depends on ssh-key with default-features = false and calls none of these APIs, but still gets sha2.

Compatibility

sha2 becomes a default feature, so:

  • Nothing changes for default builds, or for builds that enable ed25519, p256, p384, p521, ppk, rsa or crypto, which enable sha2.
  • Builds with default-features = false that enable none of those features and use a gated API (listed below) need to add features = ["sha2"]. Since 0.7 is still in release candidates, now seems the time for that.
  • ssh-protocol re-exports PublicKey, PrivateKey, Fingerprint, HashAlg and Certificate, so its users lose the gated methods too, unless something else in their build enables ssh-key/sha2. If you'd rather ssh-protocol keep them by default, I can add a default sha2 feature to it that enables ssh-key/sha2.
  • There are no new dependencies, and the MSRV doesn't change.

Proposal

This makes the sha2 dependency optional, behind a sha2 feature (sha2 = ["dep:sha2"], like sha1) that's on by default, and gates on it the APIs that hash with sha2:

  • Fingerprint::new, and fingerprint() on KeyData, PublicKey and PrivateKey
  • Certificate::validate (which still also needs std) and Certificate::validate_at
  • DotSsh::private_key_with_fingerprint and DotSsh::public_key_with_fingerprint
  • HashAlg::digest
  • SshSig::sign, SshSig::signed_data, PrivateKey::sign and PublicKey::verify
  • the AssociatedHashAlg impls for Sha256 and Sha512, and pub use sha2

These remain without it, so code with another SHA-2 implementation can still create and verify signatures, and make fingerprints from digests it computes:

  • the Fingerprint type, with parsing, Display, serde and randomart
  • AssociatedHashAlg, the *_digest and *_prehash sshsig methods, and SshSig::signed_data_for_prehash. They now take Digest from encoding::digest, the same digest 0.11 trait that sha2 re-exports.
  • Certificate::verify_signature

ed25519, p256, p384, p521, ppk and rsa enable sha2: ssh-key's own code for ed25519, p256, ppk and rsa uses SHA-2 directly (sk-* signatures cover SHA-256 hashes of the application and the message, rsa signs and verifies with rsa-sha2-256 and rsa-sha2-512, and ppk uses HMAC-SHA-256), and p384 and p521 pull in sha2 via ECDSA anyway. (dsa and ppk enable dep:sha1 the same way.) dsa and encryption don't.

Also:

  • In debug builds, certificate::Builder::sign checks the new certificate with validate_at and the signing key's fingerprint. Without sha2, it calls verify_signature instead.
  • Tests that compute fingerprints or sign with sshsig are gated on sha2.
  • The README gets a paragraph about the feature. I left CHANGELOG.md alone, since its entries are written at release time.
  • ssh-key.yml runs cargo test --no-default-features --features ecdsa,rand_core,std. The feature powerset never enables std and runs in release mode, so without this step, CI would never build std, or the debug-build certificate check, without sha2. The powerset itself grows from 24 to 44 combinations.

Prior art

#27 removed the fingerprint feature in 2022, to cut down on features and feature-related complexity, "especially as fingerprints are important baseline functionality". This does add a feature back, but keeps fingerprints on by default. It's named after the dependency, like sha1, rather than fingerprint, because it gates more than fingerprints and isn't meant to undo #27.

Validation

The same commit passed CI, with this repo's workflows, in my fork (cataggar#2). Locally, I also ran the ssh-key.yml test and no_std jobs on stable and 1.85.0, plus clippy, rustdoc, rustfmt and typos.

  • With default features and with --all-features, the same tests run as on master: 100 and 180.
  • cargo tree -e normal lists no sha2 for ssh-key --no-default-features --features alloc,ecdsa,rand_core,std, nor for ssh-protocol.
  • russh's SymCrypt-only build passes its dependency check with this change, without the sha2 exception.
  • Docs built without sha2 have unresolved intra-doc links to the gated methods, as partial-feature builds already have for other features. With --all-features, as in CI and on docs.rs, they're clean.

cataggar added a commit to cataggar/russh that referenced this pull request Oct 15, 2026
With only the `symcrypt` feature, the dependency graph still had `sha2`:
`ssh-key` always depended on it, to compute key fingerprints among other
things, and `ci/symcrypt-ban-check.sh` allowed it as its one exception.
Upstream made `sha2` required on purpose (RustCrypto/SSH#27), so the
change is in our fork: cataggar/RustCrypto-SSH#2 (proposed upstream as
RustCrypto/SSH#602) makes `sha2` a default feature of `ssh-key`.

russh now takes `ssh-key` from the fork, pinned at 6a7e2a8 (the squash
merge of that PR), with `default-features = false` and its other
defaults (`ecdsa`, `rand_core` and `std`). `_rustcrypto` enables
`ssh-key/sha2`, so the aws-lc-rs and ring backends keep the `ssh-key` API
they had, and so does a dev-dependency, for the tests that compute
fingerprints; the ban check leaves dev-dependencies out. The library
calls none of the APIs that need `sha2`.

The ban check loses its `sha2` exception (`allowed_dependents` stays for
`--allow-aws-lc-ed25519`). The README lists the `ssh_key` APIs that
symcrypt-only builds lose, mainly fingerprints, and how to get them
back. The `keys::certificate` docs stop linking to
`Certificate::validate` and `validate_at`, which symcrypt-only builds no
longer have.

Closes #27

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1e56d65e-86a1-43d2-89bd-6c4801475f3c
cataggar added a commit to cataggar/russh that referenced this pull request Oct 15, 2026
With only the `symcrypt` feature, the dependency graph still had `sha2`:
`ssh-key` always depended on it, to compute key fingerprints among other
things, and `ci/symcrypt-ban-check.sh` allowed it as its one exception.
Upstream made `sha2` required on purpose (RustCrypto/SSH#27), so the
change is in our fork: cataggar/RustCrypto-SSH#2 (proposed upstream as
RustCrypto/SSH#602) makes `sha2` a default feature of `ssh-key`.

russh now takes `ssh-key` from the fork, pinned at 6a7e2a8 (the squash
merge of that PR), with `default-features = false` and its other
defaults (`ecdsa`, `rand_core` and `std`). `_rustcrypto` enables
`ssh-key/sha2`, so the aws-lc-rs and ring backends keep the `ssh-key` API
they had, and so does a dev-dependency, for the tests that compute
fingerprints; the ban check leaves dev-dependencies out. The library
calls none of the APIs that need `sha2`.

The ban check loses its `sha2` exception (`allowed_dependents` stays for
`--allow-aws-lc-ed25519`). The README lists the `ssh_key` APIs that
symcrypt-only builds lose, mainly fingerprints, and how to get them
back. The `keys::certificate` docs stop linking to
`Certificate::validate` and `validate_at`, which symcrypt-only builds no
longer have.

Closes #27

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1e56d65e-86a1-43d2-89bd-6c4801475f3c
cataggar added a commit to cataggar/russh that referenced this pull request Oct 15, 2026
With only the `symcrypt` feature, the dependency graph still had `sha2`:
`ssh-key` always depended on it, to compute key fingerprints among other
things, and `ci/symcrypt-ban-check.sh` allowed it as its one exception.
Upstream made `sha2` required on purpose (RustCrypto/SSH#27), so the
change is in our fork: cataggar/RustCrypto-SSH#2 (proposed upstream as
RustCrypto/SSH#602) makes `sha2` a default feature of `ssh-key`.

russh now takes `ssh-key` from the fork, pinned at 6a7e2a8 (the squash
merge of that PR), with `default-features = false` and its other
defaults (`ecdsa`, `rand_core` and `std`). `_rustcrypto` enables
`ssh-key/sha2`, so the aws-lc-rs and ring backends keep the `ssh-key` API
they had, and so does a dev-dependency, for the tests that compute
fingerprints; the ban check leaves dev-dependencies out. The library
calls none of the APIs that need `sha2`.

The ban check loses its `sha2` exception (`allowed_dependents` stays for
`--allow-aws-lc-ed25519`). The README lists the `ssh_key` APIs that
symcrypt-only builds lose, mainly fingerprints, and how to get them
back. The `keys::certificate` docs stop linking to
`Certificate::validate` and `validate_at`, which symcrypt-only builds no
longer have.

Closes #27

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1e56d65e-86a1-43d2-89bd-6c4801475f3c
cataggar added a commit to cataggar/russh that referenced this pull request Oct 15, 2026
With only the `symcrypt` feature, the dependency graph still had `sha2`:
`ssh-key` always depended on it, to compute key fingerprints among other
things, and `ci/symcrypt-ban-check.sh` allowed it as its one exception.
Upstream made `sha2` required on purpose (RustCrypto/SSH#27), so the
change is in our fork: cataggar/RustCrypto-SSH#2 (proposed upstream as
RustCrypto/SSH#602) makes `sha2` a default feature of `ssh-key`.

russh now takes `ssh-key` from the fork, pinned at 6a7e2a8 (the squash
merge of that PR), with `default-features = false` and its other
defaults (`ecdsa`, `rand_core` and `std`). `_rustcrypto` enables
`ssh-key/sha2`, so the aws-lc-rs and ring backends keep the `ssh-key` API
they had, and so does a dev-dependency, for the tests that compute
fingerprints; the ban check leaves dev-dependencies out. The library
calls none of the APIs that need `sha2`.

The ban check loses its `sha2` exception (`allowed_dependents` stays for
`--allow-aws-lc-ed25519`). The README lists the `ssh_key` APIs that
symcrypt-only builds lose, mainly fingerprints, and how to get them
back. The `keys::certificate` docs stop linking to
`Certificate::validate` and `validate_at`, which symcrypt-only builds no
longer have.

Closes #27

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1e56d65e-86a1-43d2-89bd-6c4801475f3c
Comment thread ssh-key/Cargo.toml Resolved
@tarcieri

Copy link
Copy Markdown
Member

Sorry, should've reviewed this sooner. Looks good aside from the one nit. Mind rebasing and we can get this merged? (maybe wait a few hours, I'm making some other changes that might conflict)

Adds a `sha2` feature, on by default, and makes the `sha2` dependency
optional. It gates the APIs that hash with `sha2`: key fingerprints,
`Certificate::validate{,_at}`, the `DotSsh` fingerprint lookups,
`HashAlg::digest`, `SshSig::{sign, signed_data}`, `PrivateKey::sign`,
`PublicKey::verify`, the `AssociatedHashAlg` impls for `Sha256` and
`Sha512`, and the `sha2` re-export. The `ed25519`, `p256`, `p384`,
`p521`, `ppk` and `rsa` features enable it.

`Digest` now comes from `encoding::digest` (the same trait), so
`AssociatedHashAlg` and the `*_digest`/`*_prehash` signing and
verification methods remain available without `sha2`. In debug builds
without `sha2`, `certificate::Builder::sign` checks the new certificate
with `verify_signature` instead of `validate_at`.

With this, `ssh-key` with `default-features = false`, and therefore
`ssh-protocol`, no longer depends on `sha2`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@cataggar
cataggar force-pushed the ssh-key-optional-sha2 branch from ddb66e9 to 07d46c1 Compare November 11, 2026 03:18
@cataggar

cataggar commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor Author

No problem at all. Rebased onto master and enabled sha2 for p384 and p521 as well.

@tarcieri
tarcieri merged commit 3a9ab0f into RustCrypto:master Nov 11, 2026
17 checks passed
@cataggar
cataggar deleted the ssh-key-optional-sha2 branch November 11, 2026 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants