diff --git a/README.md b/README.md index 3972f29..5612f02 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# Oblivious HTTP +# Attested Oblivious HTTP This is a rust implementation of [Oblivious -HTTP](https://www.rfc-editor.org/rfc/rfc9458.html) +HTTP](https://www.ietf.org/archive/id/draft-ohai-chunked-ohttp-01.html) and the supporting [Binary HTTP -Messages](https://www.rfc-editor.org/rfc/rfc9292.html). +Messages](https://www.rfc-editor.org/rfc/rfc9292.html) that supports attestation and chunking. The `ohttp` crate uses either [hpke](https://github.com/rozbb/rust-hpke) or [NSS](https://firefox-source-docs.mozilla.org/security/nss/index.html) for @@ -36,8 +36,8 @@ The `ohttp` crate has the following features: - `client` enables the client-side processing of oblivious HTTP messages: encrypting requests and decrypting responses. This is enabled by default. -- `server` enables the server-side processing of oblivious HTTP messages: - decrypting requests and encrypting responses. This is enabled by default. +- `server` enables the server-side processing of chunked oblivious HTTP messages: + decrypting requests and encrypting chunked responses. This is enabled by default. - `rust-hpke` selects the [hpke](https://github.com/rozbb/rust-hpke) crate for HPKE encryption. This is enabled by default and cannot be enabled at the same @@ -68,62 +68,25 @@ cargo run --bin bhttp-convert < ./examples/response.txt | \ ``` Sample client and server implementations can be found in `ohttp-client` and -`ohttp-server` respectively. The server acts as both an Oblivious Gateway -Resource and a Target Resource. You will need to provide your own relay. +`ohttp-server` respectively. The server acts as an Oblivious Gateway +Resource. You will need to provide a Target resource and your own relay. Though a direct request to the server will demonstrate that things are working, the server sees your IP address. +## Development Environment -## Getting and Building With NSS +The repo supports development using GitHub Codespaces and devcontainers. -The build setup is a little tricky, mostly because building NSS is a bit fiddly. +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=707634300&skip_quickstart=true&machine=premiumLinux&geo=EuropeWest) -First, you need a machine capable of [building -NSS](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Building). -For those on Ubuntu/Debian, the minimal set of prerequisites for an x64 build -(and the later steps) can be installed using: +## Build and Test -```sh -sudo apt-get install \ - ca-certificates coreutils curl git make mercurial \ - build-essential clang llvm libclang-dev lld \ - gyp ninja-build pkg-config zlib1g-dev -``` - -You then need to clone this repository, the NSS repository, and the NSPR -repository. I generally put them all in the same place. - -```sh -cd $workspace -git clone https://github.com/martinthomson/ohttp ./ohttp -git clone https://github.com/nss-dev/nss ./nss -# or -# hg clone https://hg.mozilla.org/projects/nss ./nss -hg clone https://hg.mozilla.org/projects/nspr ./nspr -``` - -The build then needs to be told about where to find NSS. The runtime also needs -to be told where to find NSS libraries. This helps avoid linking with any NSS -version you might have installed in the OS, which won't work (yet). - -```sh -export NSS_DIR=$workspace/nss -export LD_LIBRARY_PATH=$workspace/dist/Debug/lib +To build docker images for the server and client, and test with a sample target service, ``` - -You might need to tweak this. On a Mac, use `DYLD_LIBRARY_PATH` instead of -`LD_LIBRARY_PATH`. And if you are building with `--release`, the path includes -"Release" rather than "Debug". - -Then you should be able to build and run tests: - -```sh -cd $workspace -cargo build -cargo test +make build +make run ``` - ## Contributing Contributions are welcome provided you are respectful of others in your @@ -136,6 +99,6 @@ There is a pre-commit script that you can link to `.git/hooks/pre-commit` that runs `cargo fmt` on all commits. Just run `./pre-commit install` to have it install itself. -## Minnimum Supported Rust Version (MSRV) +## Minimum Supported Rust Version (MSRV) -`ohttp` and `bhttp` should compile on Rust 1.63.0. +`ohttp` and `bhttp` should compile on Rust 1.70.0. diff --git a/examples/request.txt b/examples/request.txt index 6f49769..4246312 100644 --- a/examples/request.txt +++ b/examples/request.txt @@ -1,4 +1,4 @@ -GET /hello.txt HTTP/1.1 +GET /stream HTTP/1.1 User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3 Host: www.example.com Accept-Language: en, mi diff --git a/ohttp/Cargo.toml b/ohttp/Cargo.toml index ffaabd5..e6dde35 100644 --- a/ohttp/Cargo.toml +++ b/ohttp/Cargo.toml @@ -26,9 +26,10 @@ aead = {version = "0.4", optional = true, features = ["std"]} aes-gcm = {version = "0.9", optional = true} byteorder = "1.4" chacha20poly1305 = {version = "0.8", optional = true} +colored = "2.0.4" hex = "0.4" hkdf = {version = "0.11", optional = true} -hpke = {version = "0.11.0", optional = true, default-features = false, features = ["std", "x25519"]} +hpke = {version = "0.12.0", optional = true, default-features = false, features = ["std", "x25519", "p384"]} lazy_static = "1.4" log = {version = "0.4", default-features = false} rand = {version = "0.8", optional = true} @@ -39,6 +40,10 @@ regex-automata = {version = "~0.3", optional = true} regex-syntax = {version = "~0.7", optional = true} sha2 = {version = "0.9", optional = true} thiserror = "1" +futures-util = "0.3.30" +futures = "0.3.30" +bytes = "1.7.2" +async-stream = "0.3.5" [dependencies.hpke-pq] package = "hpke_pq" diff --git a/ohttp/src/config.rs b/ohttp/src/config.rs index 9b55755..2c94551 100644 --- a/ohttp/src/config.rs +++ b/ohttp/src/config.rs @@ -78,6 +78,21 @@ impl KeyConfig { }) } + /// Construct a configuration from an existing private key + /// # Panics + /// If the configurations don't include a supported configuration. + pub fn import_p384(key_id: u8, kem: Kem, sk: ::PrivateKey, pk: ::PublicKey, mut symmetric: Vec) -> Res { + Self::strip_unsupported(&mut symmetric, kem); + assert!(!symmetric.is_empty()); + Ok(Self { + key_id, + kem, + symmetric, + sk: Some(crate::rh::hpke::PrivateKey::P384(sk)), + pk: crate::rh::hpke::PublicKey::P384(pk), + }) + } + /// Derive a configuration for the server side from input keying material, /// using the `DeriveKeyPair` functionality of the HPKE KEM defined here: /// diff --git a/ohttp/src/hpke.rs b/ohttp/src/hpke.rs index 6865777..d832820 100644 --- a/ohttp/src/hpke.rs +++ b/ohttp/src/hpke.rs @@ -31,6 +31,8 @@ macro_rules! convert_enum { convert_enum! { pub enum Kem { + P384Sha384 = 17, + X25519Sha256 = 32, #[cfg(feature = "pq")] @@ -42,6 +44,8 @@ impl Kem { #[must_use] pub fn n_enc(self) -> usize { match self { + Kem::P384Sha384 => 97, + Kem::X25519Sha256 => 32, #[cfg(feature = "pq")] @@ -52,6 +56,8 @@ impl Kem { #[must_use] pub fn n_pk(self) -> usize { match self { + Kem::P384Sha384 => 97, + Kem::X25519Sha256 => 32, #[cfg(feature = "pq")] diff --git a/ohttp/src/lib.rs b/ohttp/src/lib.rs index 38e3666..5b36f2c 100644 --- a/ohttp/src/lib.rs +++ b/ohttp/src/lib.rs @@ -1,4 +1,4 @@ -#![deny(warnings, clippy::pedantic)] +#![deny(clippy::pedantic)] #![allow(clippy::missing_errors_doc)] // I'm too lazy #![cfg_attr( not(all(feature = "client", feature = "server")), @@ -15,6 +15,10 @@ mod rand; #[cfg(feature = "rust-hpke")] mod rh; +use async_stream::stream; +use futures::{stream::Stream, StreamExt}; +use futures_util::stream::once; + pub use crate::{ config::{KeyConfig, SymmetricSuite}, err::Error, @@ -25,7 +29,7 @@ use crate::{ hpke::{Aead as AeadId, Kdf, Kem}, }; use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; -use log::trace; +use log::{info, trace}; use std::{ cmp::max, convert::TryFrom, @@ -259,6 +263,23 @@ impl ServerResponse { }) } + // Variable length encoding of an integer + fn variant_encode(&mut self, mut val: usize) -> Vec { + let mut bytes = Vec::new(); + loop { + let mut byte = (val & 0x7F) as u8; // Take the last 7 bits + val >>= 7; // Shift right by 7 bits + if val != 0 { + byte |= 0x80; // Set the MSB if there's more to encode + } + bytes.push(byte); + if val == 0 { + break; + } + } + bytes + } + /// Consume this object by encapsulating a response. pub fn encapsulate(mut self, response: &[u8]) -> Res> { let mut enc_response = self.response_nonce; @@ -266,6 +287,84 @@ impl ServerResponse { enc_response.append(&mut ct); Ok(enc_response) } + + // Consume this object by encapsulating a stream + // https://www.ietf.org/archive/id/draft-ohai-chunked-ohttp-01.html#name-response-format + // Chunked Encapsulated Response { + // Response Nonce (Nk), + // Chunked Response Chunks (..), + // } + + // Chunked Response Chunks { + // Non-Final Response Chunk (..), + // Final Response Chunk Indicator (i) = 0, + // AEAD-Protected Final Response Chunk (..), + // } + + // Non-Final Response Chunk { + // Length (i) = 1.., + // AEAD-Protected Chunk (..), + // } + pub fn encapsulate_stream( + mut self, + input: S, + ) -> std::pin::Pin>> + Send + 'static>> + where + S: Stream, E>> + Send + 'static, + E: std::fmt::Debug + Send, + { + // Response Nonce (Nk) + let response_nonce = Ok(self.response_nonce.clone()); + info!("Response nonce {}", hex::encode(&self.response_nonce.clone())); + let nonce_stream = once(async { response_nonce }); + + let mut input = Box::pin(input); + let output_stream = stream! { + let current = input.next().await; + let Some(current) = current else { return }; + let Ok(mut current) = current else { return }; + + loop { + info!("Processing chunk {}", std::str::from_utf8(¤t).unwrap()); + if let Some(next) = input.next().await { + let mut enc_response = Vec::new(); + + // Non-Final Response Chunk (..), + let aad = ""; + let mut ct = self.aead.seal(aad.as_bytes(), ¤t).unwrap(); + let mut enc_length = self.variant_encode(ct.len()); + // Length (i) = 1.., + enc_response.append(&mut enc_length); + + // AEAD-Protected Chunk (..), + enc_response.append(&mut ct); + + info!("Encapsulated chunk {}", hex::encode(&enc_response)); + yield Ok(enc_response); + current = next.unwrap(); + } else { + let mut enc_response = Vec::new(); + + // Final Response Chunk Indicator (i) = 0, + let mut final_chunk_indicator = self.variant_encode(0); + enc_response.append(&mut final_chunk_indicator); + + // AEAD-Protected Final Response Chunk (..), + let aad = "final"; + let mut ct = self.aead.seal(aad.as_bytes(), ¤t).unwrap(); + let mut enc_length = self.variant_encode(ct.len()); + enc_response.append(&mut enc_length); + enc_response.append(&mut ct); + info!("Encapsulated final chunk {}", hex::encode(&enc_response)); + yield Ok(enc_response); + return; + } + } + }; + + let stream = nonce_stream.chain(output_stream); + Box::pin(stream) + } } #[cfg(feature = "server")] @@ -281,6 +380,8 @@ impl std::fmt::Debug for ServerResponse { pub struct ClientResponse { hpke: HpkeS, enc: Vec, + seq: u64, + aead: Option, } #[cfg(feature = "client")] @@ -289,7 +390,14 @@ impl ClientResponse { /// Doesn't do anything because we don't have the nonce yet, so /// the work that can be done is limited. fn new(hpke: HpkeS, enc: Vec) -> Self { - Self { hpke, enc } + let seq = 0; + let aead = None; + Self { + hpke, + enc, + seq, + aead, + } } /// Consume this object by decapsulating a response. @@ -308,6 +416,97 @@ impl ClientResponse { )?; aead.open(&[], 0, ct) // 0 is the sequence number } + + fn set_response_nonce(&mut self, enc_response: &[u8]) -> Res<()> { + let mid = entropy(self.hpke.config()); + if mid != enc_response.len() { + return Err(Error::Truncated); + } + let aead = make_aead( + Mode::Decrypt, + self.hpke.config(), + &self.hpke, + self.enc.clone(), + enc_response, + )?; + self.aead = Some(aead); + Ok(()) + } + + fn variant_decode(&mut self, bytes: &[u8]) -> Result<(u64, usize), String> { + let mut value: u64 = 0; + let mut shift = 0; + let mut bytes_read = 0; + + for &byte in bytes { + let byte_value = (byte & 0x7F) as u64; + value |= byte_value << shift; + bytes_read += 1; + if byte & 0x80 == 0 { + // Continuation bit is not set, end of the VLQ-encoded integer + return Ok((value, bytes_read)); + } + shift += 7; + if shift >= 64 { + return Err("VLQ-encoded integer is too large".to_string()); + } + } + Err("Incomplete VLQ-encoded integer".to_string()) + } + + pub async fn decapsulate_stream( + mut self, + mut stream: S, + ) -> std::pin::Pin>> + Send + 'static>> + where + S: Stream>> + Send + 'static + Unpin, + { + // Response Nonce (Nk) + if let Some(nonce) = stream.next().await { + let enc_response = nonce.unwrap(); + info!("Setting response nonce: {}({})", hex::encode(&enc_response), enc_response.len()); + self.set_response_nonce(&enc_response).unwrap(); + } + + let output_stream = stream! { + while let Some(next) = stream.next().await { + let mut enc_response = next.unwrap(); + if enc_response.is_empty() { return }; + info!("Decrypting chunk: {}({})", hex::encode(&enc_response), enc_response.len()); + let (len, bytes_read) = self.variant_decode(&enc_response).unwrap(); + info!("Chunk length: {}, bytes read {}", len, bytes_read); + if len != 0 { + let aad = ""; + let (_, ct) = enc_response.split_at(bytes_read); + let mut current = ct.to_vec(); + while (current.len() as u64) < len { + enc_response = stream.next().await.unwrap().unwrap(); + info!("Appending chunk: {}({})", hex::encode(&enc_response), enc_response.len()); + current.append(&mut enc_response); + } + self.seq += 1; + yield self.aead.as_mut().unwrap().open(aad.as_bytes(), self.seq - 1, ¤t); + } else { + let (_, rest) = enc_response.split_at(bytes_read); + let (_, bytes_read) = self.variant_decode(&rest).unwrap(); + let (_, ct) = rest.split_at(bytes_read); + // Read to the end + let mut current = ct.to_vec(); + while let Some(next) = stream.next().await { + enc_response = next.unwrap(); + info!("Appending chunk: {}({})", hex::encode(&enc_response), enc_response.len()); + current.append(&mut enc_response); + } + let aad = "final"; + self.seq += 1; + yield self.aead.as_mut().unwrap().open(aad.as_bytes(), self.seq - 1, ¤t); + return; + } + } + }; + + Box::pin(output_stream) + } } #[cfg(all(test, feature = "client", feature = "server"))] diff --git a/ohttp/src/rh/hpke.rs b/ohttp/src/rh/hpke.rs index 4b81152..0182c75 100644 --- a/ohttp/src/rh/hpke.rs +++ b/ohttp/src/rh/hpke.rs @@ -11,9 +11,9 @@ use ::hpke as rust_hpke; use ::hpke_pq as rust_hpke; use rust_hpke::{ - aead::{AeadCtxR, AeadCtxS, AeadTag, AesGcm128, ChaCha20Poly1305}, - kdf::HkdfSha256, - kem::{Kem as KemTrait, X25519HkdfSha256}, + aead::{AeadCtxR, AeadCtxS, AeadTag, AesGcm128, AesGcm256, ChaCha20Poly1305}, + kdf::{HkdfSha256, HkdfSha384}, + kem::{Kem as KemTrait, X25519HkdfSha256, DhP384HkdfSha384}, setup_receiver, setup_sender, Deserializable, OpModeR, OpModeS, Serializable, }; @@ -51,7 +51,7 @@ impl Config { pub fn supported(self) -> bool { // TODO support more options - self.kdf == Kdf::HkdfSha256 && matches!(self.aead, Aead::Aes128Gcm | Aead::ChaCha20Poly1305) + matches!(self.kdf, Kdf::HkdfSha256 | Kdf::HkdfSha384) && matches!(self.aead, Aead::Aes128Gcm | Aead::Aes256Gcm | Aead::ChaCha20Poly1305) } } @@ -70,6 +70,8 @@ impl Default for Config { pub enum PublicKey { X25519(::PublicKey), + P384(::PublicKey), + #[cfg(feature = "pq")] X25519Kyber768Draft00(::PublicKey), } @@ -78,6 +80,8 @@ impl PublicKey { #[allow(clippy::unnecessary_wraps)] pub fn key_data(&self) -> Res> { Ok(match self { + Self::P384(k) => Vec::from(k.to_bytes().as_slice()), + Self::X25519(k) => Vec::from(k.to_bytes().as_slice()), #[cfg(feature = "pq")] @@ -99,8 +103,8 @@ impl std::fmt::Debug for PublicKey { #[allow(clippy::large_enum_variant)] #[derive(Clone)] pub enum PrivateKey { + P384(::PrivateKey), X25519(::PrivateKey), - #[cfg(feature = "pq")] X25519Kyber768Draft00(::PrivateKey), } @@ -109,6 +113,7 @@ impl PrivateKey { #[allow(clippy::unnecessary_wraps)] pub fn key_data(&self) -> Res> { Ok(match self { + Self::P384(k) => Vec::from(k.to_bytes().as_slice()), Self::X25519(k) => Vec::from(k.to_bytes().as_slice()), #[cfg(feature = "pq")] @@ -134,6 +139,11 @@ enum SenderContextX25519HkdfSha256HkdfSha256 { ChaCha20Poly1305(Box>), } +enum SenderContextDhP384HkdfSha384HkdfSha384 { + AesGcm128(Box>), + AesGcm256(Box>), +} + #[cfg(feature = "pq")] enum SenderContextX25519Kyber768Draft00HkdfSha256 { AesGcm128(Box>), @@ -143,6 +153,10 @@ enum SenderContextX25519HkdfSha256 { HkdfSha256(SenderContextX25519HkdfSha256HkdfSha256), } +enum SenderContextDhP384HkdfSha384 { + HkdfSha384(SenderContextDhP384HkdfSha384HkdfSha384), +} + #[cfg(feature = "pq")] enum SenderContextX25519Kyber768Draft00 { HkdfSha256(SenderContextX25519Kyber768Draft00HkdfSha256), @@ -151,6 +165,8 @@ enum SenderContextX25519Kyber768Draft00 { enum SenderContext { X25519HkdfSha256(SenderContextX25519HkdfSha256), + DhP384HkdfSha384(SenderContextDhP384HkdfSha384), + #[cfg(feature = "pq")] X25519Kyber768Draft00(SenderContextX25519Kyber768Draft00), } @@ -170,7 +186,18 @@ impl SenderContext { let tag = context.seal_in_place_detached(plaintext, aad)?; Vec::from(tag.to_bytes().as_slice()) } - + Self::DhP384HkdfSha384(SenderContextDhP384HkdfSha384::HkdfSha384( + SenderContextDhP384HkdfSha384HkdfSha384::AesGcm128(context), + )) => { + let tag = context.seal_in_place_detached(plaintext, aad)?; + Vec::from(tag.to_bytes().as_slice()) + } + Self::DhP384HkdfSha384(SenderContextDhP384HkdfSha384::HkdfSha384( + SenderContextDhP384HkdfSha384HkdfSha384::AesGcm256(context), + )) => { + let tag = context.seal_in_place_detached(plaintext, aad)?; + Vec::from(tag.to_bytes().as_slice()) + } #[cfg(feature = "pq")] Self::X25519Kyber768Draft00(SenderContextX25519Kyber768Draft00::HkdfSha256( SenderContextX25519Kyber768Draft00HkdfSha256::AesGcm128(context), @@ -193,7 +220,16 @@ impl SenderContext { )) => { context.export(info, out_buf)?; } - + Self::DhP384HkdfSha384(SenderContextDhP384HkdfSha384::HkdfSha384( + SenderContextDhP384HkdfSha384HkdfSha384::AesGcm128(context), + )) => { + context.export(info, out_buf)?; + } + Self::DhP384HkdfSha384(SenderContextDhP384HkdfSha384::HkdfSha384( + SenderContextDhP384HkdfSha384HkdfSha384::AesGcm256(context), + )) => { + context.export(info, out_buf)?; + } #[cfg(feature = "pq")] Self::X25519Kyber768Draft00(SenderContextX25519Kyber768Draft00::HkdfSha256( SenderContextX25519Kyber768Draft00HkdfSha256::AesGcm128(context), @@ -277,7 +313,24 @@ impl HpkeS { SenderContextX25519HkdfSha256::HkdfSha256, SenderContextX25519HkdfSha256HkdfSha256::ChaCha20Poly1305, }, - + { + Kem::P384Sha384 => DhP384HkdfSha384, + Kdf::HkdfSha384 => HkdfSha384, + Aead::Aes128Gcm => AesGcm128, + PublicKey::P384, + SenderContext::DhP384HkdfSha384, + SenderContextDhP384HkdfSha384::HkdfSha384, + SenderContextDhP384HkdfSha384HkdfSha384::AesGcm128, + }, + { + Kem::P384Sha384 => DhP384HkdfSha384, + Kdf::HkdfSha384 => HkdfSha384, + Aead::Aes256Gcm => AesGcm256, + PublicKey::P384, + SenderContext::DhP384HkdfSha384, + SenderContextDhP384HkdfSha384::HkdfSha384, + SenderContextDhP384HkdfSha384HkdfSha384::AesGcm256, + }, #[cfg(feature = "pq")] { Kem::X25519Kyber768Draft00 => X25519Kyber768Draft00, @@ -335,6 +388,11 @@ enum ReceiverContextX25519HkdfSha256HkdfSha256 { ChaCha20Poly1305(Box>), } +enum ReceiverContextDhP384HkdfSha384HkdfSha384 { + AesGcm128(Box>), + AesGcm256(Box>), +} + #[cfg(feature = "pq")] enum ReceiverContextX25519Kyber768Draft00HkdfSha256 { AesGcm128(Box>), @@ -344,6 +402,10 @@ enum ReceiverContextX25519HkdfSha256 { HkdfSha256(ReceiverContextX25519HkdfSha256HkdfSha256), } +enum ReceiverContextDhP384HkdfSha384 { + HkdfSha384(ReceiverContextDhP384HkdfSha384HkdfSha384), +} + #[cfg(feature = "pq")] enum ReceiverContextX25519Kyber768Draft00 { HkdfSha256(ReceiverContextX25519Kyber768Draft00HkdfSha256), @@ -351,6 +413,7 @@ enum ReceiverContextX25519Kyber768Draft00 { enum ReceiverContext { X25519HkdfSha256(ReceiverContextX25519HkdfSha256), + DhP384HkdfSha384(ReceiverContextDhP384HkdfSha384), #[cfg(feature = "pq")] X25519Kyber768Draft00(ReceiverContextX25519Kyber768Draft00), @@ -383,7 +446,30 @@ impl ReceiverContext { context.open_in_place_detached(ct, aad, &tag)?; ct } - + Self::DhP384HkdfSha384(ReceiverContextDhP384HkdfSha384::HkdfSha384( + ReceiverContextDhP384HkdfSha384HkdfSha384::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::DhP384HkdfSha384(ReceiverContextDhP384HkdfSha384::HkdfSha384( + ReceiverContextDhP384HkdfSha384HkdfSha384::AesGcm256(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 + } #[cfg(feature = "pq")] Self::X25519Kyber768Draft00(ReceiverContextX25519Kyber768Draft00::HkdfSha256( ReceiverContextX25519Kyber768Draft00HkdfSha256::AesGcm128(context), @@ -412,6 +498,16 @@ impl ReceiverContext { )) => { context.export(info, out_buf)?; } + Self::DhP384HkdfSha384(ReceiverContextDhP384HkdfSha384::HkdfSha384( + ReceiverContextDhP384HkdfSha384HkdfSha384::AesGcm128(context), + )) => { + context.export(info, out_buf)?; + } + Self::DhP384HkdfSha384(ReceiverContextDhP384HkdfSha384::HkdfSha384( + ReceiverContextDhP384HkdfSha384HkdfSha384::AesGcm256(context), + )) => { + context.export(info, out_buf)?; + } #[cfg(feature = "pq")] Self::X25519Kyber768Draft00(ReceiverContextX25519Kyber768Draft00::HkdfSha256( @@ -493,6 +589,24 @@ impl HpkeR { ReceiverContextX25519HkdfSha256::HkdfSha256, ReceiverContextX25519HkdfSha256HkdfSha256::ChaCha20Poly1305, }, + { + Kem::P384Sha384 => DhP384HkdfSha384, + Kdf::HkdfSha384 => HkdfSha384, + Aead::Aes128Gcm => AesGcm128, + PrivateKey::P384, + ReceiverContext::DhP384HkdfSha384, + ReceiverContextDhP384HkdfSha384::HkdfSha384, + ReceiverContextDhP384HkdfSha384HkdfSha384::AesGcm128, + }, + { + Kem::P384Sha384 => DhP384HkdfSha384, + Kdf::HkdfSha384 => HkdfSha384, + Aead::Aes256Gcm => AesGcm256, + PrivateKey::P384, + ReceiverContext::DhP384HkdfSha384, + ReceiverContextDhP384HkdfSha384::HkdfSha384, + ReceiverContextDhP384HkdfSha384HkdfSha384::AesGcm256, + }, #[cfg(feature = "pq")] { @@ -515,6 +629,10 @@ impl HpkeR { pub fn decode_public_key(kem: Kem, k: &[u8]) -> Res { Ok(match kem { + Kem::P384Sha384 => { + PublicKey::P384(::PublicKey::from_bytes(k)?) + } + Kem::X25519Sha256 => { PublicKey::X25519(::PublicKey::from_bytes(k)?) } @@ -554,6 +672,11 @@ impl Deref for HpkeR { pub fn generate_key_pair(kem: Kem) -> Res<(PrivateKey, PublicKey)> { let mut csprng = thread_rng(); let (sk, pk) = match kem { + Kem::P384Sha384 => { + let (sk, pk) = DhP384HkdfSha384::gen_keypair(&mut csprng); + (PrivateKey::P384(sk), PublicKey::P384(pk)) + } + Kem::X25519Sha256 => { let (sk, pk) = X25519HkdfSha256::gen_keypair(&mut csprng); (PrivateKey::X25519(sk), PublicKey::X25519(pk)) @@ -575,6 +698,11 @@ pub fn generate_key_pair(kem: Kem) -> Res<(PrivateKey, PublicKey)> { #[allow(clippy::unnecessary_wraps)] pub fn derive_key_pair(kem: Kem, ikm: &[u8]) -> Res<(PrivateKey, PublicKey)> { let (sk, pk) = match kem { + Kem::P384Sha384 => { + let (sk, pk) = DhP384HkdfSha384::derive_keypair(ikm); + (PrivateKey::P384(sk), PublicKey::P384(pk)) + } + Kem::X25519Sha256 => { let (sk, pk) = X25519HkdfSha256::derive_keypair(ikm); (PrivateKey::X25519(sk), PublicKey::X25519(pk))