Skip to content

ssh-key: make sha2 optional - #602

Open
cataggar wants to merge 1 commit into
RustCrypto:masterfrom
cataggar:ssh-key-optional-sha2
Open

cataggar wants to merge 1 commit into
RustCrypto:masterfrom
cataggar:ssh-key-optional-sha2

Conversation

@cataggar

Copy link
Copy Markdown

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, 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, ppk and rsa enable sha2, because ssh-key's own code for them uses SHA-2: 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. (dsa and ppk enable dep:sha1 the same way.) p384, p521, dsa and encryption don't, although their dependencies pull in sha2 anyway. If you'd rather every algorithm feature enabled sha2, so that fingerprints come with any of them, that's one word per feature.

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.

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`, `ppk` and
`rsa` features enable it, because their implementations use SHA-2
directly.

`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>
Copilot-Session: 1e56d65e-86a1-43d2-89bd-6c4801475f3c

This branch has not been deployed

No deployments
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.

1 participant