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}