Skip to main content

ohttp/
lib.rs

1#![deny(warnings, clippy::pedantic)]
2#![allow(clippy::missing_errors_doc)] // I'm too lazy
3#![cfg_attr(
4    not(all(feature = "client", feature = "server")),
5    allow(dead_code, unused_imports)
6)]
7#[cfg(all(feature = "nss", feature = "rust-hpke"))]
8compile_error!("features \"nss\" and \"rust-hpke\" are mutually incompatible");
9
10mod config;
11mod crypto;
12mod err;
13pub mod hpke;
14#[cfg(feature = "nss")]
15mod nss;
16#[cfg(feature = "rust-hpke")]
17mod rand;
18#[cfg(feature = "rust-hpke")]
19mod rh;
20#[cfg(feature = "stream")]
21mod stream;
22
23use std::{
24    cmp::max,
25    convert::TryFrom,
26    io::{Cursor, Read},
27    mem::size_of,
28};
29
30use byteorder::{NetworkEndian, WriteBytesExt};
31use crypto::{Decrypt, Encrypt};
32use log::trace;
33
34#[cfg(feature = "nss")]
35use crate::nss::{
36    PublicKey, SymKey,
37    aead::{Aead, Mode, NONCE_LEN},
38    hkdf::{Hkdf, KeyMechanism},
39    hpke::{Config as HpkeConfig, Exporter, HpkeR, HpkeS},
40    random,
41};
42#[cfg(feature = "stream")]
43use crate::stream::{ClientRequest as StreamClient, ServerRequest as ServerRequestStream};
44pub use crate::{
45    config::{KeyConfig, SymmetricSuite},
46    err::Error,
47};
48use crate::{err::Res, hpke::Aead as AeadId};
49#[cfg(feature = "rust-hpke")]
50use crate::{
51    rand::random,
52    rh::{
53        SymKey,
54        aead::{Aead, Mode, NONCE_LEN},
55        hkdf::{Hkdf, KeyMechanism},
56        hpke::{Config as HpkeConfig, Exporter, HpkeR, HpkeS, PublicKey},
57    },
58};
59
60/// The request header is a `KeyId` and 2 each for KEM, KDF, and AEAD identifiers
61const REQUEST_HEADER_LEN: usize = size_of::<KeyId>() + 6;
62const INFO_REQUEST: &[u8] = b"message/bhttp request";
63const LABEL_RESPONSE: &[u8] = b"message/bhttp response";
64const INFO_KEY: &[u8] = b"key";
65const INFO_NONCE: &[u8] = b"nonce";
66
67/// The type of a key identifier.
68pub type KeyId = u8;
69
70pub fn init() {
71    #[cfg(feature = "nss")]
72    nss::init();
73}
74
75/// Construct the info parameter we use to initialize an `HpkeS` instance.
76fn build_info(label: &[u8], key_id: KeyId, config: HpkeConfig) -> Res<Vec<u8>> {
77    let mut info = Vec::with_capacity(label.len() + 1 + REQUEST_HEADER_LEN);
78    info.extend_from_slice(label);
79    info.push(0);
80    info.write_u8(key_id)?;
81    info.write_u16::<NetworkEndian>(u16::from(config.kem()))?;
82    info.write_u16::<NetworkEndian>(u16::from(config.kdf()))?;
83    info.write_u16::<NetworkEndian>(u16::from(config.aead()))?;
84    trace!("HPKE info: {}", hex::encode(&info));
85    Ok(info)
86}
87
88/// This is the sort of information we expect to receive from the receiver.
89/// This might not be necessary if we agree on a format.
90#[cfg(feature = "client")]
91pub struct ClientRequest {
92    key_id: KeyId,
93    config: HpkeConfig,
94    pk: PublicKey,
95}
96
97#[cfg(feature = "client")]
98impl ClientRequest {
99    /// Construct a `ClientRequest` from a specific `KeyConfig` instance.
100    pub fn from_config(config: &mut KeyConfig) -> Res<Self> {
101        // TODO(mt) choose the best config, not just the first.
102        let selected = config.select(config.symmetric[0])?;
103        Ok(Self {
104            key_id: config.key_id,
105            config: selected,
106            pk: config.pk.clone(),
107        })
108    }
109
110    /// Reads an encoded configuration and constructs a single use client sender.
111    /// See `KeyConfig::decode` for the structure details.
112    pub fn from_encoded_config(encoded_config: &[u8]) -> Res<Self> {
113        let mut config = KeyConfig::decode(encoded_config)?;
114        Self::from_config(&mut config)
115    }
116
117    /// Reads an encoded list of configurations and constructs a single use client sender
118    /// from the first supported configuration.
119    /// See `KeyConfig::decode_list` for the structure details.
120    pub fn from_encoded_config_list(encoded_config_list: &[u8]) -> Res<Self> {
121        let mut configs = KeyConfig::decode_list(encoded_config_list)?;
122        if let Some(mut config) = configs.pop() {
123            Self::from_config(&mut config)
124        } else {
125            Err(Error::Unsupported)
126        }
127    }
128
129    /// Encapsulate a request.  This consumes this object.
130    /// This produces a response handler and the bytes of an encapsulated request.
131    pub fn encapsulate(self, request: &[u8]) -> Res<(Vec<u8>, ClientResponse)> {
132        // Build the info, which contains the message header.
133        let info = build_info(INFO_REQUEST, self.key_id, self.config)?;
134        let mut hpke = HpkeS::new(self.config, &self.pk, &info)?;
135
136        let header = Vec::from(&info[INFO_REQUEST.len() + 1..]);
137        debug_assert_eq!(header.len(), REQUEST_HEADER_LEN);
138
139        let extra = hpke.config().kem().n_enc() + hpke.config().aead().n_t() + request.len();
140        let expected_len = header.len() + extra;
141
142        let mut enc_request = header;
143        enc_request.reserve_exact(extra);
144
145        let enc = hpke.enc()?;
146        enc_request.extend_from_slice(&enc);
147
148        let mut ct = hpke.seal(&[], request)?;
149        enc_request.append(&mut ct);
150
151        debug_assert_eq!(expected_len, enc_request.len());
152        Ok((enc_request, ClientResponse::new(hpke, enc)))
153    }
154
155    #[cfg(feature = "stream")]
156    pub fn encapsulate_stream<S>(self, dst: S) -> Res<StreamClient<S>> {
157        StreamClient::start(dst, self.config, self.key_id, &self.pk)
158    }
159}
160
161/// A server can handle multiple requests.
162/// It holds a single key pair and can generate a configuration.
163/// (A more complex server would have multiple key pairs. This is simple.)
164#[cfg(feature = "server")]
165#[derive(Debug, Clone)]
166pub struct Server {
167    config: KeyConfig,
168}
169
170#[cfg(feature = "server")]
171impl Server {
172    /// Create a new server configuration.
173    /// # Panics
174    /// If the configuration doesn't include a private key.
175    pub fn new(config: KeyConfig) -> Res<Self> {
176        assert!(config.sk.is_some());
177        Ok(Self { config })
178    }
179
180    /// Get the configuration that this server uses.
181    #[must_use]
182    pub fn config(&self) -> &KeyConfig {
183        &self.config
184    }
185
186    fn decode_request_header(&self, r: &mut Cursor<&[u8]>, label: &[u8]) -> Res<(HpkeR, Vec<u8>)> {
187        let hpke_config = self.config.decode_hpke_config(r)?;
188        let sym = SymmetricSuite::new(hpke_config.kdf(), hpke_config.aead());
189        let config = self.config.select(sym)?;
190        let info = build_info(label, self.config.key_id, hpke_config)?;
191
192        let mut enc = vec![0; config.kem().n_enc()];
193        r.read_exact(&mut enc)?;
194
195        Ok((
196            HpkeR::new(
197                config,
198                &self.config.pk,
199                self.config.sk.as_ref().unwrap(),
200                &enc,
201                &info,
202            )?,
203            enc,
204        ))
205    }
206
207    /// Remove encapsulation on a request.
208    /// # Panics
209    /// Not as a consequence of this code, but Rust won't know that for sure.
210    pub fn decapsulate(&self, enc_request: &[u8]) -> Res<(Vec<u8>, ServerResponse)> {
211        if enc_request.len() <= REQUEST_HEADER_LEN {
212            return Err(Error::Truncated);
213        }
214        let mut r = Cursor::new(enc_request);
215        let (mut hpke, enc) = self.decode_request_header(&mut r, INFO_REQUEST)?;
216
217        let request = hpke.open(&[], &enc_request[usize::try_from(r.position())?..])?;
218        Ok((request, ServerResponse::new(&hpke, &enc)?))
219    }
220
221    /// Remove encapsulation on a streamed request.
222    #[cfg(feature = "stream")]
223    pub fn decapsulate_stream<S>(self, src: S) -> ServerRequestStream<S> {
224        ServerRequestStream::new(self.config, src)
225    }
226}
227
228fn entropy(config: HpkeConfig) -> usize {
229    max(config.aead().n_n(), config.aead().n_k())
230}
231
232fn export_secret<E: Exporter>(exp: &E, label: &[u8], cfg: HpkeConfig) -> Res<SymKey> {
233    exp.export(label, entropy(cfg))
234}
235
236fn make_aead(mode: Mode, cfg: HpkeConfig, secret: &SymKey, enc: &[u8], nonce: &[u8]) -> Res<Aead> {
237    let mut salt = enc.to_vec();
238    salt.extend_from_slice(nonce);
239
240    let hkdf = Hkdf::new(cfg.kdf());
241    let prk = hkdf.extract(&salt, secret)?;
242
243    let key = hkdf.expand_key(&prk, INFO_KEY, KeyMechanism::Aead(cfg.aead()))?;
244    let iv = hkdf.expand_data(&prk, INFO_NONCE, cfg.aead().n_n())?;
245    let nonce_base = <[u8; NONCE_LEN]>::try_from(iv).unwrap();
246
247    Aead::new(mode, cfg.aead(), &key, nonce_base)
248}
249
250/// An object for encapsulating responses.
251/// The only way to obtain one of these is through `Server::decapsulate()`.
252#[cfg(feature = "server")]
253pub struct ServerResponse {
254    response_nonce: Vec<u8>,
255    aead: Aead,
256}
257
258#[cfg(feature = "server")]
259impl ServerResponse {
260    fn new(hpke: &HpkeR, enc: &[u8]) -> Res<Self> {
261        let response_nonce = random(entropy(hpke.config()));
262        let aead = make_aead(
263            Mode::Encrypt,
264            hpke.config(),
265            &export_secret(hpke, LABEL_RESPONSE, hpke.config())?,
266            enc,
267            &response_nonce,
268        )?;
269        Ok(Self {
270            response_nonce,
271            aead,
272        })
273    }
274
275    /// Consume this object by encapsulating a response.
276    pub fn encapsulate(mut self, response: &[u8]) -> Res<Vec<u8>> {
277        let mut enc_response = self.response_nonce;
278        let mut ct = self.aead.seal(&[], response)?;
279        enc_response.append(&mut ct);
280        Ok(enc_response)
281    }
282}
283
284#[cfg(feature = "server")]
285impl std::fmt::Debug for ServerResponse {
286    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287        f.write_str("ServerResponse")
288    }
289}
290
291/// An object for decapsulating responses.
292/// The only way to obtain one of these is through `ClientRequest::encapsulate()`.
293#[cfg(feature = "client")]
294pub struct ClientResponse {
295    hpke: HpkeS,
296    enc: Vec<u8>,
297}
298
299#[cfg(feature = "client")]
300impl ClientResponse {
301    /// Private method for constructing one of these.
302    /// Doesn't do anything because we don't have the nonce yet, so
303    /// the work that can be done is limited.
304    fn new(hpke: HpkeS, enc: Vec<u8>) -> Self {
305        Self { hpke, enc }
306    }
307
308    /// Consume this object by decapsulating a response.
309    pub fn decapsulate(self, enc_response: &[u8]) -> Res<Vec<u8>> {
310        let mid = entropy(self.hpke.config());
311        if mid >= enc_response.len() {
312            return Err(Error::Truncated);
313        }
314        let (response_nonce, ct) = enc_response.split_at(mid);
315        let mut aead = make_aead(
316            Mode::Decrypt,
317            self.hpke.config(),
318            &export_secret(&self.hpke, LABEL_RESPONSE, self.hpke.config())?,
319            &self.enc,
320            response_nonce,
321        )?;
322        aead.open(&[], ct) // 0 is the sequence number
323    }
324}
325
326#[cfg(all(test, feature = "client", feature = "server"))]
327mod test {
328    use std::{fmt::Debug, io::ErrorKind};
329
330    use log::trace;
331
332    use crate::{
333        ClientRequest, Error, KeyConfig, KeyId, Server,
334        config::SymmetricSuite,
335        err::Res,
336        hpke::{Aead, Kdf, Kem},
337    };
338
339    pub const KEY_ID: KeyId = 1;
340    pub const KEM: Kem = Kem::X25519Sha256;
341    pub const SYMMETRIC: &[SymmetricSuite] = &[
342        SymmetricSuite::new(Kdf::HkdfSha256, Aead::Aes128Gcm),
343        SymmetricSuite::new(Kdf::HkdfSha256, Aead::ChaCha20Poly1305),
344    ];
345
346    pub const REQUEST: &[u8] = &[
347        0x00, 0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74, 0x70, 0x73, 0x0b, 0x65, 0x78, 0x61,
348        0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x01, 0x2f,
349    ];
350    pub const RESPONSE: &[u8] = &[0x01, 0x40, 0xc8];
351
352    pub fn init() {
353        crate::init();
354        _ = env_logger::try_init(); // ignore errors here
355    }
356
357    pub fn make_config() -> KeyConfig {
358        KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap()
359    }
360
361    #[test]
362    fn request_response() {
363        init();
364
365        let server_config = make_config();
366        let server = Server::new(server_config).unwrap();
367        let encoded_config = server.config().encode().unwrap();
368        trace!("Config: {}", hex::encode(&encoded_config));
369
370        let client = ClientRequest::from_encoded_config(&encoded_config).unwrap();
371        let (enc_request, client_response) = client.encapsulate(REQUEST).unwrap();
372        trace!("Request: {}", hex::encode(REQUEST));
373        trace!("Encapsulated Request: {}", hex::encode(&enc_request));
374
375        let (request, server_response) = server.decapsulate(&enc_request).unwrap();
376        assert_eq!(&request[..], REQUEST);
377
378        let enc_response = server_response.encapsulate(RESPONSE).unwrap();
379        trace!("Encapsulated Response: {}", hex::encode(&enc_response));
380
381        let response = client_response.decapsulate(&enc_response).unwrap();
382        assert_eq!(&response[..], RESPONSE);
383        trace!("Response: {}", hex::encode(RESPONSE));
384    }
385
386    #[test]
387    fn request_response_p256() {
388        init();
389
390        // P-256 HPKE may not be supported by all backends (e.g., NSS lacks P-256 HPKE).
391        if !super::HpkeConfig::new(Kem::P256Sha256, Kdf::HkdfSha256, Aead::Aes128Gcm).supported() {
392            return;
393        }
394
395        let server_config = KeyConfig::new(KEY_ID, Kem::P256Sha256, Vec::from(SYMMETRIC)).unwrap();
396        let server = Server::new(server_config).unwrap();
397        let encoded_config = server.config().encode().unwrap();
398        trace!("P256 Config: {}", hex::encode(&encoded_config));
399
400        let client = ClientRequest::from_encoded_config(&encoded_config).unwrap();
401        let (enc_request, client_response) = client.encapsulate(REQUEST).unwrap();
402        trace!("P256 Encapsulated Request: {}", hex::encode(&enc_request));
403
404        let (request, server_response) = server.decapsulate(&enc_request).unwrap();
405        assert_eq!(&request[..], REQUEST);
406
407        let enc_response = server_response.encapsulate(RESPONSE).unwrap();
408        let response = client_response.decapsulate(&enc_response).unwrap();
409        assert_eq!(&response[..], RESPONSE);
410    }
411
412    #[test]
413    fn request_response_xwing() {
414        init();
415
416        // X-Wing HPKE is only available with the rust-hpke backend.
417        if !super::HpkeConfig::new(Kem::XWing, Kdf::HkdfSha256, Aead::Aes128Gcm).supported() {
418            return;
419        }
420
421        let server_config = KeyConfig::new(KEY_ID, Kem::XWing, Vec::from(SYMMETRIC)).unwrap();
422        let server = Server::new(server_config).unwrap();
423        let encoded_config = server.config().encode().unwrap();
424        trace!("X-Wing Config: {}", hex::encode(&encoded_config));
425
426        let client = ClientRequest::from_encoded_config(&encoded_config).unwrap();
427        let (enc_request, client_response) = client.encapsulate(REQUEST).unwrap();
428        trace!("X-Wing Encapsulated Request: {}", hex::encode(&enc_request));
429
430        let (request, server_response) = server.decapsulate(&enc_request).unwrap();
431        assert_eq!(&request[..], REQUEST);
432
433        let enc_response = server_response.encapsulate(RESPONSE).unwrap();
434        let response = client_response.decapsulate(&enc_response).unwrap();
435        assert_eq!(&response[..], RESPONSE);
436    }
437
438    #[test]
439    fn two_requests() {
440        init();
441
442        let server_config = make_config();
443        let server = Server::new(server_config).unwrap();
444        let encoded_config = server.config().encode().unwrap();
445
446        let client1 = ClientRequest::from_encoded_config(&encoded_config).unwrap();
447        let (enc_request1, client_response1) = client1.encapsulate(REQUEST).unwrap();
448        let client2 = ClientRequest::from_encoded_config(&encoded_config).unwrap();
449        let (enc_request2, client_response2) = client2.encapsulate(REQUEST).unwrap();
450        assert_ne!(enc_request1, enc_request2);
451
452        let (request1, server_response1) = server.decapsulate(&enc_request1).unwrap();
453        assert_eq!(&request1[..], REQUEST);
454        let (request2, server_response2) = server.decapsulate(&enc_request2).unwrap();
455        assert_eq!(&request2[..], REQUEST);
456
457        let enc_response1 = server_response1.encapsulate(RESPONSE).unwrap();
458        let enc_response2 = server_response2.encapsulate(RESPONSE).unwrap();
459        assert_ne!(enc_response1, enc_response2);
460
461        let response1 = client_response1.decapsulate(&enc_response1).unwrap();
462        assert_eq!(&response1[..], RESPONSE);
463        let response2 = client_response2.decapsulate(&enc_response2).unwrap();
464        assert_eq!(&response2[..], RESPONSE);
465    }
466
467    fn assert_truncated<T: Debug>(res: Res<T>) {
468        match res.unwrap_err() {
469            Error::Truncated => {}
470            #[cfg(feature = "rust-hpke")]
471            Error::Aead(_) => {}
472            #[cfg(feature = "nss")]
473            Error::Crypto(_) => {}
474            Error::Io(e) => assert_eq!(e.kind(), ErrorKind::UnexpectedEof),
475            e => panic!("unexpected error type: {e:?}"),
476        }
477    }
478
479    fn request_truncated(cut: usize) {
480        init();
481
482        let server_config = make_config();
483        let server = Server::new(server_config).unwrap();
484        let encoded_config = server.config().encode().unwrap();
485
486        let client = ClientRequest::from_encoded_config(&encoded_config).unwrap();
487        let (enc_request, _) = client.encapsulate(REQUEST).unwrap();
488
489        let res = server.decapsulate(&enc_request[..cut]);
490        assert_truncated(res);
491    }
492
493    #[test]
494    fn request_truncated_header() {
495        request_truncated(4);
496    }
497
498    #[test]
499    fn request_truncated_enc() {
500        // header is 7, enc is 32
501        request_truncated(24);
502    }
503
504    #[test]
505    fn request_truncated_ct() {
506        // header and enc is 39, aead needs at least 16 more
507        request_truncated(42);
508    }
509
510    fn response_truncated(cut: usize) {
511        init();
512
513        let server_config = make_config();
514        let server = Server::new(server_config).unwrap();
515        let encoded_config = server.config().encode().unwrap();
516
517        let client = ClientRequest::from_encoded_config(&encoded_config).unwrap();
518        let (enc_request, client_response) = client.encapsulate(REQUEST).unwrap();
519
520        let (request, server_response) = server.decapsulate(&enc_request).unwrap();
521        assert_eq!(&request[..], REQUEST);
522
523        let enc_response = server_response.encapsulate(RESPONSE).unwrap();
524
525        let res = client_response.decapsulate(&enc_response[..cut]);
526        assert_truncated(res);
527    }
528
529    #[test]
530    fn response_truncated_ct() {
531        // nonce is 16, aead needs at least 16 more
532        response_truncated(20);
533    }
534
535    #[test]
536    fn response_truncated_nonce() {
537        response_truncated(7);
538    }
539
540    #[cfg(feature = "rust-hpke")]
541    #[test]
542    fn derive_key_pair() {
543        const IKM: &[u8] = &[
544            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
545            0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
546        ];
547        const EXPECTED_CONFIG: &[u8] = &[
548            0x01, 0x00, 0x20, 0xfc, 0x01, 0x38, 0x93, 0x64, 0x10, 0x31, 0x1a, 0x0c, 0x64, 0x1a,
549            0x5c, 0xa0, 0x86, 0x39, 0x1d, 0xe8, 0xe7, 0x03, 0x82, 0x33, 0x3f, 0x6d, 0x64, 0x49,
550            0x25, 0x21, 0xad, 0x7d, 0xc7, 0x8a, 0x5d, 0x00, 0x08, 0x00, 0x01, 0x00, 0x01, 0x00,
551            0x01, 0x00, 0x03,
552        ];
553
554        init();
555
556        let config = KeyConfig::decode(EXPECTED_CONFIG).unwrap();
557
558        let new_config = KeyConfig::derive(KEY_ID, KEM, Vec::from(SYMMETRIC), IKM).unwrap();
559        assert_eq!(config.key_id, new_config.key_id);
560        assert_eq!(config.kem, new_config.kem);
561        assert_eq!(config.symmetric, new_config.symmetric);
562
563        let server = Server::new(new_config).unwrap();
564        let encoded_config = server.config().encode().unwrap();
565        assert_eq!(EXPECTED_CONFIG, encoded_config);
566    }
567
568    #[test]
569    fn request_from_config_list() {
570        init();
571
572        let server_config = make_config();
573        let server = Server::new(server_config).unwrap();
574        let encoded_config = server.config().encode().unwrap();
575
576        let mut header: [u8; 2] = [0; 2];
577        header[0] = u8::try_from((encoded_config.len() & 0xFF00) >> 8).unwrap();
578        header[1] = u8::try_from(encoded_config.len() & 0xFF).unwrap();
579        let mut encoded_config_list = Vec::new();
580        encoded_config_list.extend(header.to_vec());
581        encoded_config_list.extend(encoded_config);
582
583        let client = ClientRequest::from_encoded_config_list(&encoded_config_list).unwrap();
584        let (enc_request, client_response) = client.encapsulate(REQUEST).unwrap();
585
586        let (request, server_response) = server.decapsulate(&enc_request).unwrap();
587        assert_eq!(&request[..], REQUEST);
588
589        let enc_response = server_response.encapsulate(RESPONSE).unwrap();
590
591        let response = client_response.decapsulate(&enc_response).unwrap();
592        assert_eq!(&response[..], RESPONSE);
593    }
594}