feat(quic): support stateless resets

Resolves #3419.

Pull-Request: #4554.
This commit is contained in:
João Oliveira
2023-10-02 01:28:32 +01:00
committed by GitHub
parent c86d1117be
commit 7f92cb0725
8 changed files with 97 additions and 5 deletions

2
Cargo.lock generated
View File

@ -2642,6 +2642,7 @@ dependencies = [
"criterion", "criterion",
"ed25519-dalek", "ed25519-dalek",
"hex-literal", "hex-literal",
"hkdf",
"libsecp256k1", "libsecp256k1",
"log", "log",
"multihash", "multihash",
@ -2928,6 +2929,7 @@ dependencies = [
"quickcheck", "quickcheck",
"quinn", "quinn",
"rand 0.8.5", "rand 0.8.5",
"ring",
"rustls 0.21.7", "rustls 0.21.7",
"socket2 0.5.4", "socket2 0.5.4",
"thiserror", "thiserror",

View File

@ -1,3 +1,10 @@
## 0.2.4 - unreleased
- Implement `Keypair::derive_secret`, to deterministically derive a new secret from the embedded secret key.
See [PR 4554].
[PR 4554]: https://github.com/libp2p/rust-libp2p/pull/4554
## 0.2.3 ## 0.2.3
- Fix [RUSTSEC-2022-0093] by updating `ed25519-dalek` to `2.0`. - Fix [RUSTSEC-2022-0093] by updating `ed25519-dalek` to `2.0`.

View File

@ -15,6 +15,7 @@ categories = ["cryptography"]
asn1_der = { version = "0.7.6", optional = true } asn1_der = { version = "0.7.6", optional = true }
bs58 = { version = "0.5.0", optional = true } bs58 = { version = "0.5.0", optional = true }
ed25519-dalek = { version = "2.0", optional = true, features = ["rand_core"] } ed25519-dalek = { version = "2.0", optional = true, features = ["rand_core"] }
hkdf = { version = "0.12.3", optional = true }
libsecp256k1 = { version = "0.7.0", optional = true } libsecp256k1 = { version = "0.7.0", optional = true }
log = "0.4" log = "0.4"
multihash = { version = "0.19.1", optional = true } multihash = { version = "0.19.1", optional = true }
@ -32,11 +33,11 @@ zeroize = { version = "1.6", optional = true }
ring = { version = "0.16.9", features = ["alloc", "std"], default-features = false, optional = true} ring = { version = "0.16.9", features = ["alloc", "std"], default-features = false, optional = true}
[features] [features]
secp256k1 = [ "dep:libsecp256k1", "dep:asn1_der", "dep:rand", "dep:sha2", "dep:zeroize" ] secp256k1 = [ "dep:libsecp256k1", "dep:asn1_der", "dep:rand", "dep:sha2", "dep:hkdf", "dep:zeroize" ]
ecdsa = [ "dep:p256", "dep:rand", "dep:void", "dep:zeroize", "dep:sec1" ] ecdsa = [ "dep:p256", "dep:rand", "dep:void", "dep:zeroize", "dep:sec1", "dep:sha2", "dep:hkdf" ]
rsa = [ "dep:ring", "dep:asn1_der", "dep:rand", "dep:zeroize" ] rsa = [ "dep:ring", "dep:asn1_der", "dep:rand", "dep:zeroize" ]
ed25519 = [ "dep:ed25519-dalek", "dep:rand", "dep:zeroize" ] ed25519 = [ "dep:ed25519-dalek", "dep:rand", "dep:zeroize", "dep:sha2", "dep:hkdf" ]
peerid = [ "dep:multihash", "dep:bs58", "dep:rand", "dep:thiserror", "dep:sha2" ] peerid = [ "dep:multihash", "dep:bs58", "dep:rand", "dep:thiserror", "dep:sha2", "dep:hkdf" ]
[dev-dependencies] [dev-dependencies]
quickcheck = { workspace = true } quickcheck = { workspace = true }

View File

@ -197,6 +197,10 @@ impl SecretKey {
sk_bytes.zeroize(); sk_bytes.zeroize();
Ok(SecretKey(secret)) Ok(SecretKey(secret))
} }
pub(crate) fn to_bytes(&self) -> [u8; 32] {
self.0
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -342,6 +342,59 @@ impl Keypair {
KeyPairInner::Ecdsa(_) => KeyType::Ecdsa, KeyPairInner::Ecdsa(_) => KeyType::Ecdsa,
} }
} }
/// Deterministically derive a new secret from this [`Keypair`], taking into account the provided domain.
///
/// This works for all key types except RSA where it returns `None`.
///
/// # Example
///
/// ```
/// # fn main() {
/// # use libp2p_identity as identity;
///
/// let key = identity::Keypair::generate_ed25519();
///
/// let new_key = key.derive_secret(b"my encryption key").expect("can derive secret for ed25519");
/// # }
/// ```
#[allow(unused_variables, unreachable_code)]
pub fn derive_secret(&self, domain: &[u8]) -> Option<[u8; 32]> {
#[cfg(any(
feature = "ecdsa",
feature = "secp256k1",
feature = "ed25519",
feature = "rsa"
))]
return Some(
hkdf::Hkdf::<sha2::Sha256>::extract(None, &[domain, &self.secret()?].concat())
.0
.into(),
);
None
}
/// Return the secret key of the [`Keypair`].
#[allow(dead_code)]
pub(crate) fn secret(&self) -> Option<[u8; 32]> {
match self.keypair {
#[cfg(feature = "ed25519")]
KeyPairInner::Ed25519(ref inner) => Some(inner.secret().to_bytes()),
#[cfg(all(feature = "rsa", not(target_arch = "wasm32")))]
KeyPairInner::Rsa(_) => return None,
#[cfg(feature = "secp256k1")]
KeyPairInner::Secp256k1(ref inner) => Some(inner.secret().to_bytes()),
#[cfg(feature = "ecdsa")]
KeyPairInner::Ecdsa(ref inner) => Some(
inner
.secret()
.to_bytes()
.try_into()
.expect("Ecdsa's private key should be 32 bytes"),
),
}
}
} }
#[cfg(feature = "ecdsa")] #[cfg(feature = "ecdsa")]
@ -901,4 +954,11 @@ mod tests {
assert_eq!(converted_pubkey, pubkey); assert_eq!(converted_pubkey, pubkey);
assert_eq!(converted_pubkey.key_type(), KeyType::Ecdsa) assert_eq!(converted_pubkey.key_type(), KeyType::Ecdsa)
} }
#[test]
#[cfg(feature = "ecdsa")]
fn test_secret_from_ecdsa_private_key() {
let keypair = Keypair::generate_ecdsa();
assert!(keypair.derive_secret(b"domain separator!").is_some())
}
} }

View File

@ -1,3 +1,9 @@
## 0.9.3 - unreleased
- Support QUIC stateless resets for supported `libp2p_identity::Keypair`s. See [PR 4554].
[PR 4554]: https://github.com/libp2p/rust-libp2p/pull/4554
## 0.9.2 ## 0.9.2
- Cut stable release. - Cut stable release.

View File

@ -25,6 +25,7 @@ rustls = { version = "0.21.7", default-features = false }
thiserror = "1.0.48" thiserror = "1.0.48"
tokio = { version = "1.32.0", default-features = false, features = ["net", "rt", "time"], optional = true } tokio = { version = "1.32.0", default-features = false, features = ["net", "rt", "time"], optional = true }
socket2 = "0.5.4" socket2 = "0.5.4"
ring = "0.16.20"
[features] [features]
tokio = ["dep:tokio", "if-watch/tokio", "quinn/runtime-tokio"] tokio = ["dep:tokio", "if-watch/tokio", "quinn/runtime-tokio"]

View File

@ -61,6 +61,8 @@ pub struct Config {
client_tls_config: Arc<rustls::ClientConfig>, client_tls_config: Arc<rustls::ClientConfig>,
/// TLS server config for the inner [`quinn::ServerConfig`]. /// TLS server config for the inner [`quinn::ServerConfig`].
server_tls_config: Arc<rustls::ServerConfig>, server_tls_config: Arc<rustls::ServerConfig>,
/// Libp2p identity of the node.
keypair: libp2p_identity::Keypair,
} }
impl Config { impl Config {
@ -80,6 +82,7 @@ impl Config {
// Ensure that one stream is not consuming the whole connection. // Ensure that one stream is not consuming the whole connection.
max_stream_data: 10_000_000, max_stream_data: 10_000_000,
keypair: keypair.clone(),
} }
} }
} }
@ -104,6 +107,7 @@ impl From<Config> for QuinnConfig {
max_stream_data, max_stream_data,
support_draft_29, support_draft_29,
handshake_timeout: _, handshake_timeout: _,
keypair,
} = config; } = config;
let mut transport = quinn::TransportConfig::default(); let mut transport = quinn::TransportConfig::default();
// Disable uni-directional streams. // Disable uni-directional streams.
@ -128,7 +132,14 @@ impl From<Config> for QuinnConfig {
let mut client_config = quinn::ClientConfig::new(client_tls_config); let mut client_config = quinn::ClientConfig::new(client_tls_config);
client_config.transport_config(transport); client_config.transport_config(transport);
let mut endpoint_config = quinn::EndpointConfig::default(); let mut endpoint_config = keypair
.derive_secret(b"libp2p quic stateless reset key")
.map(|secret| {
let reset_key = Arc::new(ring::hmac::Key::new(ring::hmac::HMAC_SHA256, &secret));
quinn::EndpointConfig::new(reset_key)
})
.unwrap_or_default();
if !support_draft_29 { if !support_draft_29 {
endpoint_config.supported_versions(vec![1]); endpoint_config.supported_versions(vec![1]);
} }