From d29c65b1b06b0d6555d16d86826ddb614442e60e Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 23 Aug 2024 17:04:50 +0200 Subject: [PATCH] WIP --- rustls/src/client/hs.rs | 2 +- rustls/src/crypto/aws_lc_rs/mod.rs | 1 + rustls/src/crypto/hash.rs | 3 +- rustls/src/crypto/mod.rs | 5 ++ rustls/src/crypto/ring/hash.rs | 1 + rustls/src/crypto/ring/mod.rs | 1 + rustls/src/msgs/enums.rs | 1 + rustls/src/msgs/handshake.rs | 80 ++++++++++++++++++++++++++++-- rustls/src/server/tls13.rs | 6 +-- 9 files changed, 91 insertions(+), 9 deletions(-) diff --git a/rustls/src/client/hs.rs b/rustls/src/client/hs.rs index 38b756ba..d1350226 100644 --- a/rustls/src/client/hs.rs +++ b/rustls/src/client/hs.rs @@ -347,7 +347,7 @@ fn emit_client_hello_for_retry( } if let Some(challenge) = retryreq.and_then(|r| r.puzzle_challenge()) { - let Some(solution) = challenge.solve() else { + let Some(solution) = challenge.solve(&config.provider) else { return Err(cx.common.send_fatal_alert( AlertDescription::IllegalParameter, PeerMisbehaved::IllegalHelloRetryRequestWithUnsolvablePuzzle, diff --git a/rustls/src/crypto/aws_lc_rs/mod.rs b/rustls/src/crypto/aws_lc_rs/mod.rs index 407658ff..967c55aa 100644 --- a/rustls/src/crypto/aws_lc_rs/mod.rs +++ b/rustls/src/crypto/aws_lc_rs/mod.rs @@ -44,6 +44,7 @@ pub fn default_provider() -> CryptoProvider { kx_groups: default_kx_groups(), signature_verification_algorithms: SUPPORTED_SIG_ALGS, secure_random: &AwsLcRs, + sha256_hasher: &hash::SHA256, key_provider: &AwsLcRs, } } diff --git a/rustls/src/crypto/hash.rs b/rustls/src/crypto/hash.rs index 214dad4e..710dc6ca 100644 --- a/rustls/src/crypto/hash.rs +++ b/rustls/src/crypto/hash.rs @@ -1,4 +1,5 @@ use alloc::boxed::Box; +use core::fmt::Debug; pub use crate::msgs::enums::HashAlgorithm; @@ -6,7 +7,7 @@ pub use crate::msgs::enums::HashAlgorithm; /// /// This interface can do both one-shot and incremental hashing, using /// [`Hash::hash()`] and [`Hash::start()`] respectively. -pub trait Hash: Send + Sync { +pub trait Hash: Send + Sync + Debug { /// Start an incremental hash computation. fn start(&self) -> Box; diff --git a/rustls/src/crypto/mod.rs b/rustls/src/crypto/mod.rs index b73f0115..c88b6b94 100644 --- a/rustls/src/crypto/mod.rs +++ b/rustls/src/crypto/mod.rs @@ -10,6 +10,7 @@ use once_cell::sync::OnceCell; use pki_types::PrivateKeyDer; use zeroize::Zeroize; +use crate::crypto::hash::Hash; use crate::sign::SigningKey; pub use crate::webpki::{ verify_tls12_signature, verify_tls13_signature, WebPkiSupportedAlgorithms, @@ -209,6 +210,9 @@ pub struct CryptoProvider { /// Source of cryptographically secure random numbers. pub secure_random: &'static dyn SecureRandom, + /// Sha256 hasher + pub sha256_hasher: &'static dyn Hash, + /// Provider for loading private [SigningKey]s from [PrivateKeyDer]. pub key_provider: &'static dyn KeyProvider, } @@ -295,6 +299,7 @@ impl CryptoProvider { signature_verification_algorithms, secure_random, key_provider, + .. } = self; cipher_suites.iter().all(|cs| cs.fips()) && kx_groups.iter().all(|kx| kx.fips()) diff --git a/rustls/src/crypto/ring/hash.rs b/rustls/src/crypto/ring/hash.rs index 220dc536..e2971022 100644 --- a/rustls/src/crypto/ring/hash.rs +++ b/rustls/src/crypto/ring/hash.rs @@ -9,6 +9,7 @@ use crate::msgs::enums::HashAlgorithm; pub(crate) static SHA256: Hash = Hash(&digest::SHA256, HashAlgorithm::SHA256); pub(crate) static SHA384: Hash = Hash(&digest::SHA384, HashAlgorithm::SHA384); +#[derive(Debug)] pub(crate) struct Hash(&'static digest::Algorithm, HashAlgorithm); impl crypto::hash::Hash for Hash { diff --git a/rustls/src/crypto/ring/mod.rs b/rustls/src/crypto/ring/mod.rs index b24c245d..6e1c0021 100644 --- a/rustls/src/crypto/ring/mod.rs +++ b/rustls/src/crypto/ring/mod.rs @@ -35,6 +35,7 @@ pub fn default_provider() -> CryptoProvider { kx_groups: ALL_KX_GROUPS.to_vec(), signature_verification_algorithms: SUPPORTED_SIG_ALGS, secure_random: &Ring, + sha256_hasher: &hash::SHA256, key_provider: &Ring, } } diff --git a/rustls/src/msgs/enums.rs b/rustls/src/msgs/enums.rs index 90c1419e..a215d452 100644 --- a/rustls/src/msgs/enums.rs +++ b/rustls/src/msgs/enums.rs @@ -396,6 +396,7 @@ enum_builder! { @U16 pub enum ClientPuzzleType { COOKIE => 0, + SHA256 => 1, } } diff --git a/rustls/src/msgs/handshake.rs b/rustls/src/msgs/handshake.rs index 7aa8f129..4e8259fc 100644 --- a/rustls/src/msgs/handshake.rs +++ b/rustls/src/msgs/handshake.rs @@ -10,7 +10,7 @@ use pki_types::{CertificateDer, DnsName}; #[cfg(feature = "tls12")] use crate::crypto::ActiveKeyExchange; -use crate::crypto::SecureRandom; +use crate::crypto::{CryptoProvider, SecureRandom}; use crate::enums::{ CertificateCompressionAlgorithm, CipherSuite, EchClientHelloType, HandshakeType, ProtocolVersion, SignatureScheme, @@ -532,6 +532,7 @@ impl TlsListElement for ClientPuzzleType { pub enum ClientPuzzle { SupportIndication(Vec), Cookie(PayloadU16), + Sha256(PayloadU16), Unknown(ClientPuzzleType, PayloadU16), } @@ -546,6 +547,10 @@ impl Codec<'_> for ClientPuzzle { vec![ClientPuzzleType::COOKIE].encode(bytes); cookie.encode(bytes); } + ClientPuzzle::Sha256(solution) => { + vec![ClientPuzzleType::SHA256].encode(bytes); + solution.encode(bytes); + } ClientPuzzle::Unknown(puzzletype, data) => { vec![*puzzletype].encode(bytes); data.encode(bytes); @@ -562,6 +567,7 @@ impl Codec<'_> for ClientPuzzle { ([], _) => Err(InvalidMessage::InvalidClientPuzzle), (_, 0) => Ok(Self::SupportIndication(puzzletype)), ([ClientPuzzleType::COOKIE], _) => Ok(Self::Cookie(PayloadU16(sub.rest().into()))), + ([ClientPuzzleType::SHA256], _) => Ok(Self::Sha256(PayloadU16(sub.rest().into()))), ([unknown], _) => Ok(Self::Unknown(*unknown, PayloadU16(sub.rest().into()))), _ => Err(InvalidMessage::InvalidClientPuzzle), } @@ -570,12 +576,28 @@ impl Codec<'_> for ClientPuzzle { impl ClientPuzzle { pub fn support_indicator() -> Self { - Self::SupportIndication(vec![ClientPuzzleType::COOKIE]) + Self::SupportIndication(vec![ClientPuzzleType::COOKIE, ClientPuzzleType::SHA256]) } - pub fn check(&self, challenge: &ClientPuzzleChallenge) -> bool { + pub fn check(&self, challenge: &ClientPuzzleChallenge, provider: &CryptoProvider) -> bool { match (self, challenge) { (Self::Cookie(actual), ClientPuzzleChallenge::Cookie(expected)) => actual == expected, + (Self::Sha256(solution), ClientPuzzleChallenge::Sha256(difficulty, challenge)) => { + const TAIL: &'static [u8] = b"TLS SHA256CPUPuzzle\0"; + let mut buf = vec![0; challenge.0.len() + size_of::() + TAIL.len()]; + buf[0..challenge.0.len()].copy_from_slice(&challenge.0); + buf[challenge.0.len()..][..solution.0.len()].copy_from_slice(&solution.0); + buf[challenge.0.len() + solution.0.len()..].copy_from_slice(TAIL); + let full = difficulty / 8; + let partial = difficulty % 8; + let hash = provider.sha256_hasher.hash(&buf); + let hashbytes: &[u8] = hash.as_ref(); + hashbytes + .iter() + .take(full as _) + .all(|v| *v == 0) + && hashbytes[full as usize].leading_zeros() >= partial as _ + } _ => false, } } @@ -584,6 +606,7 @@ impl ClientPuzzle { #[derive(Debug, Clone)] pub enum ClientPuzzleChallenge { Cookie(PayloadU16), + Sha256(u8, PayloadU16), Unknown(ClientPuzzleType, PayloadU16), } @@ -594,6 +617,12 @@ impl Codec<'_> for ClientPuzzleChallenge { vec![ClientPuzzleType::COOKIE].encode(bytes); cookie.encode(bytes); } + Self::Sha256(difficulty, challenge) => { + vec![ClientPuzzleType::SHA256].encode(bytes); + let nested = LengthPrefixedBuffer::new(ListLength::U16, bytes); + difficulty.encode(nested.buf); + challenge.encode(nested.buf); + } Self::Unknown(puzzletype, data) => { vec![*puzzletype].encode(bytes); data.encode(bytes); @@ -608,6 +637,12 @@ impl Codec<'_> for ClientPuzzleChallenge { match &puzzletype[..] { [ClientPuzzleType::COOKIE] => Ok(Self::Cookie(PayloadU16(sub.rest().into()))), + [ClientPuzzleType::SHA256] => { + let difficulty = u8::read(&mut sub)?; + let challenge = PayloadU16::read(&mut sub)?; + sub.expect_empty("puzzle")?; + Ok(Self::Sha256(difficulty, challenge)) + } [unknown] => Ok(Self::Unknown(*unknown, PayloadU16(sub.rest().into()))), _ => Err(InvalidMessage::InvalidClientPuzzle), } @@ -621,11 +656,48 @@ impl ClientPuzzleChallenge { Ok(Self::Cookie(PayloadU16(challenge))) } - pub fn solve(&self) -> Option { + pub fn new_sha(difficulty: u8, secure_random: &dyn SecureRandom) -> Result { + let mut challenge = vec![0; 16]; + secure_random.fill(&mut challenge)?; + Ok(Self::Sha256(difficulty, PayloadU16::new(challenge))) + } + + pub fn solve(&self, provider: &CryptoProvider) -> Option { match self { ClientPuzzleChallenge::Cookie(challenge) => { Some(ClientPuzzle::Cookie(challenge.clone())) } + ClientPuzzleChallenge::Sha256(difficulty, challenge) => { + if *difficulty > 32 { + // too difficult + return None; + } + + const TAIL: &'static [u8] = b"TLS SHA256CPUPuzzle\0"; + let mut buf = vec![0; challenge.0.len() + size_of::() + TAIL.len()]; + buf[0..challenge.0.len()].copy_from_slice(&challenge.0); + buf[challenge.0.len() + size_of::()..].copy_from_slice(TAIL); + let full = difficulty / 8; + let partial = difficulty % 8; + for sol in 0..u64::MAX { + buf[challenge.0.len()..][..size_of::()] + .copy_from_slice(&sol.to_ne_bytes()); + + let hash = provider.sha256_hasher.hash(&buf); + let hashbytes: &[u8] = hash.as_ref(); + if hashbytes + .iter() + .take(full as _) + .all(|v| *v == 0) + && hashbytes[full as usize].leading_zeros() >= partial as _ + { + return Some(ClientPuzzle::Sha256(PayloadU16::new( + sol.to_ne_bytes().into(), + ))); + } + } + None + } ClientPuzzleChallenge::Unknown(_, _) => None, } } diff --git a/rustls/src/server/tls13.rs b/rustls/src/server/tls13.rs index e6c9ecf2..2a340f97 100644 --- a/rustls/src/server/tls13.rs +++ b/rustls/src/server/tls13.rs @@ -200,7 +200,7 @@ mod client_hello { let chosen_share_and_kxg = if let (Some(challenge), Some(solution)) = (&self.challenge, client_hello.puzzle_solution()) { - if solution.check(challenge) { + if solution.check(challenge, &self.config.provider) { chosen_share_and_kxg } else { None @@ -225,7 +225,7 @@ mod client_hello { if !client_hello .supported_puzzles() - .contains(&crate::msgs::enums::ClientPuzzleType::COOKIE) + .contains(&crate::msgs::enums::ClientPuzzleType::SHA256) { return Err(cx.common.send_fatal_alert( AlertDescription::HandshakeFailure, @@ -234,7 +234,7 @@ mod client_hello { } let challenge = - ClientPuzzleChallenge::new_cookie(self.config.provider.secure_random)?; + ClientPuzzleChallenge::new_sha(8, self.config.provider.secure_random)?; emit_hello_retry_request( &mut self.transcript,