From 202672bce6ab89ac38078a56c5946fb1cc0f5528 Mon Sep 17 00:00:00 2001 From: Steve Fan <29133953+stevefan1999-personal@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:33:00 +0800 Subject: [PATCH 1/3] feat: add ChaCha20-Poly1305 session ticketer Provide a TicketRotator-backed session ticket producer using ChaCha20-Poly1305, for server-side TLS session resumption. --- Cargo.lock | 1 + Cargo.toml | 1 + src/lib.rs | 2 + src/ticketer.rs | 226 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 230 insertions(+) create mode 100644 src/ticketer.rs diff --git a/Cargo.lock b/Cargo.lock index 3199dc0..f7ced93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -675,6 +675,7 @@ dependencies = [ "sec1", "sha2", "signature", + "subtle", "x25519-dalek", ] diff --git a/Cargo.toml b/Cargo.toml index e8e5de0..5bad2e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ rustls = { version = "0.23", default-features = false } sec1 = { version = "0.8", default-features = false } sha2 = { version = "0.11", default-features = false } signature = { version = "3", default-features = false } +subtle = { version = "2", default-features = false } x25519-dalek = { version = "3", default-features = false } [features] diff --git a/src/lib.rs b/src/lib.rs index 6332bca..208b122 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -267,4 +267,6 @@ mod kx; mod misc; pub mod quic; pub mod sign; +#[cfg(feature = "std")] +pub mod ticketer; mod verify; diff --git a/src/ticketer.rs b/src/ticketer.rs new file mode 100644 index 0000000..4c6e5d1 --- /dev/null +++ b/src/ticketer.rs @@ -0,0 +1,226 @@ +//! Session ticket encryption using ChaCha20-Poly1305. +//! +//! Provides a [`Ticketer`] factory that wraps rustls [`TicketRotator`] with a +//! ChaCha20-Poly1305 AEAD backend for server-side TLS session resumption. + +use alloc::boxed::Box; +use alloc::sync::Arc; +use alloc::vec::Vec; +use core::fmt::{self, Debug, Formatter}; +use core::sync::atomic::{AtomicUsize, Ordering}; + +use aead::AeadInOut; +use chacha20poly1305::{ChaCha20Poly1305, KeyInit}; +use getrandom::rand_core::TryRng; +use rustls::crypto::GetRandomFailed; +use rustls::server::ProducesTickets; +use rustls::ticketer::TicketRotator; +use rustls::Error; +use subtle::ConstantTimeEq; + +fn try_split_at(data: &[u8], at: usize) -> Option<(&[u8], &[u8])> { + if data.len() < at { + None + } else { + Some(data.split_at(at)) + } +} + +fn fill_random(buf: &mut [u8]) -> Result<(), GetRandomFailed> { + getrandom::SysRng + .try_fill_bytes(buf) + .map_err(|_| GetRandomFailed) +} + +/// A concrete, safe ticket creation mechanism. +#[non_exhaustive] +pub struct Ticketer {} + +impl Ticketer { + /// Make the recommended `Ticketer`. This produces tickets with a 12 hour + /// life (via a 6-hour rotator) and randomly generated keys. + /// + /// The encryption mechanism used is ChaCha20-Poly1305. + #[allow(clippy::new_ret_no_self, clippy::missing_errors_doc)] + pub fn new() -> Result, Error> { + Ok(Arc::new(TicketRotator::new( + 6 * 60 * 60, + make_ticket_generator, + )?)) + } +} + +fn make_ticket_generator() -> Result, GetRandomFailed> { + Ok(Box::new(AeadTicketer::new()?)) +} + +/// `ProducesTickets` implementation using ChaCha20-Poly1305. +/// +/// Does not enforce lifetime constraints itself; intended for use under a +/// [`TicketRotator`] that manages rotation and advertised lifetime. +struct AeadTicketer { + key: ChaCha20Poly1305, + key_name: [u8; 16], + /// Tracks the largest ciphertext produced by `encrypt`, and uses it to + /// early-reject `decrypt` queries that are too long. + /// + /// Accepting excessively long ciphertexts means a "Partitioning Oracle + /// Attack" (see ) can be more + /// efficient, though also note that these are thought to be cryptographically + /// hard if the key is full-entropy (as it is here). + maximum_ciphertext_len: AtomicUsize, +} + +impl AeadTicketer { + fn new() -> Result { + let mut key_bytes = [0u8; 32]; + fill_random(&mut key_bytes)?; + + let key = ChaCha20Poly1305::new_from_slice(&key_bytes).map_err(|_| GetRandomFailed)?; + + let mut key_name = [0u8; 16]; + fill_random(&mut key_name)?; + + Ok(Self { + key, + key_name, + maximum_ciphertext_len: AtomicUsize::new(0), + }) + } +} + +impl ProducesTickets for AeadTicketer { + fn enabled(&self) -> bool { + true + } + + fn lifetime(&self) -> u32 { + // Not used when this ticketer is only used via a `TicketRotator` that is + // responsible for defining and managing the lifetime of tickets. + 0 + } + + /// Encrypt `message` and return the ciphertext. + fn encrypt(&self, message: &[u8]) -> Option> { + // Random nonce, because a counter is a privacy leak. + let mut nonce_buf = [0u8; 12]; + fill_random(&mut nonce_buf).ok()?; + let nonce = nonce_buf.into(); + + // ciphertext structure is: + // key_name: [u8; 16] + // nonce: [u8; 12] + // message: [u8, _] + // tag: [u8; 16] + + let mut ciphertext = + Vec::with_capacity(self.key_name.len() + nonce_buf.len() + message.len() + 16); + ciphertext.extend(self.key_name); + ciphertext.extend(nonce_buf); + ciphertext.extend(message); + let tag = self + .key + .encrypt_inout_detached( + &nonce, + &self.key_name, + (&mut ciphertext[self.key_name.len() + nonce_buf.len()..]).into(), + ) + .ok()?; + ciphertext.extend(tag.as_slice()); + + self + .maximum_ciphertext_len + .fetch_max(ciphertext.len(), Ordering::SeqCst); + Some(ciphertext) + } + + /// Decrypt `ciphertext` and recover the original message. + fn decrypt(&self, ciphertext: &[u8]) -> Option> { + if ciphertext.len() > self.maximum_ciphertext_len.load(Ordering::SeqCst) { + return None; + } + + let (alleged_key_name, ciphertext) = try_split_at(ciphertext, self.key_name.len())?; + + let (nonce_bytes, ciphertext) = try_split_at(ciphertext, 12)?; + + // checking the key_name is the expected one, *and* then putting it into the + // additionally authenticated data is duplicative. this check quickly rejects + // tickets for a different ticketer (see `TicketRotator`), while including it + // in the AAD ensures it is authenticated independent of that check and that + // any attempted attack on the integrity such as [^1] must happen for each + // `key_label`, not over a population of potential keys. this approach + // is overall similar to [^2]. + // + // [^1]: https://eprint.iacr.org/2020/1491.pdf + // [^2]: "Authenticated Encryption with Key Identification", fig 6 + // + if ConstantTimeEq::ct_ne(&self.key_name[..], alleged_key_name).into() { + return None; + } + + let nonce = nonce_bytes.try_into().ok()?; + + let mut out = Vec::from(ciphertext); + if out.len() < 16 { + return None; + } + let tag_vec = out.split_off(out.len() - 16); + let tag = tag_vec.as_slice().try_into().ok()?; + + self + .key + .decrypt_inout_detached(&nonce, alleged_key_name, (&mut out[..]).into(), &tag) + .ok()?; + + Some(out) + } +} + +impl Debug for AeadTicketer { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // Note: we deliberately omit the key from the debug output. + f.debug_struct("AeadTicketer").finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basic_pairwise_test() { + let t = Ticketer::new().unwrap(); + assert!(t.enabled()); + let cipher = t.encrypt(b"hello world").unwrap(); + let plain = t.decrypt(&cipher).unwrap(); + assert_eq!(plain, b"hello world"); + } + + #[test] + fn refuses_decrypt_before_encrypt() { + let t = Ticketer::new().unwrap(); + assert_eq!(t.decrypt(b"hello"), None); + } + + #[test] + fn refuses_decrypt_larger_than_largest_encryption() { + let t = Ticketer::new().unwrap(); + let mut cipher = t.encrypt(b"hello world").unwrap(); + assert_eq!(t.decrypt(&cipher), Some(b"hello world".to_vec())); + + // obviously this would never work anyway, but this + // and `refuses_decrypt_before_encrypt` exercise the + // first branch in `decrypt()` + cipher.push(0); + assert_eq!(t.decrypt(&cipher), None); + } + + #[test] + fn aead_ticketer_is_debug() { + let t = make_ticket_generator().unwrap(); + assert_eq!(alloc::format!("{t:?}"), "AeadTicketer"); + assert!(t.enabled()); + assert_eq!(t.lifetime(), 0); + } +} From b2ec51c0e81937f4cd43a655d3d32f097d5fc66d Mon Sep 17 00:00:00 2001 From: Steve Fan <29133953+stevefan1999-personal@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:43:59 +0800 Subject: [PATCH 2/3] fix: rustfmt ticketer module --- src/ticketer.rs | 322 ++++++++++++++++++++++++------------------------ 1 file changed, 160 insertions(+), 162 deletions(-) diff --git a/src/ticketer.rs b/src/ticketer.rs index 4c6e5d1..2408564 100644 --- a/src/ticketer.rs +++ b/src/ticketer.rs @@ -19,17 +19,17 @@ use rustls::Error; use subtle::ConstantTimeEq; fn try_split_at(data: &[u8], at: usize) -> Option<(&[u8], &[u8])> { - if data.len() < at { - None - } else { - Some(data.split_at(at)) - } + if data.len() < at { + None + } else { + Some(data.split_at(at)) + } } fn fill_random(buf: &mut [u8]) -> Result<(), GetRandomFailed> { - getrandom::SysRng - .try_fill_bytes(buf) - .map_err(|_| GetRandomFailed) + getrandom::SysRng + .try_fill_bytes(buf) + .map_err(|_| GetRandomFailed) } /// A concrete, safe ticket creation mechanism. @@ -37,21 +37,21 @@ fn fill_random(buf: &mut [u8]) -> Result<(), GetRandomFailed> { pub struct Ticketer {} impl Ticketer { - /// Make the recommended `Ticketer`. This produces tickets with a 12 hour - /// life (via a 6-hour rotator) and randomly generated keys. - /// - /// The encryption mechanism used is ChaCha20-Poly1305. - #[allow(clippy::new_ret_no_self, clippy::missing_errors_doc)] - pub fn new() -> Result, Error> { - Ok(Arc::new(TicketRotator::new( - 6 * 60 * 60, - make_ticket_generator, - )?)) - } + /// Make the recommended `Ticketer`. This produces tickets with a 12 hour + /// life (via a 6-hour rotator) and randomly generated keys. + /// + /// The encryption mechanism used is ChaCha20-Poly1305. + #[allow(clippy::new_ret_no_self, clippy::missing_errors_doc)] + pub fn new() -> Result, Error> { + Ok(Arc::new(TicketRotator::new( + 6 * 60 * 60, + make_ticket_generator, + )?)) + } } fn make_ticket_generator() -> Result, GetRandomFailed> { - Ok(Box::new(AeadTicketer::new()?)) + Ok(Box::new(AeadTicketer::new()?)) } /// `ProducesTickets` implementation using ChaCha20-Poly1305. @@ -59,168 +59,166 @@ fn make_ticket_generator() -> Result, GetRandomFailed> /// Does not enforce lifetime constraints itself; intended for use under a /// [`TicketRotator`] that manages rotation and advertised lifetime. struct AeadTicketer { - key: ChaCha20Poly1305, - key_name: [u8; 16], - /// Tracks the largest ciphertext produced by `encrypt`, and uses it to - /// early-reject `decrypt` queries that are too long. - /// - /// Accepting excessively long ciphertexts means a "Partitioning Oracle - /// Attack" (see ) can be more - /// efficient, though also note that these are thought to be cryptographically - /// hard if the key is full-entropy (as it is here). - maximum_ciphertext_len: AtomicUsize, + key: ChaCha20Poly1305, + key_name: [u8; 16], + /// Tracks the largest ciphertext produced by `encrypt`, and uses it to + /// early-reject `decrypt` queries that are too long. + /// + /// Accepting excessively long ciphertexts means a "Partitioning Oracle + /// Attack" (see ) can be more + /// efficient, though also note that these are thought to be cryptographically + /// hard if the key is full-entropy (as it is here). + maximum_ciphertext_len: AtomicUsize, } impl AeadTicketer { - fn new() -> Result { - let mut key_bytes = [0u8; 32]; - fill_random(&mut key_bytes)?; + fn new() -> Result { + let mut key_bytes = [0u8; 32]; + fill_random(&mut key_bytes)?; - let key = ChaCha20Poly1305::new_from_slice(&key_bytes).map_err(|_| GetRandomFailed)?; + let key = ChaCha20Poly1305::new_from_slice(&key_bytes).map_err(|_| GetRandomFailed)?; - let mut key_name = [0u8; 16]; - fill_random(&mut key_name)?; + let mut key_name = [0u8; 16]; + fill_random(&mut key_name)?; - Ok(Self { - key, - key_name, - maximum_ciphertext_len: AtomicUsize::new(0), - }) - } + Ok(Self { + key, + key_name, + maximum_ciphertext_len: AtomicUsize::new(0), + }) + } } impl ProducesTickets for AeadTicketer { - fn enabled(&self) -> bool { - true - } - - fn lifetime(&self) -> u32 { - // Not used when this ticketer is only used via a `TicketRotator` that is - // responsible for defining and managing the lifetime of tickets. - 0 - } - - /// Encrypt `message` and return the ciphertext. - fn encrypt(&self, message: &[u8]) -> Option> { - // Random nonce, because a counter is a privacy leak. - let mut nonce_buf = [0u8; 12]; - fill_random(&mut nonce_buf).ok()?; - let nonce = nonce_buf.into(); - - // ciphertext structure is: - // key_name: [u8; 16] - // nonce: [u8; 12] - // message: [u8, _] - // tag: [u8; 16] - - let mut ciphertext = - Vec::with_capacity(self.key_name.len() + nonce_buf.len() + message.len() + 16); - ciphertext.extend(self.key_name); - ciphertext.extend(nonce_buf); - ciphertext.extend(message); - let tag = self - .key - .encrypt_inout_detached( - &nonce, - &self.key_name, - (&mut ciphertext[self.key_name.len() + nonce_buf.len()..]).into(), - ) - .ok()?; - ciphertext.extend(tag.as_slice()); - - self - .maximum_ciphertext_len - .fetch_max(ciphertext.len(), Ordering::SeqCst); - Some(ciphertext) - } - - /// Decrypt `ciphertext` and recover the original message. - fn decrypt(&self, ciphertext: &[u8]) -> Option> { - if ciphertext.len() > self.maximum_ciphertext_len.load(Ordering::SeqCst) { - return None; + fn enabled(&self) -> bool { + true } - let (alleged_key_name, ciphertext) = try_split_at(ciphertext, self.key_name.len())?; - - let (nonce_bytes, ciphertext) = try_split_at(ciphertext, 12)?; - - // checking the key_name is the expected one, *and* then putting it into the - // additionally authenticated data is duplicative. this check quickly rejects - // tickets for a different ticketer (see `TicketRotator`), while including it - // in the AAD ensures it is authenticated independent of that check and that - // any attempted attack on the integrity such as [^1] must happen for each - // `key_label`, not over a population of potential keys. this approach - // is overall similar to [^2]. - // - // [^1]: https://eprint.iacr.org/2020/1491.pdf - // [^2]: "Authenticated Encryption with Key Identification", fig 6 - // - if ConstantTimeEq::ct_ne(&self.key_name[..], alleged_key_name).into() { - return None; + fn lifetime(&self) -> u32 { + // Not used when this ticketer is only used via a `TicketRotator` that is + // responsible for defining and managing the lifetime of tickets. + 0 } - let nonce = nonce_bytes.try_into().ok()?; - - let mut out = Vec::from(ciphertext); - if out.len() < 16 { - return None; + /// Encrypt `message` and return the ciphertext. + fn encrypt(&self, message: &[u8]) -> Option> { + // Random nonce, because a counter is a privacy leak. + let mut nonce_buf = [0u8; 12]; + fill_random(&mut nonce_buf).ok()?; + let nonce = nonce_buf.into(); + + // ciphertext structure is: + // key_name: [u8; 16] + // nonce: [u8; 12] + // message: [u8, _] + // tag: [u8; 16] + + let mut ciphertext = + Vec::with_capacity(self.key_name.len() + nonce_buf.len() + message.len() + 16); + ciphertext.extend(self.key_name); + ciphertext.extend(nonce_buf); + ciphertext.extend(message); + let tag = self + .key + .encrypt_inout_detached( + &nonce, + &self.key_name, + (&mut ciphertext[self.key_name.len() + nonce_buf.len()..]).into(), + ) + .ok()?; + ciphertext.extend(tag.as_slice()); + + self.maximum_ciphertext_len + .fetch_max(ciphertext.len(), Ordering::SeqCst); + Some(ciphertext) } - let tag_vec = out.split_off(out.len() - 16); - let tag = tag_vec.as_slice().try_into().ok()?; - self - .key - .decrypt_inout_detached(&nonce, alleged_key_name, (&mut out[..]).into(), &tag) - .ok()?; - - Some(out) - } + /// Decrypt `ciphertext` and recover the original message. + fn decrypt(&self, ciphertext: &[u8]) -> Option> { + if ciphertext.len() > self.maximum_ciphertext_len.load(Ordering::SeqCst) { + return None; + } + + let (alleged_key_name, ciphertext) = try_split_at(ciphertext, self.key_name.len())?; + + let (nonce_bytes, ciphertext) = try_split_at(ciphertext, 12)?; + + // checking the key_name is the expected one, *and* then putting it into the + // additionally authenticated data is duplicative. this check quickly rejects + // tickets for a different ticketer (see `TicketRotator`), while including it + // in the AAD ensures it is authenticated independent of that check and that + // any attempted attack on the integrity such as [^1] must happen for each + // `key_label`, not over a population of potential keys. this approach + // is overall similar to [^2]. + // + // [^1]: https://eprint.iacr.org/2020/1491.pdf + // [^2]: "Authenticated Encryption with Key Identification", fig 6 + // + if ConstantTimeEq::ct_ne(&self.key_name[..], alleged_key_name).into() { + return None; + } + + let nonce = nonce_bytes.try_into().ok()?; + + let mut out = Vec::from(ciphertext); + if out.len() < 16 { + return None; + } + let tag_vec = out.split_off(out.len() - 16); + let tag = tag_vec.as_slice().try_into().ok()?; + + self.key + .decrypt_inout_detached(&nonce, alleged_key_name, (&mut out[..]).into(), &tag) + .ok()?; + + Some(out) + } } impl Debug for AeadTicketer { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // Note: we deliberately omit the key from the debug output. - f.debug_struct("AeadTicketer").finish() - } + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // Note: we deliberately omit the key from the debug output. + f.debug_struct("AeadTicketer").finish() + } } #[cfg(test)] mod tests { - use super::*; - - #[test] - fn basic_pairwise_test() { - let t = Ticketer::new().unwrap(); - assert!(t.enabled()); - let cipher = t.encrypt(b"hello world").unwrap(); - let plain = t.decrypt(&cipher).unwrap(); - assert_eq!(plain, b"hello world"); - } - - #[test] - fn refuses_decrypt_before_encrypt() { - let t = Ticketer::new().unwrap(); - assert_eq!(t.decrypt(b"hello"), None); - } - - #[test] - fn refuses_decrypt_larger_than_largest_encryption() { - let t = Ticketer::new().unwrap(); - let mut cipher = t.encrypt(b"hello world").unwrap(); - assert_eq!(t.decrypt(&cipher), Some(b"hello world".to_vec())); - - // obviously this would never work anyway, but this - // and `refuses_decrypt_before_encrypt` exercise the - // first branch in `decrypt()` - cipher.push(0); - assert_eq!(t.decrypt(&cipher), None); - } - - #[test] - fn aead_ticketer_is_debug() { - let t = make_ticket_generator().unwrap(); - assert_eq!(alloc::format!("{t:?}"), "AeadTicketer"); - assert!(t.enabled()); - assert_eq!(t.lifetime(), 0); - } + use super::*; + + #[test] + fn basic_pairwise_test() { + let t = Ticketer::new().unwrap(); + assert!(t.enabled()); + let cipher = t.encrypt(b"hello world").unwrap(); + let plain = t.decrypt(&cipher).unwrap(); + assert_eq!(plain, b"hello world"); + } + + #[test] + fn refuses_decrypt_before_encrypt() { + let t = Ticketer::new().unwrap(); + assert_eq!(t.decrypt(b"hello"), None); + } + + #[test] + fn refuses_decrypt_larger_than_largest_encryption() { + let t = Ticketer::new().unwrap(); + let mut cipher = t.encrypt(b"hello world").unwrap(); + assert_eq!(t.decrypt(&cipher), Some(b"hello world".to_vec())); + + // obviously this would never work anyway, but this + // and `refuses_decrypt_before_encrypt` exercise the + // first branch in `decrypt()` + cipher.push(0); + assert_eq!(t.decrypt(&cipher), None); + } + + #[test] + fn aead_ticketer_is_debug() { + let t = make_ticket_generator().unwrap(); + assert_eq!(alloc::format!("{t:?}"), "AeadTicketer"); + assert!(t.enabled()); + assert_eq!(t.lifetime(), 0); + } } From 6400b41381950f0bc00565678058c491ee7c1699 Mon Sep 17 00:00:00 2001 From: Steve Fan <29133953+stevefan1999-personal@users.noreply.github.com> Date: Wed, 5 Aug 2026 21:05:06 +0800 Subject: [PATCH 3/3] chore(deps): cargo update to latest crate versions --- Cargo.lock | 59 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7ced93..2a69f57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" dependencies = [ "cipher", "cpubits", @@ -66,9 +66,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.67" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "shlex", @@ -220,7 +220,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -393,9 +393,9 @@ dependencies = [ [[package]] name = "hybrid-array" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" dependencies = [ "subtle", "typenum", @@ -413,9 +413,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "log" @@ -548,18 +548,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -630,9 +630,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.42" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "log", "once_cell", @@ -644,9 +644,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "zeroize", ] @@ -712,31 +712,31 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", ] [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -803,6 +803,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "typenum" version = "1.20.1"