Workspacify

This commit is contained in:
2025-05-03 17:30:01 -07:00
parent 9fe5b78962
commit d036997de3
60 changed files with 450 additions and 212 deletions

10
crypto/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "crypto"
edition = "2024"
[dependencies]
num-bigint-dig = { workspace = true }
num-integer = { workspace = true }
num-traits = { workspace = true }
thiserror = { workspace = true }
zeroize = { workspace = true }

View File

@@ -0,0 +1,24 @@
pub static ALLOWED_KEY_EXCHANGE_ALGORITHMS: &[&str] = &[
"ecdh-sha2-nistp256",
"ecdh-sha2-nistp384",
"ecdh-sha2-nistp521",
"curve25519-sha256",
];
pub static ALLOWED_HOST_KEY_ALGORITHMS: &[&str] = &[
"ssh-ed25519",
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
"ecdsa-sha2-nistp521",
"ssh-rsa",
];
pub static ALLOWED_ENCRYPTION_ALGORITHMS: &[&str] = &[
"aes256-ctr",
"aes256-gcm@openssh.com",
"chacha20-poly1305@openssh.com",
];
pub static ALLOWED_MAC_ALGORITHMS: &[&str] = &["hmac-sha2-256", "hmac-sha2-512"];
pub static ALLOWED_COMPRESSION_ALGORITHMS: &[&str] = &["none", "zlib"];

272
crypto/src/lib.rs Normal file
View File

@@ -0,0 +1,272 @@
pub mod known_algorithms;
pub mod rsa;
use std::fmt;
use std::str::FromStr;
use thiserror::Error;
#[derive(Debug, PartialEq, Eq)]
pub enum KeyExchangeAlgorithm {
EcdhSha2Nistp256,
EcdhSha2Nistp384,
EcdhSha2Nistp521,
Curve25519Sha256,
}
impl KeyExchangeAlgorithm {
pub fn allowed() -> &'static [KeyExchangeAlgorithm] {
&[
KeyExchangeAlgorithm::EcdhSha2Nistp256,
KeyExchangeAlgorithm::EcdhSha2Nistp384,
KeyExchangeAlgorithm::EcdhSha2Nistp521,
KeyExchangeAlgorithm::Curve25519Sha256,
]
}
}
#[derive(Debug, Error)]
pub enum AlgoFromStrError {
#[error("Did not recognize key exchange algorithm '{0}'")]
UnknownKeyExchangeAlgorithm(String),
#[error("Did not recognize host key algorithm '{0}'")]
UnknownHostKeyAlgorithm(String),
#[error("Did not recognize encryption algorithm '{0}'")]
UnknownEncryptionAlgorithm(String),
#[error("Did not recognize MAC algorithm '{0}'")]
UnknownMacAlgorithm(String),
#[error("Did not recognize compression algorithm '{0}'")]
UnknownCompressionAlgorithm(String),
}
impl FromStr for KeyExchangeAlgorithm {
type Err = AlgoFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ecdh-sha2-nistp256" => Ok(KeyExchangeAlgorithm::EcdhSha2Nistp256),
"ecdh-sha2-nistp384" => Ok(KeyExchangeAlgorithm::EcdhSha2Nistp384),
"ecdh-sha2-nistp521" => Ok(KeyExchangeAlgorithm::EcdhSha2Nistp521),
"curve25519-sha256" => Ok(KeyExchangeAlgorithm::Curve25519Sha256),
other => Err(AlgoFromStrError::UnknownKeyExchangeAlgorithm(
other.to_string(),
)),
}
}
}
impl fmt::Display for KeyExchangeAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KeyExchangeAlgorithm::EcdhSha2Nistp256 => write!(f, "ecdh-sha2-nistp256"),
KeyExchangeAlgorithm::EcdhSha2Nistp384 => write!(f, "ecdh-sha2-nistp384"),
KeyExchangeAlgorithm::EcdhSha2Nistp521 => write!(f, "ecdh-sha2-nistp521"),
KeyExchangeAlgorithm::Curve25519Sha256 => write!(f, "curve25519-sha256"),
}
}
}
#[test]
fn can_invert_kex_algos() {
for variant in KeyExchangeAlgorithm::allowed().iter() {
let s = variant.to_string();
let reversed = KeyExchangeAlgorithm::from_str(&s);
assert!(reversed.is_ok());
assert_eq!(variant, &reversed.unwrap());
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum HostKeyAlgorithm {
Ed25519,
EcdsaSha2Nistp256,
EcdsaSha2Nistp384,
EcdsaSha2Nistp521,
Rsa,
}
impl HostKeyAlgorithm {
pub fn allowed() -> &'static [Self] {
&[
HostKeyAlgorithm::Ed25519,
HostKeyAlgorithm::EcdsaSha2Nistp256,
HostKeyAlgorithm::EcdsaSha2Nistp384,
HostKeyAlgorithm::EcdsaSha2Nistp521,
HostKeyAlgorithm::Rsa,
]
}
}
impl fmt::Display for HostKeyAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HostKeyAlgorithm::Ed25519 => write!(f, "ssh-ed25519"),
HostKeyAlgorithm::EcdsaSha2Nistp256 => write!(f, "ecdsa-sha2-nistp256"),
HostKeyAlgorithm::EcdsaSha2Nistp384 => write!(f, "ecdsa-sha2-nistp384"),
HostKeyAlgorithm::EcdsaSha2Nistp521 => write!(f, "ecdsa-sha2-nistp521"),
HostKeyAlgorithm::Rsa => write!(f, "ssh-rsa"),
}
}
}
impl FromStr for HostKeyAlgorithm {
type Err = AlgoFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ssh-ed25519" => Ok(HostKeyAlgorithm::Ed25519),
"ecdsa-sha2-nistp256" => Ok(HostKeyAlgorithm::EcdsaSha2Nistp256),
"ecdsa-sha2-nistp384" => Ok(HostKeyAlgorithm::EcdsaSha2Nistp384),
"ecdsa-sha2-nistp521" => Ok(HostKeyAlgorithm::EcdsaSha2Nistp521),
"ssh-rsa" => Ok(HostKeyAlgorithm::Rsa),
unknown => Err(AlgoFromStrError::UnknownHostKeyAlgorithm(
unknown.to_string(),
)),
}
}
}
#[test]
fn can_invert_host_key_algos() {
for variant in HostKeyAlgorithm::allowed().iter() {
let s = variant.to_string();
let reversed = HostKeyAlgorithm::from_str(&s);
assert!(reversed.is_ok());
assert_eq!(variant, &reversed.unwrap());
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum EncryptionAlgorithm {
Aes256Ctr,
Aes256Gcm,
ChaCha20Poly1305,
}
impl EncryptionAlgorithm {
pub fn allowed() -> &'static [Self] {
&[
EncryptionAlgorithm::Aes256Ctr,
EncryptionAlgorithm::Aes256Gcm,
EncryptionAlgorithm::ChaCha20Poly1305,
]
}
}
impl fmt::Display for EncryptionAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EncryptionAlgorithm::Aes256Ctr => write!(f, "aes256-ctr"),
EncryptionAlgorithm::Aes256Gcm => write!(f, "aes256-gcm@openssh.com"),
EncryptionAlgorithm::ChaCha20Poly1305 => write!(f, "chacha20-poly1305@openssh.com"),
}
}
}
impl FromStr for EncryptionAlgorithm {
type Err = AlgoFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"aes256-ctr" => Ok(EncryptionAlgorithm::Aes256Ctr),
"aes256-gcm@openssh.com" => Ok(EncryptionAlgorithm::Aes256Gcm),
"chacha20-poly1305@openssh.com" => Ok(EncryptionAlgorithm::ChaCha20Poly1305),
_ => Err(AlgoFromStrError::UnknownEncryptionAlgorithm(s.to_string())),
}
}
}
#[test]
fn can_invert_encryption_algos() {
for variant in EncryptionAlgorithm::allowed().iter() {
let s = variant.to_string();
let reversed = EncryptionAlgorithm::from_str(&s);
assert!(reversed.is_ok());
assert_eq!(variant, &reversed.unwrap());
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum MacAlgorithm {
HmacSha256,
HmacSha512,
}
impl MacAlgorithm {
pub fn allowed() -> &'static [Self] {
&[MacAlgorithm::HmacSha256, MacAlgorithm::HmacSha512]
}
}
impl fmt::Display for MacAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MacAlgorithm::HmacSha256 => write!(f, "hmac-sha2-256"),
MacAlgorithm::HmacSha512 => write!(f, "hmac-sha2-512"),
}
}
}
impl FromStr for MacAlgorithm {
type Err = AlgoFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"hmac-sha2-256" => Ok(MacAlgorithm::HmacSha256),
"hmac-sha2-512" => Ok(MacAlgorithm::HmacSha512),
_ => Err(AlgoFromStrError::UnknownMacAlgorithm(s.to_string())),
}
}
}
#[test]
fn can_invert_mac_algos() {
for variant in MacAlgorithm::allowed().iter() {
let s = variant.to_string();
let reversed = MacAlgorithm::from_str(&s);
assert!(reversed.is_ok());
assert_eq!(variant, &reversed.unwrap());
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum CompressionAlgorithm {
None,
Zlib,
}
impl CompressionAlgorithm {
pub fn allowed() -> &'static [Self] {
&[CompressionAlgorithm::None, CompressionAlgorithm::Zlib]
}
}
impl fmt::Display for CompressionAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CompressionAlgorithm::None => write!(f, "none"),
CompressionAlgorithm::Zlib => write!(f, "zlib"),
}
}
}
impl FromStr for CompressionAlgorithm {
type Err = AlgoFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"none" => Ok(CompressionAlgorithm::None),
"zlib" => Ok(CompressionAlgorithm::Zlib),
_ => Err(AlgoFromStrError::UnknownCompressionAlgorithm(s.to_string())),
}
}
}
#[test]
fn can_invert_compression_algos() {
for variant in CompressionAlgorithm::allowed().iter() {
let s = variant.to_string();
let reversed = CompressionAlgorithm::from_str(&s);
assert!(reversed.is_ok());
assert_eq!(variant, &reversed.unwrap());
}
}

251
crypto/src/rsa.rs Normal file
View File

@@ -0,0 +1,251 @@
use num_bigint_dig::{BigInt, BigUint, ModInverse};
use num_integer::{sqrt, Integer};
use num_traits::Pow;
use zeroize::{Zeroize, Zeroizing};
/// An RSA public key
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct PublicKey {
/// The public modulus, the product of the primes 'p' and 'q'
n: BigUint,
/// The public exponent.
e: BigUint,
}
/// An RSA private key
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct PrivateKey {
/// The public modulus, the product of the primes 'p' and 'q'
n: BigUint,
/// The public exponent
e: BigUint,
/// The private exponent
d: BigUint,
/// The prime 'p'
p: BigUint,
/// The prime 'q'
q: BigUint,
/// d mod (p-1)
dmodp1: BigUint,
/// d mod (q-1)
dmodq1: BigUint,
/// q^-1 mod P
qinv: BigInt,
}
impl Drop for PrivateKey {
fn drop(&mut self) {
self.d.zeroize();
self.p.zeroize();
self.q.zeroize();
self.dmodp1.zeroize();
self.dmodq1.zeroize();
self.qinv.zeroize();
}
}
impl PublicKey {
/// Generate a public key from the given input values.
///
/// No checking is performed at this point to ensure that these
/// values are sane in any way.
pub fn new(n: BigUint, e: BigUint) -> Self {
PublicKey { n, e }
}
}
#[derive(Debug, thiserror::Error)]
pub enum PrivateKeyLoadError {
#[error("Invalid value for public 'e' value; must be between 2^16 and 2^256, got {0}")]
InvalidEValue(BigUint),
#[error("Could not recover primes 'p' and 'q' from provided private key data")]
CouldNotRecoverPrimes,
#[error("Could not generate modular inverse of 'q' in provided private key data")]
CouldNotGenerateModInv,
#[error("Could not cross-confirm value '{value}' in provided private key data")]
IncoherentValue { value: &'static str },
}
impl PrivateKey {
/// Generate a private key from the associated public key and the private
/// value 'd'.
///
/// This will do some further computations, and should not be called when
/// you absolutely must get an answer back immediately. Or, to put it
/// another way, you really should call this with `block_in_place` or
/// similar in an async context.
pub fn from_d(public: &PublicKey, d: BigUint) -> Result<Self, PrivateKeyLoadError> {
let (p, q) = recover_primes(&public.n, &public.e, &d)?;
let dmodp1 = &d % (&p - 1u64);
let dmodq1 = &d % (&q - 1u64);
let qinv = (&q)
.mod_inverse(&p)
.ok_or(PrivateKeyLoadError::CouldNotGenerateModInv)?;
Ok(PrivateKey {
n: public.n.clone(),
e: public.e.clone(),
d,
p,
q,
dmodp1,
dmodq1,
qinv,
})
}
/// Generate a private key from the associated public key, the private
/// value 'd', and several other useful values.
///
/// This will do some additional computations, and should not be called
/// when you absolutely must get an answer back immediately. Or, to put
/// it another way, you really should call this with `block_in_place` or
/// similar.
///
/// This version of this function performs some safety checking to ensure
/// the provided values are reasonable. To avoid this cost, but at the
/// risk of accepting bad key data, use the `_unchecked` variant.
pub fn from_parts(
public: &PublicKey,
d: BigUint,
qinv: BigInt,
p: BigUint,
q: BigUint,
) -> Result<Self, PrivateKeyLoadError> {
let computed_private = Self::from_d(public, d)?;
if qinv != computed_private.qinv {
return Err(PrivateKeyLoadError::IncoherentValue { value: "qinv" });
}
if p != computed_private.p {
return Err(PrivateKeyLoadError::IncoherentValue { value: "p" });
}
if q != computed_private.q {
return Err(PrivateKeyLoadError::IncoherentValue { value: "q" });
}
Ok(computed_private)
}
/// Generate a private key from the associated public key, the private
/// value 'd', and several other useful values.
///
/// This will do some additional computations, and should not be called
/// when you absolutely must get an answer back immediately. Or, to put
/// it another way, you really should call this with `block_in_place` or
/// similar.
///
/// This version of this function performs no safety checking to ensure
/// the provided values are reasonable.
pub fn from_parts_unchecked(
public: &PublicKey,
d: BigUint,
qinv: BigInt,
p: BigUint,
q: BigUint,
) -> Result<Self, PrivateKeyLoadError> {
let dmodp1 = &d % (&p - 1u64);
let dmodq1 = &d % (&q - 1u64);
Ok(PrivateKey {
n: public.n.clone(),
e: public.e.clone(),
d,
qinv,
p,
q,
dmodp1,
dmodq1,
})
}
}
/// Recover the two primes, `p` and `q`, used to generate the given private
/// key.
///
/// This algorithm is as straightforward an implementation of Appendix C.1
/// of NIST 800-56b, revision 2, as I could make it.
fn recover_primes(
n: &BigUint,
e: &BigUint,
d: &BigUint,
) -> Result<(BigUint, BigUint), PrivateKeyLoadError> {
// Assumptions:
// 1. The modulus n is the product of two prime factors p and q, with p > q.
// 2. Both p and q are less than 2^(nBits/2), where nBits ≥ 2048 is the bit length of n.
let n_bits = n.bits() * 8;
let max_p_or_q = BigUint::from(2u8).pow(n_bits / 2);
// 3. The public exponent e is an odd integer between 2^16 and 2^256.
let two = BigUint::from(2u64);
if e < &two.pow(16u64) || e > &two.pow(256u64) {
return Err(PrivateKeyLoadError::InvalidEValue(e.clone()));
}
// 4. The private exponent d is a positive integer that is less than λ(n) = LCM(p 1, q 1).
// 5. The exponents e and d satisfy de ≡ 1 (mod λ(n)).
// Implementation:
// 1. Let a = (de 1) × GCD(n 1, de 1).
let mut de_minus_1 = Zeroizing::new(d * e);
*de_minus_1 -= 1u64;
let n_minus_one = Zeroizing::new(n - 1u64);
let gcd_of_n1_and_de1 = Zeroizing::new(n_minus_one.gcd(&de_minus_1));
let a = Zeroizing::new(&*de_minus_1 * &*gcd_of_n1_and_de1);
// 2. Let m = a/n and r = a mn, so that a = mn + r and 0 ≤ r < n.
let m = Zeroizing::new(&*a / n);
let mn = Zeroizing::new(&*m * n);
if *mn > *a {
// if mn is greater than 'a', then 'r' is going to be negative, which
// violates our assumptions.
return Err(PrivateKeyLoadError::CouldNotRecoverPrimes);
}
let r = Zeroizing::new(&*a - (&*m * n));
if &*r >= n {
// this violates the other side condition of 2
return Err(PrivateKeyLoadError::CouldNotRecoverPrimes);
}
// 3. Let b = ( (n r)/(m + 1) ) + 1; if b is not an integer or b^2 ≤ 4n, then output an
// error indicator, and exit without further processing.
let b = Zeroizing::new(((n - &*r) / (&*m + 1u64)) + 1u64);
let b_squared = Zeroizing::new(&*b * &*b);
// 4n contains no secret information, actually, so no need to add the zeorize trait
let four_n = 4usize * n;
if *b_squared <= four_n {
return Err(PrivateKeyLoadError::CouldNotRecoverPrimes);
}
// 4. Let ϒ be the positive square root of b2 4n; if ϒ is not an integer, then output
// an error indicator, and exit without further processing.
let b_squared_minus_four_n = Zeroizing::new(&*b_squared - four_n);
let y = Zeroizing::new(sqrt((*b_squared_minus_four_n).clone()));
let cross_check = Zeroizing::new(&*y * &*y);
if cross_check != b_squared_minus_four_n {
return Err(PrivateKeyLoadError::CouldNotRecoverPrimes);
}
// 5. Let p = (b + ϒ)/2 and let q = (b ϒ)/2.
let mut p = &*b + &*y;
p >>= 1;
let mut q = &*b - &*y;
q >>= 1;
// go back and check some of our assumptions from above:
// 1. The modulus n is the product of two prime factors p and q, with p > q.
if n != &(&p * &q) {
p.zeroize();
q.zeroize();
return Err(PrivateKeyLoadError::CouldNotRecoverPrimes);
}
if p <= q {
p.zeroize();
q.zeroize();
return Err(PrivateKeyLoadError::CouldNotRecoverPrimes);
}
// 2. Both p and q are less than 2^(nBits/2), where nBits ≥ 2048 is the bit length of n.
if p >= max_p_or_q || q >= max_p_or_q {
p.zeroize();
q.zeroize();
return Err(PrivateKeyLoadError::CouldNotRecoverPrimes);
}
// 6. Output (p, q) as the prime factors.
Ok((p, q))
}