Coverage Report

Created: 2026-05-18 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/git/checkouts/nss-rs-71e20fe79ef91440/9b94ca3/src/hkdf.rs
Line
Count
Source
1
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4
// option. This file may not be copied, modified, or distributed
5
// except according to those terms.
6
7
#![allow(non_camel_case_types, reason = "C enum naming")]
8
9
use std::{
10
    convert::TryFrom as _,
11
    marker::PhantomData,
12
    os::raw::{c_char, c_int, c_uint},
13
    ptr::null_mut,
14
};
15
16
use pkcs11_bindings::{CK_ULONG, CKA_DERIVE, CKA_SIGN, CKM_HKDF_DERIVE, CKM_HKDF_KEY_GEN};
17
18
use crate::{
19
    Error, SECItem, SECItemBorrowed, SECItemType,
20
    constants::{
21
        Cipher, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256,
22
        TLS_VERSION_1_3, Version,
23
    },
24
    err::Res,
25
    p11::{
26
        self, CK_ATTRIBUTE_TYPE, CK_BBOOL, CK_INVALID_HANDLE, CK_MECHANISM_TYPE,
27
        CKF_HKDF_SALT_DATA, CKF_HKDF_SALT_NULL, CKM_HKDF_DATA, PK11_ImportDataKey, PK11Origin,
28
        PK11SymKey, Slot, SymKey, random,
29
    },
30
    ssl::CK_OBJECT_HANDLE,
31
};
32
33
experimental_api!(SSL_HkdfExtract(
34
    version: Version,
35
    cipher: Cipher,
36
    salt: *mut PK11SymKey,
37
    ikm: *mut PK11SymKey,
38
    prk: *mut *mut PK11SymKey,
39
));
40
experimental_api!(SSL_HkdfExpandLabel(
41
    version: Version,
42
    cipher: Cipher,
43
    prk: *mut PK11SymKey,
44
    handshake_hash: *const u8,
45
    handshake_hash_len: c_uint,
46
    label: *const c_char,
47
    label_len: c_uint,
48
    secret: *mut *mut PK11SymKey,
49
));
50
51
#[derive(Clone, Copy, Debug)]
52
53
pub enum HkdfError {
54
    InvalidPrkLength,
55
    InvalidLength,
56
    InternalError,
57
}
58
59
#[derive(Clone, Copy, Debug)]
60
pub enum HkdfAlgorithm {
61
    HKDF_SHA2_256,
62
    HKDF_SHA2_384,
63
    HKDF_SHA2_512,
64
}
65
66
#[derive(Clone, Copy, Debug)]
67
pub enum KeyMechanism {
68
    Hkdf,
69
}
70
71
impl KeyMechanism {
72
0
    fn mech(self) -> CK_MECHANISM_TYPE {
73
0
        CK_MECHANISM_TYPE::from(match self {
74
0
            Self::Hkdf => CKM_HKDF_DERIVE,
75
        })
76
0
    }
77
78
0
    const fn len(self) -> usize {
79
0
        match self {
80
0
            Self::Hkdf => 0, // Let the underlying module decide.
81
        }
82
0
    }
83
}
84
#[derive(Clone, Copy, Debug)]
85
86
pub(crate) struct ParamItem<'a, T> {
87
    item: SECItem,
88
    marker: PhantomData<&'a T>,
89
}
90
91
impl<'a, T: Sized + 'a> ParamItem<'a, T> {
92
0
    pub fn new(v: &'a mut T) -> Result<Self, HkdfError> {
93
0
        let item = SECItem {
94
            type_: SECItemType::siBuffer,
95
0
            data: std::ptr::from_mut::<T>(v).cast::<u8>(),
96
0
            len: c_uint::try_from(size_of::<T>()).map_err(|_| HkdfError::InvalidLength)?,
97
        };
98
0
        Ok(Self {
99
0
            item,
100
0
            marker: PhantomData,
101
0
        })
102
0
    }
103
104
0
    pub const fn ptr(&mut self) -> *mut SECItem {
105
0
        std::ptr::addr_of_mut!(self.item)
106
0
    }
107
}
108
109
const MAX_KEY_SIZE: usize = 48;
110
598
const fn key_size(version: Version, cipher: Cipher) -> Res<usize> {
111
598
    if version != TLS_VERSION_1_3 {
112
0
        return Err(Error::UnsupportedVersion);
113
598
    }
114
598
    let size = match cipher {
115
598
        TLS_AES_128_GCM_SHA256 | TLS_CHACHA20_POLY1305_SHA256 => 32,
116
0
        TLS_AES_256_GCM_SHA384 => 48,
117
0
        _ => return Err(Error::UnsupportedCipher),
118
    };
119
598
    debug_assert!(size <= MAX_KEY_SIZE);
120
598
    Ok(size)
121
598
}
122
123
/// Generate a random key of the right size for the given suite.
124
///
125
/// # Errors
126
///
127
/// If the ciphersuite or protocol version is not supported.
128
598
pub fn generate_key(version: Version, cipher: Cipher) -> Res<SymKey> {
129
    // With generic_const_expr, this becomes:
130
    //   import_key(version, &random::<{ key_size(version, cipher) }>())
131
598
    import_key(
132
598
        version,
133
598
        &random::<MAX_KEY_SIZE>()[0..key_size(version, cipher)?],
134
    )
135
598
}
136
137
/// Import a symmetric key for use with HKDF.
138
///
139
/// # Errors
140
///
141
/// Errors returned if the key buffer is an incompatible size or the NSS functions fail.
142
19.2k
pub fn import_key(version: Version, buf: &[u8]) -> Res<SymKey> {
143
19.2k
    if version != TLS_VERSION_1_3 {
144
0
        return Err(Error::UnsupportedVersion);
145
19.2k
    }
146
19.2k
    let slot = Slot::internal()?;
147
19.2k
    let key_ptr = unsafe {
148
19.2k
        PK11_ImportDataKey(
149
19.2k
            *slot,
150
19.2k
            CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
151
            PK11Origin::PK11_OriginUnwrap,
152
19.2k
            CK_ATTRIBUTE_TYPE::from(CKA_DERIVE),
153
19.2k
            SECItemBorrowed::wrap(buf)?.as_mut(),
154
19.2k
            null_mut(),
155
        )
156
    };
157
19.2k
    SymKey::from_ptr(key_ptr)
158
19.2k
}
159
160
/// Extract a PRK from the given salt and IKM using the algorithm defined in RFC 5869.
161
///
162
/// # Errors
163
///
164
/// Errors returned if inputs are too large or the NSS functions fail.
165
9.47k
pub fn extract(
166
9.47k
    version: Version,
167
9.47k
    cipher: Cipher,
168
9.47k
    salt: Option<&SymKey>,
169
9.47k
    ikm: &SymKey,
170
9.47k
) -> Res<SymKey> {
171
9.47k
    let mut prk: *mut PK11SymKey = null_mut();
172
9.47k
    let salt_ptr: *mut PK11SymKey = salt.map_or(null_mut(), |s| **s);
173
9.47k
    unsafe { SSL_HkdfExtract(version, cipher, salt_ptr, **ikm, &raw mut prk) }?;
174
9.47k
    SymKey::from_ptr(prk)
175
9.47k
}
176
177
/// Expand a PRK using the HKDF-Expand-Label function defined in RFC 8446.
178
///
179
/// # Errors
180
///
181
/// Errors returned if inputs are too large or the NSS functions fail.
182
9.65k
pub fn expand_label(
183
9.65k
    version: Version,
184
9.65k
    cipher: Cipher,
185
9.65k
    prk: &SymKey,
186
9.65k
    handshake_hash: &[u8],
187
9.65k
    label: &str,
188
9.65k
) -> Res<SymKey> {
189
9.65k
    let l = label.as_bytes();
190
9.65k
    let mut secret: *mut PK11SymKey = null_mut();
191
192
    // Note that this doesn't allow for passing null() for the handshake hash.
193
    // A zero-length slice produces an identical result.
194
    unsafe {
195
9.65k
        SSL_HkdfExpandLabel(
196
9.65k
            version,
197
9.65k
            cipher,
198
9.65k
            **prk,
199
9.65k
            handshake_hash.as_ptr(),
200
9.65k
            c_uint::try_from(handshake_hash.len())?,
201
9.65k
            l.as_ptr().cast(),
202
9.65k
            c_uint::try_from(l.len())?,
203
9.65k
            &raw mut secret,
204
        )
205
0
    }?;
206
9.65k
    SymKey::from_ptr(secret)
207
9.65k
}
208
209
pub struct Hkdf {
210
    kdf: HkdfAlgorithm,
211
}
212
213
impl Hkdf {
214
    #[must_use]
215
0
    pub const fn new(kdf: HkdfAlgorithm) -> Self {
216
0
        Self { kdf }
217
0
    }
218
219
    #[expect(clippy::unused_self)]
220
0
    pub fn import_secret(&self, ikm: &[u8]) -> Result<SymKey, HkdfError> {
221
0
        crate::init().map_err(|_| HkdfError::InternalError)?;
222
223
0
        let slot = Slot::internal().map_err(|_| HkdfError::InternalError)?;
224
0
        let ikm_item = SECItemBorrowed::wrap(ikm).map_err(|_| HkdfError::InternalError)?;
225
0
        let ikm_item_ptr = std::ptr::from_ref(ikm_item.as_ref()).cast_mut();
226
227
0
        let ptr = unsafe {
228
0
            p11::PK11_ImportSymKey(
229
0
                *slot,
230
0
                CK_MECHANISM_TYPE::from(CKM_HKDF_KEY_GEN),
231
                PK11Origin::PK11_OriginUnwrap,
232
0
                CK_ATTRIBUTE_TYPE::from(CKA_SIGN),
233
0
                ikm_item_ptr,
234
0
                null_mut(),
235
            )
236
        };
237
0
        let s = SymKey::from_ptr(ptr).map_err(|_| HkdfError::InternalError)?;
238
0
        Ok(s)
239
0
    }
240
241
0
    fn mech(&self) -> CK_MECHANISM_TYPE {
242
0
        CK_MECHANISM_TYPE::from(match self.kdf {
243
0
            HkdfAlgorithm::HKDF_SHA2_256 => p11::CKM_SHA256,
244
0
            HkdfAlgorithm::HKDF_SHA2_384 => p11::CKM_SHA384,
245
0
            HkdfAlgorithm::HKDF_SHA2_512 => p11::CKM_SHA512,
246
        })
247
0
    }
248
249
0
    pub fn extract(&self, salt: &[u8], ikm: &SymKey) -> Result<SymKey, HkdfError> {
250
0
        crate::init().map_err(|_| HkdfError::InternalError)?;
251
252
0
        let salt_type = if salt.is_empty() {
253
0
            CKF_HKDF_SALT_NULL
254
        } else {
255
0
            CKF_HKDF_SALT_DATA
256
        };
257
0
        let mut params = p11::CK_HKDF_PARAMS {
258
0
            bExtract: CK_BBOOL::from(true),
259
0
            bExpand: CK_BBOOL::from(false),
260
0
            prfHashMechanism: self.mech(),
261
0
            ulSaltType: CK_ULONG::from(salt_type),
262
0
            pSalt: salt.as_ptr().cast_mut(), // const-cast = bad API
263
0
            ulSaltLen: CK_ULONG::try_from(salt.len()).map_err(|_| HkdfError::InvalidLength)?,
264
0
            hSaltKey: CK_OBJECT_HANDLE::from(CK_INVALID_HANDLE),
265
0
            pInfo: null_mut(),
266
            ulInfoLen: 0,
267
        };
268
0
        let mut params_item = ParamItem::new(&mut params)?;
269
0
        let ptr = unsafe {
270
0
            p11::PK11_Derive(
271
0
                **ikm,
272
0
                CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
273
0
                params_item.ptr(),
274
0
                CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
275
0
                CK_MECHANISM_TYPE::from(CKA_DERIVE),
276
                0,
277
            )
278
        };
279
280
0
        let prk = SymKey::from_ptr(ptr).map_err(|_| HkdfError::InternalError)?;
281
0
        Ok(prk)
282
0
    }
283
284
    // NB: `info` must outlive the returned value.
285
0
    fn expand_params(&self, info: &[u8]) -> p11::CK_HKDF_PARAMS {
286
0
        p11::CK_HKDF_PARAMS {
287
0
            bExtract: CK_BBOOL::from(false),
288
0
            bExpand: CK_BBOOL::from(true),
289
0
            prfHashMechanism: self.mech(),
290
0
            ulSaltType: CK_ULONG::from(CKF_HKDF_SALT_NULL),
291
0
            pSalt: null_mut(),
292
0
            ulSaltLen: 0,
293
0
            hSaltKey: CK_OBJECT_HANDLE::from(CK_INVALID_HANDLE),
294
0
            pInfo: info.as_ptr().cast_mut(), // const-cast = bad API
295
0
            ulInfoLen: CK_ULONG::try_from(info.len()).expect("Integer overflow"),
296
0
        }
297
0
    }
298
299
0
    pub fn expand_key(
300
0
        &self,
301
0
        prk: &SymKey,
302
0
        info: &[u8],
303
0
        key_mech: KeyMechanism,
304
0
    ) -> Result<SymKey, HkdfError> {
305
0
        crate::init().map_err(|_| HkdfError::InternalError)?;
306
307
0
        let mut params = self.expand_params(info);
308
0
        let mut params_item = ParamItem::new(&mut params)?;
309
0
        let ptr = unsafe {
310
0
            p11::PK11_Derive(
311
0
                **prk,
312
0
                CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
313
0
                params_item.ptr(),
314
0
                key_mech.mech(),
315
0
                CK_MECHANISM_TYPE::from(CKA_DERIVE),
316
0
                c_int::try_from(key_mech.len()).map_err(|_| HkdfError::InvalidLength)?,
317
            )
318
        };
319
0
        let okm = SymKey::from_ptr(ptr).map_err(|_| HkdfError::InternalError)?;
320
0
        Ok(okm)
321
0
    }
322
323
0
    pub fn expand_data(&self, prk: &SymKey, info: &[u8], len: usize) -> Result<Vec<u8>, HkdfError> {
324
0
        crate::init().map_err(|_| HkdfError::InternalError)?;
325
326
0
        let mut params = self.expand_params(info);
327
0
        let mut params_item = ParamItem::new(&mut params)?;
328
0
        let ptr = unsafe {
329
0
            p11::PK11_Derive(
330
0
                **prk,
331
0
                CK_MECHANISM_TYPE::from(CKM_HKDF_DATA),
332
0
                params_item.ptr(),
333
0
                CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
334
0
                CK_MECHANISM_TYPE::from(CKA_DERIVE),
335
0
                c_int::try_from(len).map_err(|_| HkdfError::InvalidLength)?,
336
            )
337
        };
338
0
        let k = SymKey::from_ptr(ptr).map_err(|_| HkdfError::InternalError)?;
339
0
        let r = Vec::from(k.key_data().map_err(|_| HkdfError::InternalError)?);
340
0
        Ok(r)
341
0
    }
342
}