hpke/
kdf.rs

1//! Traits and structs for key derivation functions
2
3use crate::util::write_u16_be;
4
5use digest::{core_api::BlockSizeUser, Digest, OutputSizeUser};
6use generic_array::GenericArray;
7use hmac::SimpleHmac;
8use sha2::{Sha256, Sha384, Sha512};
9
10const VERSION_LABEL: &[u8] = b"HPKE-v1";
11
12// This is the maximum value of Nh. It is achieved by HKDF-SHA512 in RFC 9180 §7.2.
13pub(crate) const MAX_DIGEST_SIZE: usize = 64;
14
15// Pretty much all the KDF functionality is covered by the hkdf crate
16
17/// Represents key derivation functionality
18pub trait Kdf {
19    /// The underlying hash function
20    #[doc(hidden)]
21    type HashImpl: Clone + Digest + OutputSizeUser + BlockSizeUser;
22
23    /// The algorithm identifier for a KDF implementation
24    const KDF_ID: u16;
25}
26
27// We use Kdf as a type parameter, so this is to avoid ambiguity.
28use Kdf as KdfTrait;
29
30// Convenience types for the functions below
31pub(crate) type DigestArray<Kdf> =
32    GenericArray<u8, <<Kdf as KdfTrait>::HashImpl as OutputSizeUser>::OutputSize>;
33pub(crate) type SimpleHkdf<Kdf> =
34    hkdf::Hkdf<<Kdf as KdfTrait>::HashImpl, SimpleHmac<<Kdf as KdfTrait>::HashImpl>>;
35type SimpleHkdfExtract<Kdf> =
36    hkdf::HkdfExtract<<Kdf as KdfTrait>::HashImpl, SimpleHmac<<Kdf as KdfTrait>::HashImpl>>;
37
38/// The implementation of HKDF-SHA256
39pub struct HkdfSha256 {}
40
41impl KdfTrait for HkdfSha256 {
42    #[doc(hidden)]
43    type HashImpl = Sha256;
44
45    // RFC 9180 §7.2: HKDF-SHA256
46    const KDF_ID: u16 = 0x0001;
47}
48
49/// The implementation of HKDF-SHA384
50pub struct HkdfSha384 {}
51
52impl KdfTrait for HkdfSha384 {
53    #[doc(hidden)]
54    type HashImpl = Sha384;
55
56    // RFC 9180 §7.2: HKDF-SHA384
57    const KDF_ID: u16 = 0x0002;
58}
59
60/// The implementation of HKDF-SHA512
61pub struct HkdfSha512 {}
62
63impl KdfTrait for HkdfSha512 {
64    #[doc(hidden)]
65    type HashImpl = Sha512;
66
67    // RFC 9180 §7.2: HKDF-SHA512
68    const KDF_ID: u16 = 0x0003;
69}
70
71// RFC 9180 §4.1
72// def ExtractAndExpand(dh, kem_context):
73//   eae_prk = LabeledExtract("", "eae_prk", dh)
74//   shared_secret = LabeledExpand(eae_prk, "shared_secret",
75//                                 kem_context, Nsecret)
76//   return shared_secret
77
78/// Uses the given IKM to extract a secret, and then uses that secret, plus the given suite ID and
79/// info string, to expand to the output buffer
80#[doc(hidden)]
81pub fn extract_and_expand<Kdf: KdfTrait>(
82    ikm: &[u8],
83    suite_id: &[u8],
84    info: &[u8],
85    out: &mut [u8],
86) -> Result<(), hkdf::InvalidLength> {
87    // Extract using given IKM
88    let (_, hkdf_ctx) = labeled_extract::<Kdf>(&[], suite_id, b"eae_prk", ikm);
89    // Expand using given info string
90    hkdf_ctx.labeled_expand(suite_id, b"shared_secret", info, out)
91}
92
93// RFC 9180 §4
94// def LabeledExtract(salt, label, ikm):
95//   labeled_ikm = concat("HPKE-v1", suite_id, label, ikm)
96//   return Extract(salt, labeled_ikm)
97
98/// Returns the HKDF context derived from `(salt=salt, ikm="HPKE-v1"||suite_id||label||ikm)`
99#[doc(hidden)]
100pub fn labeled_extract<Kdf: KdfTrait>(
101    salt: &[u8],
102    suite_id: &[u8],
103    label: &[u8],
104    ikm: &[u8],
105) -> (DigestArray<Kdf>, SimpleHkdf<Kdf>) {
106    // Call HKDF-Extract with the IKM being the concatenation of all of the above
107    let mut extract_ctx = SimpleHkdfExtract::<Kdf>::new(Some(salt));
108    extract_ctx.input_ikm(VERSION_LABEL);
109    extract_ctx.input_ikm(suite_id);
110    extract_ctx.input_ikm(label);
111    extract_ctx.input_ikm(ikm);
112    extract_ctx.finalize()
113}
114
115// This trait only exists so I can implement it for hkdf::Hkdf
116#[doc(hidden)]
117pub trait LabeledExpand {
118    /// Does a `LabeledExpand` key derivation function using HKDF. If `out.len()` is more than 255x
119    /// the digest size (in bytes) of the underlying hash function, returns an
120    /// `Err(hkdf::InvalidLength)`.
121    fn labeled_expand(
122        &self,
123        suite_id: &[u8],
124        label: &[u8],
125        info: &[u8],
126        out: &mut [u8],
127    ) -> Result<(), hkdf::InvalidLength>;
128}
129
130impl<D> LabeledExpand for hkdf::Hkdf<D, SimpleHmac<D>>
131where
132    D: Clone + OutputSizeUser + Digest + BlockSizeUser,
133{
134    // RFC 9180 §4
135    // def LabeledExpand(prk, label, info, L):
136    //   labeled_info = concat(I2OSP(L, 2), "HPKE-v1", suite_id,
137    //                         label, info)
138    //   return Expand(prk, labeled_info, L)
139
140    /// Does a `LabeledExpand` key derivation function using HKDF. If `out.len()` is more than 255x
141    /// the digest size (in bytes) of the underlying hash function, returns an
142    /// `Err(hkdf::InvalidLength)`.
143    fn labeled_expand(
144        &self,
145        suite_id: &[u8],
146        label: &[u8],
147        info: &[u8],
148        out: &mut [u8],
149    ) -> Result<(), hkdf::InvalidLength> {
150        // We need to write the length as a u16, so that's the de-facto upper bound on length
151        if out.len() > u16::MAX as usize {
152            // The error condition is met, since 2^16 is way bigger than 255 * digest_bytelen
153            return Err(hkdf::InvalidLength);
154        }
155
156        // Encode the output length in the info string
157        let mut len_buf = [0u8; 2];
158        write_u16_be(&mut len_buf, out.len() as u16);
159
160        // Call HKDF-Expand() with the info string set to the concatenation of all of the above
161        let labeled_info = [&len_buf, VERSION_LABEL, suite_id, label, info];
162        self.expand_multi_info(&labeled_info, out)
163    }
164}