Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ohttp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
5 changes: 4 additions & 1 deletion ohttp/src/hpke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,24 @@ 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
}
}

impl Kem {
#[must_use]
pub fn n_enc(self) -> usize {
match self {
Kem::P256Sha256 => 65,
Kem::X25519Sha256 => 32,
}
}

#[must_use]
pub fn n_pk(self) -> usize {
match self {
Kem::P256Sha256 => 65,
Kem::X25519Sha256 => 32,
}
}
Expand Down
26 changes: 26 additions & 0 deletions ohttp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
144 changes: 141 additions & 3 deletions ohttp/src/rh/hpke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -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)
}
}

Expand All @@ -62,13 +63,15 @@ impl Default for Config {
#[derive(Clone)]
pub enum PublicKey {
X25519(<X25519HkdfSha256 as KemTrait>::PublicKey),
P256(<DhP256HkdfSha256 as KemTrait>::PublicKey),
}

impl PublicKey {
#[allow(clippy::unnecessary_wraps)]
pub fn key_data(&self) -> Res<Vec<u8>> {
Ok(match self {
Self::X25519(k) => Vec::from(k.to_bytes().as_slice()),
Self::P256(k) => Vec::from(k.to_bytes().as_slice()),
})
}
}
Expand All @@ -87,13 +90,15 @@ impl std::fmt::Debug for PublicKey {
#[derive(Clone)]
pub enum PrivateKey {
X25519(<X25519HkdfSha256 as KemTrait>::PrivateKey),
P256(<DhP256HkdfSha256 as KemTrait>::PrivateKey),
}

impl PrivateKey {
#[allow(clippy::unnecessary_wraps)]
pub fn key_data(&self) -> Res<Vec<u8>> {
Ok(match self {
Self::X25519(k) => Vec::from(k.to_bytes().as_slice()),
Self::P256(k) => Vec::from(k.to_bytes().as_slice()),
})
}
}
Expand All @@ -120,8 +125,18 @@ enum SenderContextX25519HkdfSha256 {
HkdfSha256(SenderContextX25519HkdfSha256HkdfSha256),
}

enum SenderContextP256HkdfSha256HkdfSha256 {
AesGcm128(Box<AeadCtxS<AesGcm128, HkdfSha256, DhP256HkdfSha256>>),
ChaCha20Poly1305(Box<AeadCtxS<ChaCha20Poly1305, HkdfSha256, DhP256HkdfSha256>>),
}

enum SenderContextP256HkdfSha256 {
HkdfSha256(SenderContextP256HkdfSha256HkdfSha256),
}

enum SenderContext {
X25519HkdfSha256(SenderContextX25519HkdfSha256),
P256HkdfSha256(SenderContextP256HkdfSha256),
}

impl SenderContext {
Expand All @@ -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())
}
})
}

Expand All @@ -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(())
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -288,8 +343,18 @@ enum ReceiverContextX25519HkdfSha256 {
HkdfSha256(ReceiverContextX25519HkdfSha256HkdfSha256),
}

enum ReceiverContextP256HkdfSha256HkdfSha256 {
AesGcm128(Box<AeadCtxR<AesGcm128, HkdfSha256, DhP256HkdfSha256>>),
ChaCha20Poly1305(Box<AeadCtxR<ChaCha20Poly1305, HkdfSha256, DhP256HkdfSha256>>),
}

enum ReceiverContextP256HkdfSha256 {
HkdfSha256(ReceiverContextP256HkdfSha256HkdfSha256),
}

enum ReceiverContext {
X25519HkdfSha256(ReceiverContextX25519HkdfSha256),
P256HkdfSha256(ReceiverContextP256HkdfSha256),
}

impl ReceiverContext {
Expand Down Expand Up @@ -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::<AesGcm128>::size() {
return Err(Error::Truncated);
}
let (ct, tag_slice) =
ciphertext.split_at_mut(ciphertext.len() - AeadTag::<AesGcm128>::size());
let tag = AeadTag::<AesGcm128>::from_bytes(tag_slice)?;
context.open_in_place_detached(ct, aad, &tag)?;
ct
}
Self::P256HkdfSha256(ReceiverContextP256HkdfSha256::HkdfSha256(
ReceiverContextP256HkdfSha256HkdfSha256::ChaCha20Poly1305(context),
)) => {
if ciphertext.len() < AeadTag::<ChaCha20Poly1305>::size() {
return Err(Error::Truncated);
}
let (ct, tag_slice) =
ciphertext.split_at_mut(ciphertext.len() - AeadTag::<ChaCha20Poly1305>::size());
let tag = AeadTag::<ChaCha20Poly1305>::from_bytes(tag_slice)?;
context.open_in_place_detached(ct, aad, &tag)?;
ct
}
})
}

Expand All @@ -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(())
}
Expand Down Expand Up @@ -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 })
Expand All @@ -422,6 +539,9 @@ impl HpkeR {
Kem::X25519Sha256 => {
PublicKey::X25519(<X25519HkdfSha256 as KemTrait>::PublicKey::from_bytes(k)?)
}
Kem::P256Sha256 => {
PublicKey::P256(<DhP256HkdfSha256 as KemTrait>::PublicKey::from_bytes(k)?)
}
})
}
}
Expand Down Expand Up @@ -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))
Expand All @@ -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))
Expand Down Expand Up @@ -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);
}
}
Loading