Skip to content

Commit bc00348

Browse files
authored
Merge pull request #257 from jacobprudhomme/add-sp800-108-kdf
Feat: Add NIST SP800-108 KDF mechanisms
2 parents ac8f628 + 5b2a843 commit bc00348

File tree

3 files changed

+1617
-4
lines changed

3 files changed

+1617
-4
lines changed

cryptoki/src/mechanism/kbkdf.rs

+373
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
// Copyright 2025 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
//! Mechanisms of NIST key-based key derive functions (SP 800-108, informally KBKDF)
4+
//! See: <https://docs.oasis-open.org/pkcs11/pkcs11-curr/v3.0/os/pkcs11-curr-v3.0-os.html#_Toc30061446>
5+
6+
use core::{convert::TryInto, marker::PhantomData, ptr};
7+
use std::num::NonZeroUsize;
8+
9+
use cryptoki_sys::{
10+
CK_ATTRIBUTE, CK_ATTRIBUTE_PTR, CK_DERIVED_KEY, CK_DERIVED_KEY_PTR, CK_INVALID_HANDLE,
11+
CK_OBJECT_HANDLE, CK_OBJECT_HANDLE_PTR, CK_PRF_DATA_PARAM, CK_PRF_DATA_PARAM_PTR,
12+
CK_SP800_108_BYTE_ARRAY, CK_SP800_108_COUNTER, CK_SP800_108_COUNTER_FORMAT,
13+
CK_SP800_108_DKM_LENGTH, CK_SP800_108_DKM_LENGTH_FORMAT, CK_SP800_108_DKM_LENGTH_SUM_OF_KEYS,
14+
CK_SP800_108_DKM_LENGTH_SUM_OF_SEGMENTS, CK_SP800_108_FEEDBACK_KDF_PARAMS,
15+
CK_SP800_108_ITERATION_VARIABLE, CK_SP800_108_KDF_PARAMS, CK_ULONG,
16+
};
17+
18+
use crate::object::{Attribute, ObjectHandle};
19+
20+
use super::MechanismType;
21+
22+
/// Endianness of byte representation of data.
23+
#[derive(Debug, Clone, Copy, PartialEq)]
24+
pub enum Endianness {
25+
/// Little endian.
26+
Little,
27+
/// Big endian.
28+
Big,
29+
}
30+
31+
/// Defines encoding format for a counter value.
32+
///
33+
/// This structure wraps a `CK_SP800_108_COUNTER_FORMAT` structure.
34+
#[derive(Debug, Clone, Copy)]
35+
#[repr(transparent)]
36+
pub struct KbkdfCounterFormat(CK_SP800_108_COUNTER_FORMAT);
37+
38+
impl KbkdfCounterFormat {
39+
/// Construct encoding format for KDF's internal counter variable.
40+
///
41+
/// # Arguments
42+
///
43+
/// * `endianness` - The endianness of the counter's bit representation.
44+
///
45+
/// * `width_in_bits` - The number of bits used to represent the counter value.
46+
pub fn new(endianness: Endianness, width_in_bits: NonZeroUsize) -> Self {
47+
Self(CK_SP800_108_COUNTER_FORMAT {
48+
bLittleEndian: (endianness == Endianness::Little).into(),
49+
ulWidthInBits: width_in_bits
50+
.get()
51+
.try_into()
52+
.expect("bit width of KBKDF internal counter does not fit in CK_ULONG"),
53+
})
54+
}
55+
}
56+
57+
/// Method for calculating length of DKM (derived key material).
58+
///
59+
/// Corresponds to CK_SP800_108_DKM_LENGTH_METHOD.
60+
#[derive(Debug, Clone, Copy)]
61+
pub enum KbkdfDkmLengthMethod {
62+
/// Sum of length of all keys derived by given invocation of KDF.
63+
SumOfKeys,
64+
/// Sum of length of all segments of output produced by PRF in given invocation of KDF.
65+
SumOfSegments,
66+
}
67+
68+
/// Defines encoding format for DKM (derived key material).
69+
///
70+
/// This structure wraps a `CK_SP800_108_DKM_LENGTH_FORMAT` structure.
71+
#[derive(Debug, Clone, Copy)]
72+
#[repr(transparent)]
73+
pub struct KbkdfDkmLengthFormat(CK_SP800_108_DKM_LENGTH_FORMAT);
74+
75+
impl KbkdfDkmLengthFormat {
76+
/// Construct encoding format for length value of DKM (derived key material) from KDF.
77+
///
78+
/// # Arguments
79+
///
80+
/// * `dkm_length_method` - The method used to calculate the DKM length value.
81+
///
82+
/// * `endianness` - The endianness of the DKM length value's bit representation.
83+
///
84+
/// * `width_in_bits` - The number of bits used to represent the DKM length value.
85+
pub fn new(
86+
dkm_length_method: KbkdfDkmLengthMethod,
87+
endianness: Endianness,
88+
width_in_bits: NonZeroUsize,
89+
) -> Self {
90+
Self(CK_SP800_108_DKM_LENGTH_FORMAT {
91+
dkmLengthMethod: match dkm_length_method {
92+
KbkdfDkmLengthMethod::SumOfKeys => CK_SP800_108_DKM_LENGTH_SUM_OF_KEYS,
93+
KbkdfDkmLengthMethod::SumOfSegments => CK_SP800_108_DKM_LENGTH_SUM_OF_SEGMENTS,
94+
},
95+
bLittleEndian: (endianness == Endianness::Little).into(),
96+
ulWidthInBits: width_in_bits
97+
.get()
98+
.try_into()
99+
.expect("bit width of KBKDF DKM length value does not fit in CK_ULONG"),
100+
})
101+
}
102+
}
103+
104+
/// The type of a segment of input data for the PRF, for a KBKDF operating in feedback- or double pipeline-mode.
105+
#[derive(Debug, Clone, Copy)]
106+
pub enum PrfDataParamType<'a> {
107+
/// Identifies location of predefined iteration variable in constructed PRF input data.
108+
///
109+
/// For counter-mode, this must contain a [`KbkdfCounterFormat`].
110+
/// For feedback- and double-pipeline mode, this must contain [`None`].
111+
IterationVariable(Option<&'a KbkdfCounterFormat>),
112+
/// Identifies location of counter in constructed PRF input data.
113+
Counter(&'a KbkdfCounterFormat),
114+
/// Identifies location of DKM (derived key material) length value in constructed PRF input data.
115+
DkmLength(&'a KbkdfDkmLengthFormat),
116+
/// Identifies location and value of byte array of data in constructed PRF input data.
117+
ByteArray(&'a [u8]),
118+
}
119+
120+
/// A segment of input data for the PRF, to be used to construct a sequence of input.
121+
///
122+
/// This structure wraps a `CK_PRF_DATA_PARAM` structure.
123+
///
124+
/// * [`PrfDataParamType::IterationVariable`] is required for the KDF in all modes.
125+
/// * In counter-mode, [`PrfDataParamType::IterationVariable`] must contain [`KbkdfCounterFormat`].
126+
/// In feedback- and double pipeline-mode, it must contain [`None`].
127+
/// * [`PrfDataParamType::Counter`] must not be present in counter-mode, and can be present at most
128+
/// once in feedback- and double-pipeline modes.
129+
/// * [`PrfDataParamType::DkmLength`] can be present at most once, in any mode.
130+
/// * [`PrfDataParamType::ByteArray`] can be present any amount of times, in any mode.
131+
#[derive(Debug, Clone, Copy)]
132+
#[repr(transparent)]
133+
pub struct PrfDataParam<'a> {
134+
inner: CK_PRF_DATA_PARAM,
135+
/// Marker type to ensure we don't outlive the data
136+
_marker: PhantomData<&'a [u8]>,
137+
}
138+
139+
impl<'a> PrfDataParam<'a> {
140+
/// Construct data parameter for input of the PRF internal to the KBKDF.
141+
///
142+
/// # Arguments
143+
///
144+
/// * `type_` - The specific type and parameters for the data parameter.
145+
pub fn new(type_: PrfDataParamType<'a>) -> Self {
146+
Self {
147+
inner: match type_ {
148+
PrfDataParamType::IterationVariable(None) => CK_PRF_DATA_PARAM {
149+
type_: CK_SP800_108_ITERATION_VARIABLE,
150+
pValue: ptr::null_mut(),
151+
ulValueLen: 0,
152+
},
153+
PrfDataParamType::IterationVariable(Some(counter_format)) => CK_PRF_DATA_PARAM {
154+
type_: CK_SP800_108_ITERATION_VARIABLE,
155+
pValue: counter_format as *const _ as *mut _,
156+
ulValueLen: size_of::<CK_SP800_108_COUNTER_FORMAT>() as CK_ULONG,
157+
},
158+
PrfDataParamType::Counter(counter_format) => CK_PRF_DATA_PARAM {
159+
type_: CK_SP800_108_COUNTER,
160+
pValue: counter_format as *const _ as *mut _,
161+
ulValueLen: size_of::<CK_SP800_108_COUNTER_FORMAT>() as CK_ULONG,
162+
},
163+
PrfDataParamType::DkmLength(dkm_length_format) => CK_PRF_DATA_PARAM {
164+
type_: CK_SP800_108_DKM_LENGTH,
165+
pValue: dkm_length_format as *const _ as *mut _,
166+
ulValueLen: size_of::<CK_SP800_108_DKM_LENGTH_FORMAT>() as CK_ULONG,
167+
},
168+
PrfDataParamType::ByteArray(data) => CK_PRF_DATA_PARAM {
169+
type_: CK_SP800_108_BYTE_ARRAY,
170+
pValue: data.as_ptr() as *mut _,
171+
ulValueLen: data
172+
.len()
173+
.try_into()
174+
.expect("length of PRF data parameter does not fit in CK_ULONG"),
175+
},
176+
},
177+
_marker: PhantomData,
178+
}
179+
}
180+
}
181+
182+
/// Container for information on an additional key to be derived.
183+
#[derive(Debug)]
184+
pub struct DerivedKey {
185+
/// Holds own data so that we have a contiguous memory region for backend to reference.
186+
/// Because of this, the address of this allocation must remain stable during its lifetime.
187+
template: Box<[CK_ATTRIBUTE]>,
188+
handle: CK_OBJECT_HANDLE,
189+
}
190+
191+
impl DerivedKey {
192+
/// Construct template for additional key to be derived by KDF.
193+
///
194+
/// # Arguments
195+
///
196+
/// * `template` - The template for the key to be derived.
197+
pub fn new(template: &[Attribute]) -> Self {
198+
let template: Box<[CK_ATTRIBUTE]> = template.iter().map(Into::into).collect();
199+
200+
Self {
201+
template,
202+
handle: CK_INVALID_HANDLE,
203+
}
204+
}
205+
206+
/// Return handle for derived key, if it has been created yet
207+
pub fn handle(&self) -> Option<ObjectHandle> {
208+
if self.handle == CK_INVALID_HANDLE {
209+
None
210+
} else {
211+
Some(ObjectHandle::new(self.handle))
212+
}
213+
}
214+
}
215+
216+
impl From<&mut DerivedKey> for CK_DERIVED_KEY {
217+
fn from(value: &mut DerivedKey) -> Self {
218+
CK_DERIVED_KEY {
219+
pTemplate: value.template.as_ptr() as CK_ATTRIBUTE_PTR,
220+
ulAttributeCount: value
221+
.template
222+
.len()
223+
.try_into()
224+
.expect("number of attributes in template does not fit in CK_ULONG"),
225+
phKey: &mut value.handle as CK_OBJECT_HANDLE_PTR,
226+
}
227+
}
228+
}
229+
230+
/// NIST SP 800-108 (aka KBKDF) counter and double pipeline-mode parameters.
231+
///
232+
/// This structure wraps a `CK_SP800_108_KDF_PARAMS` structure.
233+
#[derive(Debug)]
234+
pub struct KbkdfParams<'a> {
235+
/// Holds own data so that we have a contiguous memory region for backend to reference.
236+
/// Because of this, the address of this allocation must remain stable during its lifetime.
237+
_additional_derived_keys: Option<Box<[CK_DERIVED_KEY]>>,
238+
239+
inner: CK_SP800_108_KDF_PARAMS,
240+
/// Marker type to ensure we don't outlive the data
241+
_marker: PhantomData<&'a mut [u8]>,
242+
}
243+
244+
impl<'a> KbkdfParams<'a> {
245+
/// Construct parameters for NIST SP 800-108 KDF (aka KBKDF) pseudorandom function-based key
246+
/// derivation function, in counter or double pipeline-mode.
247+
///
248+
/// # Arguments
249+
///
250+
/// * `prf_mechanism` - The pseudorandom function that underlies the KBKDF operation.
251+
///
252+
/// * `prf_data_params` - The sequence of data segments used as input data for the PRF. Requires at least [`PrfDataParamType::IterationVariable`].
253+
///
254+
/// * `additional_derived_keys` - Any additional keys to be generated by the KDF from the base key.
255+
pub fn new(
256+
prf_mechanism: MechanismType,
257+
prf_data_params: &'a [PrfDataParam<'a>],
258+
additional_derived_keys: Option<&'a mut [DerivedKey]>,
259+
) -> Self {
260+
let mut additional_derived_keys = additional_derived_keys.map(|keys| {
261+
keys.iter_mut()
262+
.map(Into::into)
263+
.collect::<Box<[CK_DERIVED_KEY]>>()
264+
});
265+
266+
let inner = CK_SP800_108_KDF_PARAMS {
267+
prfType: prf_mechanism.into(),
268+
ulNumberOfDataParams: prf_data_params
269+
.len()
270+
.try_into()
271+
.expect("number of PRF data parameters does not fit in CK_ULONG"),
272+
pDataParams: prf_data_params.as_ptr() as CK_PRF_DATA_PARAM_PTR,
273+
ulAdditionalDerivedKeys: additional_derived_keys.as_ref().map_or(0, |keys| {
274+
keys.len()
275+
.try_into()
276+
.expect("number of additional derived keys does not fit in CK_ULONG")
277+
}),
278+
pAdditionalDerivedKeys: additional_derived_keys
279+
.as_mut()
280+
.map_or(ptr::null_mut(), |keys| {
281+
keys.as_mut_ptr() as CK_DERIVED_KEY_PTR
282+
}),
283+
};
284+
285+
Self {
286+
_additional_derived_keys: additional_derived_keys,
287+
288+
inner,
289+
_marker: PhantomData,
290+
}
291+
}
292+
293+
pub(crate) fn inner(&self) -> &CK_SP800_108_KDF_PARAMS {
294+
&self.inner
295+
}
296+
}
297+
298+
/// NIST SP 800-108 (aka KBKDF) feedback-mode parameters.
299+
///
300+
/// This structure wraps a `CK_SP800_108_FEEDBACK_KDF_PARAMS` structure.
301+
#[derive(Debug)]
302+
pub struct KbkdfFeedbackParams<'a> {
303+
/// Holds own data so that we have a contiguous memory region for backend to reference.
304+
/// Because of this, the address of this allocation must remain stable during its lifetime.
305+
_additional_derived_keys: Option<Box<[CK_DERIVED_KEY]>>,
306+
307+
inner: CK_SP800_108_FEEDBACK_KDF_PARAMS,
308+
/// Marker type to ensure we don't outlive the data
309+
_marker: PhantomData<&'a mut [u8]>,
310+
}
311+
312+
impl<'a> KbkdfFeedbackParams<'a> {
313+
/// Construct parameters for NIST SP 800-108 KDF (aka KBKDF) pseuderandom function-based key
314+
/// derivation function, in feedback-mode.
315+
///
316+
/// # Arguments
317+
///
318+
/// * `prf_mechanism` - The pseudorandom function that underlies the KBKDF operation.
319+
///
320+
/// * `prf_data_params` - The sequence of data segments used as input data for the PRF. Requires at least [`PrfDataParamType::IterationVariable`].
321+
///
322+
/// * `iv` - The IV to be used for the feedback-mode KDF.
323+
///
324+
/// * `additional_derived_keys` - Any additional keys to be generated by the KDF from the base key.
325+
pub fn new(
326+
prf_mechanism: MechanismType,
327+
prf_data_params: &'a [PrfDataParam<'a>],
328+
iv: Option<&'a [u8]>,
329+
additional_derived_keys: Option<&'a mut [DerivedKey]>,
330+
) -> Self {
331+
let mut additional_derived_keys = additional_derived_keys.map(|keys| {
332+
keys.iter_mut()
333+
.map(Into::into)
334+
.collect::<Box<[CK_DERIVED_KEY]>>()
335+
});
336+
337+
let inner = CK_SP800_108_FEEDBACK_KDF_PARAMS {
338+
prfType: prf_mechanism.into(),
339+
ulNumberOfDataParams: prf_data_params
340+
.len()
341+
.try_into()
342+
.expect("number of PRF data parameters does not fit in CK_ULONG"),
343+
pDataParams: prf_data_params.as_ptr() as CK_PRF_DATA_PARAM_PTR,
344+
ulIVLen: iv.map_or(0, |iv| {
345+
iv.len()
346+
.try_into()
347+
.expect("IV length does not fit in CK_ULONG")
348+
}),
349+
pIV: iv.map_or(ptr::null_mut(), |iv| iv.as_ptr() as *mut _),
350+
ulAdditionalDerivedKeys: additional_derived_keys.as_ref().map_or(0, |keys| {
351+
keys.len()
352+
.try_into()
353+
.expect("number of additional derived keys does not fit in CK_ULONG")
354+
}),
355+
pAdditionalDerivedKeys: additional_derived_keys
356+
.as_mut()
357+
.map_or(ptr::null_mut(), |keys| {
358+
keys.as_mut_ptr() as CK_DERIVED_KEY_PTR
359+
}),
360+
};
361+
362+
Self {
363+
_additional_derived_keys: additional_derived_keys,
364+
365+
inner,
366+
_marker: PhantomData,
367+
}
368+
}
369+
370+
pub(crate) fn inner(&self) -> &CK_SP800_108_FEEDBACK_KDF_PARAMS {
371+
&self.inner
372+
}
373+
}

0 commit comments

Comments
 (0)