diff --git a/ohttp/Cargo.toml b/ohttp/Cargo.toml index 5ee23ac..80d8b2b 100644 --- a/ohttp/Cargo.toml +++ b/ohttp/Cargo.toml @@ -32,7 +32,7 @@ chacha20poly1305 = {version = "0.10", optional = true} futures = {version = "0.3", optional = true} hex = "0.4" hkdf = {version = "0.12", optional = true} -hpke = {version = "0.13", optional = true, default-features = false, features = ["std", "x25519"]} +hpke = {version = "0.13", optional = true, default-features = false, features = ["std", "x25519", "p256"]} log = {version = "0.4", default-features = false} pin-project = {version = "1.1", optional = true} rand = {version = "0.9", optional = true} diff --git a/ohttp/src/hpke.rs b/ohttp/src/hpke.rs index 3e5eb79..fe557d3 100644 --- a/ohttp/src/hpke.rs +++ b/ohttp/src/hpke.rs @@ -31,7 +31,8 @@ macro_rules! convert_enum { convert_enum! { pub enum Kem { - X25519Sha256 = 32, + P256Sha256 = 16, // DHKEM(P-256, HKDF-SHA256) = 0x0010 + X25519Sha256 = 32, // DHKEM(X25519, HKDF-SHA256) = 0x0020 } } @@ -39,6 +40,7 @@ impl Kem { #[must_use] pub fn n_enc(self) -> usize { match self { + Kem::P256Sha256 => 65, Kem::X25519Sha256 => 32, } } @@ -46,6 +48,7 @@ impl Kem { #[must_use] pub fn n_pk(self) -> usize { match self { + Kem::P256Sha256 => 65, Kem::X25519Sha256 => 32, } } diff --git a/ohttp/src/lib.rs b/ohttp/src/lib.rs index f0a7ef2..7ff7d96 100644 --- a/ohttp/src/lib.rs +++ b/ohttp/src/lib.rs @@ -382,6 +382,32 @@ mod test { trace!("Response: {}", hex::encode(RESPONSE)); } + #[test] + fn request_response_p256() { + init(); + + // P-256 HPKE may not be supported by all backends (e.g., NSS lacks P-256 HPKE). + if !super::HpkeConfig::new(Kem::P256Sha256, Kdf::HkdfSha256, Aead::Aes128Gcm).supported() { + return; + } + + let server_config = KeyConfig::new(KEY_ID, Kem::P256Sha256, Vec::from(SYMMETRIC)).unwrap(); + let server = Server::new(server_config).unwrap(); + let encoded_config = server.config().encode().unwrap(); + trace!("P256 Config: {}", hex::encode(&encoded_config)); + + let client = ClientRequest::from_encoded_config(&encoded_config).unwrap(); + let (enc_request, client_response) = client.encapsulate(REQUEST).unwrap(); + trace!("P256 Encapsulated Request: {}", hex::encode(&enc_request)); + + let (request, server_response) = server.decapsulate(&enc_request).unwrap(); + assert_eq!(&request[..], REQUEST); + + let enc_response = server_response.encapsulate(RESPONSE).unwrap(); + let response = client_response.decapsulate(&enc_response).unwrap(); + assert_eq!(&response[..], RESPONSE); + } + #[test] fn two_requests() { init(); diff --git a/ohttp/src/rh/hpke.rs b/ohttp/src/rh/hpke.rs index 70346bf..61220d7 100644 --- a/ohttp/src/rh/hpke.rs +++ b/ohttp/src/rh/hpke.rs @@ -6,7 +6,7 @@ use log::trace; use rust_hpke::{ aead::{AeadCtxR, AeadCtxS, AeadTag, AesGcm128, ChaCha20Poly1305}, kdf::HkdfSha256, - kem::{Kem as KemTrait, X25519HkdfSha256}, + kem::{DhP256HkdfSha256, Kem as KemTrait, X25519HkdfSha256}, setup_receiver, setup_sender, Deserializable, OpModeR, OpModeS, Serializable, }; @@ -43,8 +43,9 @@ impl Config { } pub fn supported(self) -> bool { - // TODO support more options - self.kdf == Kdf::HkdfSha256 && matches!(self.aead, Aead::Aes128Gcm | Aead::ChaCha20Poly1305) + matches!(self.kem, Kem::X25519Sha256 | Kem::P256Sha256) + && self.kdf == Kdf::HkdfSha256 + && matches!(self.aead, Aead::Aes128Gcm | Aead::ChaCha20Poly1305) } } @@ -62,6 +63,7 @@ impl Default for Config { #[derive(Clone)] pub enum PublicKey { X25519(::PublicKey), + P256(::PublicKey), } impl PublicKey { @@ -69,6 +71,7 @@ impl PublicKey { pub fn key_data(&self) -> Res> { Ok(match self { Self::X25519(k) => Vec::from(k.to_bytes().as_slice()), + Self::P256(k) => Vec::from(k.to_bytes().as_slice()), }) } } @@ -87,6 +90,7 @@ impl std::fmt::Debug for PublicKey { #[derive(Clone)] pub enum PrivateKey { X25519(::PrivateKey), + P256(::PrivateKey), } impl PrivateKey { @@ -94,6 +98,7 @@ impl PrivateKey { pub fn key_data(&self) -> Res> { Ok(match self { Self::X25519(k) => Vec::from(k.to_bytes().as_slice()), + Self::P256(k) => Vec::from(k.to_bytes().as_slice()), }) } } @@ -120,8 +125,18 @@ enum SenderContextX25519HkdfSha256 { HkdfSha256(SenderContextX25519HkdfSha256HkdfSha256), } +enum SenderContextP256HkdfSha256HkdfSha256 { + AesGcm128(Box>), + ChaCha20Poly1305(Box>), +} + +enum SenderContextP256HkdfSha256 { + HkdfSha256(SenderContextP256HkdfSha256HkdfSha256), +} + enum SenderContext { X25519HkdfSha256(SenderContextX25519HkdfSha256), + P256HkdfSha256(SenderContextP256HkdfSha256), } impl SenderContext { @@ -139,6 +154,18 @@ impl SenderContext { let tag = context.seal_in_place_detached(plaintext, aad)?; Vec::from(tag.to_bytes().as_slice()) } + Self::P256HkdfSha256(SenderContextP256HkdfSha256::HkdfSha256( + SenderContextP256HkdfSha256HkdfSha256::AesGcm128(context), + )) => { + let tag = context.seal_in_place_detached(plaintext, aad)?; + Vec::from(tag.to_bytes().as_slice()) + } + Self::P256HkdfSha256(SenderContextP256HkdfSha256::HkdfSha256( + SenderContextP256HkdfSha256HkdfSha256::ChaCha20Poly1305(context), + )) => { + let tag = context.seal_in_place_detached(plaintext, aad)?; + Vec::from(tag.to_bytes().as_slice()) + } }) } @@ -154,6 +181,16 @@ impl SenderContext { )) => { context.export(info, out_buf)?; } + Self::P256HkdfSha256(SenderContextP256HkdfSha256::HkdfSha256( + SenderContextP256HkdfSha256HkdfSha256::AesGcm128(context), + )) => { + context.export(info, out_buf)?; + } + Self::P256HkdfSha256(SenderContextP256HkdfSha256::HkdfSha256( + SenderContextP256HkdfSha256HkdfSha256::ChaCha20Poly1305(context), + )) => { + context.export(info, out_buf)?; + } } Ok(()) } @@ -231,6 +268,24 @@ impl HpkeS { SenderContextX25519HkdfSha256::HkdfSha256, SenderContextX25519HkdfSha256HkdfSha256::ChaCha20Poly1305, }, + { + Kem::P256Sha256 => DhP256HkdfSha256, + Kdf::HkdfSha256 => HkdfSha256, + Aead::Aes128Gcm => AesGcm128, + PublicKey::P256, + SenderContext::P256HkdfSha256, + SenderContextP256HkdfSha256::HkdfSha256, + SenderContextP256HkdfSha256HkdfSha256::AesGcm128, + }, + { + Kem::P256Sha256 => DhP256HkdfSha256, + Kdf::HkdfSha256 => HkdfSha256, + Aead::ChaCha20Poly1305 => ChaCha20Poly1305, + PublicKey::P256, + SenderContext::P256HkdfSha256, + SenderContextP256HkdfSha256::HkdfSha256, + SenderContextP256HkdfSha256HkdfSha256::ChaCha20Poly1305, + }, ]}; Ok(Self { @@ -288,8 +343,18 @@ enum ReceiverContextX25519HkdfSha256 { HkdfSha256(ReceiverContextX25519HkdfSha256HkdfSha256), } +enum ReceiverContextP256HkdfSha256HkdfSha256 { + AesGcm128(Box>), + ChaCha20Poly1305(Box>), +} + +enum ReceiverContextP256HkdfSha256 { + HkdfSha256(ReceiverContextP256HkdfSha256HkdfSha256), +} + enum ReceiverContext { X25519HkdfSha256(ReceiverContextX25519HkdfSha256), + P256HkdfSha256(ReceiverContextP256HkdfSha256), } impl ReceiverContext { @@ -319,6 +384,30 @@ impl ReceiverContext { context.open_in_place_detached(ct, aad, &tag)?; ct } + Self::P256HkdfSha256(ReceiverContextP256HkdfSha256::HkdfSha256( + ReceiverContextP256HkdfSha256HkdfSha256::AesGcm128(context), + )) => { + if ciphertext.len() < AeadTag::::size() { + return Err(Error::Truncated); + } + let (ct, tag_slice) = + ciphertext.split_at_mut(ciphertext.len() - AeadTag::::size()); + let tag = AeadTag::::from_bytes(tag_slice)?; + context.open_in_place_detached(ct, aad, &tag)?; + ct + } + Self::P256HkdfSha256(ReceiverContextP256HkdfSha256::HkdfSha256( + ReceiverContextP256HkdfSha256HkdfSha256::ChaCha20Poly1305(context), + )) => { + if ciphertext.len() < AeadTag::::size() { + return Err(Error::Truncated); + } + let (ct, tag_slice) = + ciphertext.split_at_mut(ciphertext.len() - AeadTag::::size()); + let tag = AeadTag::::from_bytes(tag_slice)?; + context.open_in_place_detached(ct, aad, &tag)?; + ct + } }) } @@ -334,6 +423,16 @@ impl ReceiverContext { )) => { context.export(info, out_buf)?; } + Self::P256HkdfSha256(ReceiverContextP256HkdfSha256::HkdfSha256( + ReceiverContextP256HkdfSha256HkdfSha256::AesGcm128(context), + )) => { + context.export(info, out_buf)?; + } + Self::P256HkdfSha256(ReceiverContextP256HkdfSha256::HkdfSha256( + ReceiverContextP256HkdfSha256HkdfSha256::ChaCha20Poly1305(context), + )) => { + context.export(info, out_buf)?; + } } Ok(()) } @@ -408,6 +507,24 @@ impl HpkeR { ReceiverContextX25519HkdfSha256::HkdfSha256, ReceiverContextX25519HkdfSha256HkdfSha256::ChaCha20Poly1305, }, + { + Kem::P256Sha256 => DhP256HkdfSha256, + Kdf::HkdfSha256 => HkdfSha256, + Aead::Aes128Gcm => AesGcm128, + PrivateKey::P256, + ReceiverContext::P256HkdfSha256, + ReceiverContextP256HkdfSha256::HkdfSha256, + ReceiverContextP256HkdfSha256HkdfSha256::AesGcm128, + }, + { + Kem::P256Sha256 => DhP256HkdfSha256, + Kdf::HkdfSha256 => HkdfSha256, + Aead::ChaCha20Poly1305 => ChaCha20Poly1305, + PrivateKey::P256, + ReceiverContext::P256HkdfSha256, + ReceiverContextP256HkdfSha256::HkdfSha256, + ReceiverContextP256HkdfSha256HkdfSha256::ChaCha20Poly1305, + }, ]}; Ok(Self { context, config }) @@ -422,6 +539,9 @@ impl HpkeR { Kem::X25519Sha256 => { PublicKey::X25519(::PublicKey::from_bytes(k)?) } + Kem::P256Sha256 => { + PublicKey::P256(::PublicKey::from_bytes(k)?) + } }) } } @@ -463,6 +583,10 @@ pub fn generate_key_pair(kem: Kem) -> Res<(PrivateKey, PublicKey)> { let (sk, pk) = X25519HkdfSha256::gen_keypair(&mut csprng); (PrivateKey::X25519(sk), PublicKey::X25519(pk)) } + Kem::P256Sha256 => { + let (sk, pk) = DhP256HkdfSha256::gen_keypair(&mut csprng); + (PrivateKey::P256(sk), PublicKey::P256(pk)) + } }; trace!("Generated key pair: sk={sk:?} pk={pk:?}"); Ok((sk, pk)) @@ -475,6 +599,10 @@ pub fn derive_key_pair(kem: Kem, ikm: &[u8]) -> Res<(PrivateKey, PublicKey)> { let (sk, pk) = X25519HkdfSha256::derive_keypair(ikm); (PrivateKey::X25519(sk), PublicKey::X25519(pk)) } + Kem::P256Sha256 => { + let (sk, pk) = DhP256HkdfSha256::derive_keypair(ikm); + (PrivateKey::P256(sk), PublicKey::P256(pk)) + } }; trace!("Derived key pair: sk={sk:?} pk={pk:?}"); Ok((sk, pk)) @@ -535,4 +663,14 @@ mod test { fn seal_open_chacha() { seal_open(Aead::ChaCha20Poly1305, Kem::X25519Sha256); } + + #[test] + fn seal_open_gcm_p256() { + seal_open(Aead::Aes128Gcm, Kem::P256Sha256); + } + + #[test] + fn seal_open_chacha_p256() { + seal_open(Aead::ChaCha20Poly1305, Kem::P256Sha256); + } }