Skip to content

Commit

Permalink
Implemented config and SHA puzzle.
Browse files Browse the repository at this point in the history
  • Loading branch information
davidv1992 committed Sep 18, 2024
1 parent e7069b8 commit aeb4e25
Show file tree
Hide file tree
Showing 16 changed files with 184 additions and 32 deletions.
1 change: 1 addition & 0 deletions provider-example/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use alloc::boxed::Box;
use rustls::crypto::hash;
use sha2::Digest;

#[derive(Debug)]
pub struct Sha256;

impl hash::Hash for Sha256 {
Expand Down
2 changes: 2 additions & 0 deletions provider-example/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ extern crate std;

use alloc::sync::Arc;

use hash::Sha256;
use rustls::crypto::CryptoProvider;
use rustls::pki_types::PrivateKeyDer;

Expand All @@ -25,6 +26,7 @@ pub fn provider() -> CryptoProvider {
signature_verification_algorithms: verify::ALGORITHMS,
secure_random: &Provider,
key_provider: &Provider,
sha256_hasher: &Sha256,
}
}

Expand Down
1 change: 1 addition & 0 deletions rustls/src/client/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ impl ConfigBuilder<ClientConfig, WantsClientCert> {
cert_compression_cache: Arc::new(compress::CompressionCache::default()),
cert_decompressors: compress::default_cert_decompressors().to_vec(),
ech_mode: self.state.client_ech_mode,
client_puzzle_timeout: 0,
}
}
}
7 changes: 7 additions & 0 deletions rustls/src/client/client_conn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,13 @@ pub struct ClientConfig {

/// How to offer Encrypted Client Hello (ECH). The default is to not offer ECH.
pub(super) ech_mode: Option<EchMode>,

/// Timeout (in milliseconds) used to limit time spent solving a client puzzle.
///
/// A value of 0 indicates support for client puzzles is disabled (the default)
///
/// Note that in a no_std environment, this timeout is not enforced!
pub client_puzzle_timeout: u64,
}

impl ClientConfig {
Expand Down
23 changes: 16 additions & 7 deletions rustls/src/client/hs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,14 +347,23 @@ fn emit_client_hello_for_retry(
}

if let Some(challenge) = retryreq.and_then(|r| r.puzzle_challenge()) {
let solution = challenge.solve().ok_or_else(|| {
cx.common.send_fatal_alert(
AlertDescription::IllegalParameter,
if config.client_puzzle_timeout != 0 {
let solution = challenge
.solve(config.client_puzzle_timeout, &config.provider)
.ok_or_else(|| {
cx.common.send_fatal_alert(
AlertDescription::IllegalParameter,
PeerMisbehaved::IllegalHelloRetryRequestWithUnsolvablePuzzle,
)
})?;
exts.push(ClientExtension::ClientPuzzle(solution));
} else {
return Err(cx.common.send_fatal_alert(
AlertDescription::UnsupportedExtension,
PeerMisbehaved::IllegalHelloRetryRequestWithUnsolvablePuzzle,
)
})?;
exts.push(ClientExtension::ClientPuzzle(solution));
} else {
));
}
} else if support_tls13 && config.client_puzzle_timeout != 0 {
exts.push(ClientExtension::ClientPuzzle(
ClientPuzzle::support_indicator(),
));
Expand Down
1 change: 1 addition & 0 deletions rustls/src/crypto/aws_lc_rs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down
3 changes: 2 additions & 1 deletion rustls/src/crypto/hash.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use alloc::boxed::Box;
use core::fmt::Debug;

pub use crate::msgs::enums::HashAlgorithm;

/// Describes a single cryptographic hash function.
///
/// 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<dyn Context>;

Expand Down
5 changes: 5 additions & 0 deletions rustls/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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())
Expand Down
1 change: 1 addition & 0 deletions rustls/src/crypto/ring/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions rustls/src/crypto/ring/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down
2 changes: 1 addition & 1 deletion rustls/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ pub mod server {
#[cfg(any(feature = "std", feature = "hashbrown"))]
pub use handy::ServerSessionMemoryCache;
pub use server_conn::{
Accepted, ClientHello, ProducesTickets, ResolvesServerCert, ServerConfig,
Accepted, ClientHello, ProducesTickets, PuzzleConfig, ResolvesServerCert, ServerConfig,
ServerConnectionData, StoresServerSessions, UnbufferedServerConnection,
};
#[cfg(feature = "std")]
Expand Down
1 change: 1 addition & 0 deletions rustls/src/msgs/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ enum_builder! {
@U16
pub enum ClientPuzzleType {
COOKIE => 0,
SHA256 => 1,
}
}

Expand Down
96 changes: 89 additions & 7 deletions rustls/src/msgs/handshake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -27,6 +27,7 @@ use crate::msgs::enums::{
ECPointFormat, EchVersion, ExtensionType, HpkeAead, HpkeKdf, HpkeKem, KeyUpdateRequest,
NamedGroup, PSKKeyExchangeMode, ServerNameType,
};
use crate::server::PuzzleConfig;
use crate::verify::DigitallySignedStruct;
use crate::x509::wrap_in_sequence;
use crate::{rand, Error};
Expand Down Expand Up @@ -532,6 +533,7 @@ impl TlsListElement for ClientPuzzleType {
pub enum ClientPuzzle {
SupportIndication(Vec<ClientPuzzleType>),
Cookie(PayloadU16),
Sha256(PayloadU16),
Unknown(ClientPuzzleType, PayloadU16),
}

Expand All @@ -546,6 +548,10 @@ impl Codec<'_> for ClientPuzzle {
vec![ClientPuzzleType::COOKIE].encode(bytes);
cookie.encode(bytes);
}
Self::Sha256(solution) => {
vec![ClientPuzzleType::SHA256].encode(bytes);
solution.encode(bytes);
}
Self::Unknown(puzzletype, data) => {
vec![*puzzletype].encode(bytes);
data.encode(bytes);
Expand All @@ -562,6 +568,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),
}
Expand All @@ -570,12 +577,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: &[u8] = b"TLS SHA256CPUPuzzle\0";
let mut buf = vec![0; challenge.0.len() + size_of::<u64>() + 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,
}
}
Expand All @@ -584,6 +607,7 @@ impl ClientPuzzle {
#[derive(Debug, Clone)]
pub enum ClientPuzzleChallenge {
Cookie(PayloadU16),
Sha256(u8, PayloadU16),
Unknown(ClientPuzzleType, PayloadU16),
}

Expand All @@ -594,6 +618,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);
Expand All @@ -608,23 +638,75 @@ 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),
}
}
}

impl ClientPuzzleChallenge {
pub fn new_cookie(secure_random: &dyn SecureRandom) -> Result<Self, Error> {
pub fn new(config: PuzzleConfig, secure_random: &dyn SecureRandom) -> Result<Self, Error> {
match config {
PuzzleConfig::Sha256 { difficulty } => Self::new_sha(difficulty, secure_random),
PuzzleConfig::Cookie => Self::new_cookie(secure_random),
}
}

fn new_cookie(secure_random: &dyn SecureRandom) -> Result<Self, Error> {
let mut challenge = vec![0; 16];
secure_random.fill(&mut challenge)?;
Ok(Self::Cookie(PayloadU16(challenge)))
}

pub fn solve(&self) -> Option<ClientPuzzle> {
fn new_sha(difficulty: u8, secure_random: &dyn SecureRandom) -> Result<Self, Error> {
let mut challenge = vec![0; 16];
secure_random.fill(&mut challenge)?;
Ok(Self::Sha256(difficulty, PayloadU16::new(challenge)))
}

pub fn solve(&self, timeout: u64, provider: &CryptoProvider) -> Option<ClientPuzzle> {
match self {
Self::Cookie(challenge) => {
Some(ClientPuzzle::Cookie(challenge.clone()))
Self::Cookie(challenge) => Some(ClientPuzzle::Cookie(challenge.clone())),
Self::Sha256(difficulty, challenge) => {
#[cfg(feature = "std")]
let start = std::time::Instant::now();
const TAIL: &[u8] = b"TLS SHA256CPUPuzzle\0";
let mut buf = vec![0; challenge.0.len() + size_of::<u64>() + TAIL.len()];
buf[0..challenge.0.len()].copy_from_slice(&challenge.0);
buf[challenge.0.len() + size_of::<u64>()..].copy_from_slice(TAIL);
let full = difficulty / 8;
let partial = difficulty % 8;
for sol in 0..u64::MAX {
#[cfg(feature = "std")]
if sol % 256 == 0
&& std::time::Instant::now().duration_since(start)
> core::time::Duration::from_millis(timeout)
{
return None;
}
buf[challenge.0.len()..][..size_of::<u64>()]
.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
}
Self::Unknown(_, _) => None,
}
Expand Down
1 change: 1 addition & 0 deletions rustls/src/server/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ impl ConfigBuilder<ServerConfig, WantsServerCert> {
cert_compressors: compress::default_cert_compressors().to_vec(),
cert_compression_cache: Arc::new(compress::CompressionCache::default()),
cert_decompressors: compress::default_cert_decompressors().to_vec(),
client_puzzles: Vec::new(),
}
}
}
27 changes: 27 additions & 0 deletions rustls/src/server/server_conn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::error::Error;
#[cfg(feature = "logging")]
use crate::log::trace;
use crate::msgs::base::Payload;
use crate::msgs::enums::ClientPuzzleType;
use crate::msgs::handshake::{ClientHelloPayload, ProtocolName, ServerExtension};
use crate::msgs::message::Message;
#[cfg(feature = "std")]
Expand Down Expand Up @@ -371,6 +372,32 @@ pub struct ServerConfig {
///
/// [RFC8779]: https://datatracker.ietf.org/doc/rfc8879/
pub cert_decompressors: Vec<&'static dyn compress::CertDecompressor>,

/// Puzzles to challenge the client with.
///
/// If this is empty, no client puzzle will be sent.
pub client_puzzles: Vec<PuzzleConfig>,
}

/// Configuration for a single supported puzzle type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PuzzleConfig {
/// A Sha256 based puzzle.
Sha256 {
/// Difficulty of the puzzle, incrementing by 1 doubles the amount of effort needed to solve.
difficulty: u8,
},
/// A simple echo puzzle, usefull for testing.
Cookie,
}

impl PuzzleConfig {
pub(super) fn puzzle_type(&self) -> ClientPuzzleType {
match self {
Self::Sha256 { .. } => ClientPuzzleType::SHA256,
Self::Cookie => ClientPuzzleType::COOKIE,
}
}
}

impl ServerConfig {
Expand Down
Loading

0 comments on commit aeb4e25

Please sign in to comment.