diff --git a/Cargo.lock b/Cargo.lock index a6785486..f42bd2df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -860,8 +860,11 @@ dependencies = [ "bytes", "digest", "hex-literal", + "num-bigint-dig", "pem-rfc7468", "ssh-derive", + "subtle", + "zeroize", ] [[package]] diff --git a/ssh-encoding/Cargo.toml b/ssh-encoding/Cargo.toml index 6eec7596..c8bbc374 100644 --- a/ssh-encoding/Cargo.toml +++ b/ssh-encoding/Cargo.toml @@ -21,14 +21,20 @@ bytes = { version = "1", optional = true, default-features = false } digest = { version = "=0.11.0-pre.9", optional = true, default-features = false } pem-rfc7468 = { version = "1.0.0-rc.2", optional = true } ssh-derive = { version = "0.0.1-alpha", optional = true, path = "../ssh-derive" } +subtle = { version = "2", optional = true, default-features = false } +zeroize = { version = "1", optional = true, default-features = false } + +# TODO(tarcieri): migrate to `crypto-bigint` +bigint = { package = "num-bigint-dig", version = "0.8", optional = true, default-features = false } [dev-dependencies] hex-literal = "1" [features] -alloc = ["base64ct?/alloc", "pem-rfc7468?/alloc"] +alloc = ["base64ct?/alloc", "pem-rfc7468?/alloc", "zeroize?/alloc"] base64 = ["dep:base64ct"] +bigint = ["alloc", "zeroize", "dep:bigint"] bytes = ["alloc", "dep:bytes"] pem = ["base64", "dep:pem-rfc7468"] derive = ["ssh-derive"] diff --git a/ssh-encoding/src/error.rs b/ssh-encoding/src/error.rs index 9f38b465..b2a967ea 100644 --- a/ssh-encoding/src/error.rs +++ b/ssh-encoding/src/error.rs @@ -23,6 +23,10 @@ pub enum Error { /// Invalid length. Length, + /// `mpint` encoding errors. + #[cfg(feature = "alloc")] + MpintEncoding, + /// Overflow errors. Overflow, @@ -60,6 +64,8 @@ impl fmt::Display for Error { Error::CharacterEncoding => write!(f, "character encoding invalid"), Error::Label(err) => write!(f, "{}", err), Error::Length => write!(f, "length invalid"), + #[cfg(feature = "alloc")] + Error::MpintEncoding => write!(f, "`mpint` encoding invalid"), Error::Overflow => write!(f, "internal overflow error"), #[cfg(feature = "pem")] Error::Pem(err) => write!(f, "{err}"), diff --git a/ssh-encoding/src/lib.rs b/ssh-encoding/src/lib.rs index 87ccc00b..a2564669 100644 --- a/ssh-encoding/src/lib.rs +++ b/ssh-encoding/src/lib.rs @@ -83,6 +83,7 @@ //! The UTF-8 mapping does not alter the encoding of US-ASCII characters. //! //! ### `mpint`: multiple precision integers in two's complement format +//! #### [`Decode`]/[`Encode`] trait impls: `Mpint` //! //! Stored as a byte string, 8 bits per byte, MSB first (a.k.a. big endian). //! @@ -214,6 +215,8 @@ mod decode; mod encode; mod error; mod label; +#[cfg(feature = "alloc")] +mod mpint; #[macro_use] mod reader; mod writer; @@ -233,6 +236,9 @@ pub use crate::{ writer::Writer, }; +#[cfg(feature = "alloc")] +pub use crate::mpint::Mpint; + #[cfg(feature = "base64")] pub use crate::{base64::Base64Reader, base64::Base64Writer}; diff --git a/ssh-key/src/mpint.rs b/ssh-encoding/src/mpint.rs similarity index 89% rename from ssh-key/src/mpint.rs rename to ssh-encoding/src/mpint.rs index 022fb2b2..3b971ebf 100644 --- a/ssh-key/src/mpint.rs +++ b/ssh-encoding/src/mpint.rs @@ -1,19 +1,18 @@ //! Multiple precision integer -use crate::{Error, Result}; +use crate::{CheckedSum, Decode, Encode, Error, Reader, Result, Writer}; use alloc::{boxed::Box, vec::Vec}; use core::fmt; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; + +#[cfg(feature = "subtle")] use subtle::{Choice, ConstantTimeEq}; -use zeroize::Zeroize; -#[cfg(any(feature = "dsa", feature = "rsa"))] +#[cfg(any(feature = "bigint", feature = "zeroize"))] +use zeroize::Zeroize; +#[cfg(feature = "bigint")] use zeroize::Zeroizing; -/// Multiple precision integer, a.k.a. "mpint". -/// -/// This type is used for representing the big integer components of -/// DSA and RSA keys. +/// Multiple precision integer, a.k.a. `mpint`. /// /// Described in [RFC4251 ยง 5](https://datatracker.ietf.org/doc/html/rfc4251#section-5): /// @@ -38,7 +37,8 @@ use zeroize::Zeroizing; /// | 80 | `00 00 00 02 00 80` /// |-1234 | `00 00 00 02 ed cc` /// | -deadbeef | `00 00 00 05 ff 21 52 41 11` -#[derive(Clone, PartialOrd, Ord)] +#[cfg_attr(not(feature = "subtle"), derive(Clone))] +#[cfg_attr(feature = "subtle", derive(Clone, Ord, PartialOrd))] // TODO: constant time (Partial)`Ord`? pub struct Mpint { /// Inner big endian-serialized integer value inner: Box<[u8]>, @@ -109,14 +109,17 @@ impl AsRef<[u8]> for Mpint { } } +#[cfg(feature = "subtle")] impl ConstantTimeEq for Mpint { fn ct_eq(&self, other: &Self) -> Choice { self.as_ref().ct_eq(other.as_ref()) } } +#[cfg(feature = "subtle")] impl Eq for Mpint {} +#[cfg(feature = "subtle")] impl PartialEq for Mpint { fn eq(&self, other: &Self) -> bool { self.ct_eq(other).into() @@ -132,11 +135,11 @@ impl Decode for Mpint { } impl Encode for Mpint { - fn encoded_len(&self) -> encoding::Result { + fn encoded_len(&self) -> Result { [4, self.as_bytes().len()].checked_sum() } - fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> { + fn encode(&self, writer: &mut impl Writer) -> Result<()> { self.as_bytes().encode(writer)?; Ok(()) } @@ -156,14 +159,15 @@ impl TryFrom> for Mpint { fn try_from(bytes: Box<[u8]>) -> Result { match &*bytes { // Unnecessary leading 0 - [0x00] => Err(Error::FormatEncoding), + [0x00] => Err(Error::MpintEncoding), // Unnecessary leading 0 - [0x00, n, ..] if *n < 0x80 => Err(Error::FormatEncoding), + [0x00, n, ..] if *n < 0x80 => Err(Error::MpintEncoding), _ => Ok(Self { inner: bytes }), } } } +#[cfg(feature = "zeroize")] impl Zeroize for Mpint { fn zeroize(&mut self) { self.inner.zeroize(); @@ -200,7 +204,7 @@ impl fmt::UpperHex for Mpint { } } -#[cfg(any(feature = "dsa", feature = "rsa"))] +#[cfg(feature = "bigint")] impl TryFrom for Mpint { type Error = Error; @@ -209,7 +213,7 @@ impl TryFrom for Mpint { } } -#[cfg(any(feature = "dsa", feature = "rsa"))] +#[cfg(feature = "bigint")] impl TryFrom<&bigint::BigUint> for Mpint { type Error = Error; @@ -219,7 +223,7 @@ impl TryFrom<&bigint::BigUint> for Mpint { } } -#[cfg(any(feature = "dsa", feature = "rsa"))] +#[cfg(feature = "bigint")] impl TryFrom for bigint::BigUint { type Error = Error; @@ -228,7 +232,7 @@ impl TryFrom for bigint::BigUint { } } -#[cfg(any(feature = "dsa", feature = "rsa"))] +#[cfg(feature = "bigint")] impl TryFrom<&Mpint> for bigint::BigUint { type Error = Error; @@ -236,7 +240,7 @@ impl TryFrom<&Mpint> for bigint::BigUint { mpint .as_positive_bytes() .map(bigint::BigUint::from_bytes_be) - .ok_or(Error::Crypto) + .ok_or(Error::MpintEncoding) } } diff --git a/ssh-key/Cargo.toml b/ssh-key/Cargo.toml index 2a2baa24..fc0a4ca0 100644 --- a/ssh-key/Cargo.toml +++ b/ssh-key/Cargo.toml @@ -19,7 +19,7 @@ rust-version = "1.85" [dependencies] cipher = { package = "ssh-cipher", version = "=0.3.0-pre.2", features = ["zeroize"], path = "../ssh-cipher" } -encoding = { package = "ssh-encoding", version = "=0.3.0-pre.1", features = ["base64", "digest", "pem"], path = "../ssh-encoding" } +encoding = { package = "ssh-encoding", version = "=0.3.0-pre.1", features = ["base64", "digest", "pem", "subtle", "zeroize"], path = "../ssh-encoding" } sha2 = { version = "=0.11.0-pre.4", default-features = false } signature = { version = "=2.3.0-pre.4", default-features = false } subtle = { version = "2", default-features = false } @@ -66,7 +66,7 @@ std = [ ] crypto = ["ed25519", "p256", "p384", "p521", "rsa"] # NOTE: `dsa` is obsolete/weak -dsa = ["dep:bigint", "dep:dsa", "dep:sha1", "alloc", "signature/rand_core"] +dsa = ["dep:bigint", "dep:dsa", "dep:sha1", "alloc", "encoding/bigint", "signature/rand_core"] ecdsa = ["dep:sec1"] ed25519 = ["dep:ed25519-dalek", "rand_core"] encryption = [ @@ -83,7 +83,7 @@ p256 = ["dep:p256", "ecdsa"] p384 = ["dep:p384", "ecdsa"] p521 = ["dep:p521", "ecdsa"] ppk = ["dep:hex", "alloc", "cipher/aes-cbc", "dep:hmac", "dep:argon2", "dep:sha1"] -rsa = ["dep:bigint", "dep:rsa", "alloc", "rand_core"] +rsa = ["dep:bigint", "dep:rsa", "alloc", "encoding/bigint", "rand_core"] sha1 = ["dep:sha1"] tdes = ["cipher/tdes", "encryption"] diff --git a/ssh-key/src/lib.rs b/ssh-key/src/lib.rs index af231f7c..89786b12 100644 --- a/ssh-key/src/lib.rs +++ b/ssh-key/src/lib.rs @@ -158,8 +158,6 @@ mod kdf; #[cfg(feature = "std")] mod dot_ssh; -#[cfg(feature = "alloc")] -mod mpint; #[cfg(feature = "ppk")] mod ppk; #[cfg(feature = "alloc")] @@ -181,13 +179,15 @@ pub use encoding::pem::LineEnding; pub use sha2; #[cfg(feature = "alloc")] -pub use crate::{ - algorithm::AlgorithmName, - certificate::Certificate, - known_hosts::KnownHosts, - mpint::Mpint, - signature::{Signature, SigningKey}, - sshsig::SshSig, +pub use { + crate::{ + algorithm::AlgorithmName, + certificate::Certificate, + known_hosts::KnownHosts, + signature::{Signature, SigningKey}, + sshsig::SshSig, + }, + encoding::Mpint, }; #[cfg(feature = "ecdsa")] diff --git a/ssh-key/src/private/dsa.rs b/ssh-key/src/private/dsa.rs index 2416d632..f6c59ff3 100644 --- a/ssh-key/src/private/dsa.rs +++ b/ssh-key/src/private/dsa.rs @@ -97,7 +97,7 @@ impl TryFrom for dsa::BigUint { type Error = Error; fn try_from(key: DsaPrivateKey) -> Result { - dsa::BigUint::try_from(&key.inner) + Ok(dsa::BigUint::try_from(&key.inner)?) } } @@ -106,7 +106,7 @@ impl TryFrom<&DsaPrivateKey> for dsa::BigUint { type Error = Error; fn try_from(key: &DsaPrivateKey) -> Result { - dsa::BigUint::try_from(&key.inner) + Ok(dsa::BigUint::try_from(&key.inner)?) } }