Skip to content

Move Mpint from ssh-key to ssh-encoding #359

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 19, 2025
Merged
Show file tree
Hide file tree
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion ssh-encoding/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
6 changes: 6 additions & 0 deletions ssh-encoding/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ pub enum Error {
/// Invalid length.
Length,

/// `mpint` encoding errors.
#[cfg(feature = "alloc")]
MpintEncoding,

/// Overflow errors.
Overflow,

Expand Down Expand Up @@ -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}"),
Expand Down
6 changes: 6 additions & 0 deletions ssh-encoding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
//!
Expand Down Expand Up @@ -214,6 +215,8 @@ mod decode;
mod encode;
mod error;
mod label;
#[cfg(feature = "alloc")]
mod mpint;
#[macro_use]
mod reader;
mod writer;
Expand All @@ -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};

Expand Down
40 changes: 22 additions & 18 deletions ssh-key/src/mpint.rs → ssh-encoding/src/mpint.rs
Original file line number Diff line number Diff line change
@@ -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):
///
Expand All @@ -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]>,
Expand Down Expand Up @@ -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()
Expand All @@ -132,11 +135,11 @@ impl Decode for Mpint {
}

impl Encode for Mpint {
fn encoded_len(&self) -> encoding::Result<usize> {
fn encoded_len(&self) -> Result<usize> {
[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(())
}
Expand All @@ -156,14 +159,15 @@ impl TryFrom<Box<[u8]>> for Mpint {
fn try_from(bytes: Box<[u8]>) -> Result<Self> {
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();
Expand Down Expand Up @@ -200,7 +204,7 @@ impl fmt::UpperHex for Mpint {
}
}

#[cfg(any(feature = "dsa", feature = "rsa"))]
#[cfg(feature = "bigint")]
impl TryFrom<bigint::BigUint> for Mpint {
type Error = Error;

Expand All @@ -209,7 +213,7 @@ impl TryFrom<bigint::BigUint> for Mpint {
}
}

#[cfg(any(feature = "dsa", feature = "rsa"))]
#[cfg(feature = "bigint")]
impl TryFrom<&bigint::BigUint> for Mpint {
type Error = Error;

Expand All @@ -219,7 +223,7 @@ impl TryFrom<&bigint::BigUint> for Mpint {
}
}

#[cfg(any(feature = "dsa", feature = "rsa"))]
#[cfg(feature = "bigint")]
impl TryFrom<Mpint> for bigint::BigUint {
type Error = Error;

Expand All @@ -228,15 +232,15 @@ impl TryFrom<Mpint> for bigint::BigUint {
}
}

#[cfg(any(feature = "dsa", feature = "rsa"))]
#[cfg(feature = "bigint")]
impl TryFrom<&Mpint> for bigint::BigUint {
type Error = Error;

fn try_from(mpint: &Mpint) -> Result<bigint::BigUint> {
mpint
.as_positive_bytes()
.map(bigint::BigUint::from_bytes_be)
.ok_or(Error::Crypto)
.ok_or(Error::MpintEncoding)
}
}

Expand Down
6 changes: 3 additions & 3 deletions ssh-key/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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 = [
Expand All @@ -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"]

Expand Down
18 changes: 9 additions & 9 deletions ssh-key/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -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")]
Expand Down
4 changes: 2 additions & 2 deletions ssh-key/src/private/dsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ impl TryFrom<DsaPrivateKey> for dsa::BigUint {
type Error = Error;

fn try_from(key: DsaPrivateKey) -> Result<dsa::BigUint> {
dsa::BigUint::try_from(&key.inner)
Ok(dsa::BigUint::try_from(&key.inner)?)
}
}

Expand All @@ -106,7 +106,7 @@ impl TryFrom<&DsaPrivateKey> for dsa::BigUint {
type Error = Error;

fn try_from(key: &DsaPrivateKey) -> Result<dsa::BigUint> {
dsa::BigUint::try_from(&key.inner)
Ok(dsa::BigUint::try_from(&key.inner)?)
}
}

Expand Down