1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/// HKDF Errors.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
    /// The requested output key material in expand was too large for the used
    /// hash function.
    OkmTooLarge,
    /// At least one function argument has been too large to process.
    ArgumentsTooLarge,
}

macro_rules! impl_hkdf {
    ($name:ident,$extract:ident,$expand:ident,$tag_len:literal) => {
        pub mod $name {
            use super::Error;

            /// HKDF extract using the `salt`, and the input key material `ikm`.
            /// Returns the pre-key material in an array of tag length.
            ///
            /// Note that this function panics if `salt` or `ikm` is larger than 2**32 bytes.
            pub fn extract(salt: &[u8], ikm: &[u8]) -> [u8; $tag_len] {
                let mut prk = [0u8; $tag_len];
                unsafe {
                    hacl_sys::$extract(
                        prk.as_mut_ptr(),
                        salt.as_ptr() as _,
                        salt.len().try_into().unwrap(),
                        ikm.as_ptr() as _,
                        ikm.len().try_into().unwrap(),
                    );
                }
                prk
            }

            /// HKDF expand using the pre-key material `prk` and `info`. The output length
            /// is defined through the result type.
            /// Returns the key material in an array of length `okm_len` or
            /// [`Error::OkmTooLarge`] if the requested `okm_len` is too large.
            ///
            /// Note that this function returns an [`Error::ArgumentsTooLarge`]
            /// if `salt`, `ikm`, or `OKM_LEN` is larger than 2**32 bytes.
            pub fn expand<const OKM_LEN: usize>(
                prk: &[u8],
                info: &[u8],
            ) -> Result<[u8; OKM_LEN], Error> {
                if OKM_LEN > 255 * $tag_len {
                    // Output size is too large. HACL doesn't catch this.
                    return Err(Error::OkmTooLarge);
                }
                let mut okm = [0u8; OKM_LEN];
                unsafe {
                    hacl_sys::$expand(
                        okm.as_mut_ptr(),
                        prk.as_ptr() as _,
                        prk.len().try_into().map_err(|_| Error::ArgumentsTooLarge)?,
                        info.as_ptr() as _,
                        info.len()
                            .try_into()
                            .map_err(|_| Error::ArgumentsTooLarge)?,
                        OKM_LEN.try_into().map_err(|_| Error::ArgumentsTooLarge)?,
                    );
                }
                Ok(okm)
            }

            /// HKDF using the `salt`, input key material `ikm`, `info`. The output length
            /// is defined through the result type.
            /// Calls `extract` and `expand` with the given input.
            ///
            /// Returns the key material in an array of length `okm_len`.
            pub fn hkdf<const OKM_LEN: usize>(
                salt: &[u8],
                ikm: &[u8],
                info: &[u8],
            ) -> Result<[u8; OKM_LEN], Error> {
                let prk = extract(salt, ikm);
                expand(&prk, info)
            }

            /// This module uses heap allocated vectors for cases where the output
            /// length is not const.
            pub mod vec {
                use super::super::Error;

                /// HKDF expand using the pre-key material `prk` and `info`. The output length
                /// is defined through the result type.
                /// Returns the key material in an array of length `okm_len` or
                /// [`Error::OkmTooLarge`] if the requested `okm_len` is too large.
                ///
                /// Note that this function returns an [`Error::ArgumentsTooLarge`]
                /// if `salt`, `ikm`, or `OKM_LEN` is larger than 2**32 bytes.
                pub fn expand(prk: &[u8], info: &[u8], okm_len: usize) -> Result<Vec<u8>, Error> {
                    if okm_len > 255 * $tag_len {
                        // Output size is too large. HACL doesn't catch this.
                        return Err(Error::OkmTooLarge);
                    }
                    let mut okm = vec![0u8; okm_len];
                    unsafe {
                        hacl_sys::$expand(
                            okm.as_mut_ptr(),
                            prk.as_ptr() as _,
                            prk.len().try_into().map_err(|_| Error::ArgumentsTooLarge)?,
                            info.as_ptr() as _,
                            info.len()
                                .try_into()
                                .map_err(|_| Error::ArgumentsTooLarge)?,
                            okm_len.try_into().map_err(|_| Error::ArgumentsTooLarge)?,
                        );
                    }
                    Ok(okm)
                }

                /// HKDF using the `salt`, input key material `ikm`, `info`. The output length
                /// is defined through the result type.
                /// Calls `extract` and `expand` with the given input.
                ///
                /// Returns the key material in an array of length `okm_len`.
                pub fn hkdf(
                    salt: &[u8],
                    ikm: &[u8],
                    info: &[u8],
                    okm_len: usize,
                ) -> Result<Vec<u8>, Error> {
                    let prk = super::extract(salt, ikm);
                    expand(&prk, info, okm_len)
                }
            }
        }
    };
}

impl_hkdf!(
    sha2_256,
    Hacl_HKDF_extract_sha2_256,
    Hacl_HKDF_expand_sha2_256,
    32
);

impl_hkdf!(
    sha2_384,
    Hacl_HKDF_extract_sha2_384,
    Hacl_HKDF_expand_sha2_384,
    48
);

impl_hkdf!(
    sha2_512,
    Hacl_HKDF_extract_sha2_512,
    Hacl_HKDF_expand_sha2_512,
    64
);