-
Notifications
You must be signed in to change notification settings - Fork 75
Feat: Add NIST SP800-108 KDF mechanisms #257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feat: Add NIST SP800-108 KDF mechanisms #257
Conversation
83afca7
to
b2bc6ed
Compare
2eb3eeb
to
ea1d1f5
Compare
Signed-off-by: Jacob Prud'homme <[email protected]>
…ism params Signed-off-by: Jacob Prud'homme <[email protected]>
…locations in PKCS#11 structs Signed-off-by: Jacob Prud'homme <[email protected]>
Signed-off-by: Jacob Prud'homme <[email protected]>
Signed-off-by: Jacob Prud'homme <[email protected]>
Signed-off-by: Jacob Prud'homme <[email protected]>
…correctly Because I was forgetting that the slice of Attributes within had to be converted to a slice of CK_ATTRIBUTEs! This need to convert internally (otherwise exposing end users to the internal CK_ATTRIBUTE type) meant that we needed to store this converted slice within each DerivedKey, so that the memory stayed valid. This meant playing around with a few changes to the standard "PKCS#11 struct wrapped in a Rust struct marked `transparent`" pattern that had been used for params Signed-off-by: Jacob Prud'homme <[email protected]>
Signed-off-by: Jacob Prud'homme <[email protected]>
Gave up some type safety on which types of PRF data parameters are allowed for which KDF modes, in exchange for being a 1-to-1 binding with the PKCS#11 spec. Depending on the wants of the project, this can be reversed. Signed-off-by: Jacob Prud'homme <[email protected]>
…after KDF operation Signed-off-by: Jacob Prud'homme <[email protected]>
Signed-off-by: Jacob Prud'homme <[email protected]>
Signed-off-by: Jacob Prud'homme <[email protected]>
Signed-off-by: Jacob Prud'homme <[email protected]>
Changed my mind on coding style Signed-off-by: Jacob Prud'homme <[email protected]>
Signed-off-by: Jacob Prud'homme <[email protected]>
Signed-off-by: Jacob Prud'homme <[email protected]>
Signed-off-by: Jacob Prud'homme <[email protected]>
Signed-off-by: Jacob Prud'homme <[email protected]>
ea1d1f5
to
d0a7c87
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it looks good. It's quite a sizable change 😅
The make_mechanism
made me think of #225 🤔
cryptoki/src/mechanism/kbkdf.rs
Outdated
/// * `endianness` - The endianness of the counter's bit representation. | ||
/// | ||
/// * `width_in_bits` - The number of bits used to represent the counter value. | ||
pub fn new(endianness: Endianness, width_in_bits: usize) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does width_in_bits
have any value constraints? Maybe using NonZeroUsize
would be a good idea? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spec doesn't explicitly say anything: https://docs.oasis-open.org/pkcs11/pkcs11-curr/v3.0/os/pkcs11-curr-v3.0-os.html#_Toc30061446. But I think it's logical that if you didn't want a Counter
or DkmLength
format object to be present in the input data, you would just leave it out instead of providing one with length 0. I'll make this change
/// Container for information on an additional key to be derived. | ||
#[derive(Debug)] | ||
pub struct DerivedKey { | ||
template: Pin<Box<[CK_ATTRIBUTE]>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just out of curiosity why this is pinned?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Essentially I just wanted to include some visual hint to the programmer that it's crucial that the address of this data doesn't change (because of the fact that we pass pointers into it to the backend). I know this is redundant since Box
guarantees this anyway, but I thought it would be nice to make this explicit. If you think it's just noise or could be confusing, I'm happy to remove it
#[derive(Debug)] | ||
pub struct KbkdfParams<'a> { | ||
/// Holds own data so that we have a contiguous memory region to give to backend | ||
_additional_derived_keys: Option<Pin<Box<[CK_DERIVED_KEY]>>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the underscore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we don't use this member, the lint check complains without it. It's just an internal member needed to hold the slice of additional key PKCS#11 structs so that it remains valid for the lifetime of the params object (this is a consequence of what I explained in the Design Justifications section of the PR body)
cryptoki/tests/basic.rs
Outdated
|
||
/* DOUBLE PIPELINE-MODE */ | ||
|
||
/* FIXME: NIST SP800-108 in double-pipeline mode is not yet supported by SoftHSM or Kryoptic */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it'd be good to move all these unsupported cases into a separate test and mark them with #[ignore = "unsupported by both softhsm and kryoptic"]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
Co-authored-by: Wiktor Kwapisiewicz <[email protected]> Signed-off-by: Jacob Prud'homme <[email protected]>
…nd `DkmLengthFormat` Signed-off-by: Jacob Prud'homme <[email protected]>
Signed-off-by: Jacob Prud'homme <[email protected]>
…o indicate necessity of mutability Signed-off-by: Jacob Prud'homme <[email protected]>
918c253
to
bea29ba
Compare
@wiktor-k glad to know I was on the same wavelength as previous work using mutable parameters! I was worried I was doing something weird. Also, did you have any comments/remarks on what I've written in the PR body? |
Tbh I want to calmly re-read it as I didn't have much time for open-source reviews today. I'll add a more concrete comment soon. |
Author: Jacob Prud'homme
Email: [email protected]
Description
This PR adds support for the NIST SP800-108 KDF (aka KBKDF) in counter-, feedback- and double pipeline-modes
Summary of Changes
SP800_108_{COUNTER,FEEDBACK,DOUBLE_PIPELINE}_KDF
mechanism typesMechanism
variants and associated parameters (KbkdfParams
for counter- and double pipeline-mode,KbkdfFeedbackParams
for feedback-mode)PrfDataParam
,KbkdfDkmLengthFormat
,KbkdfDkmLengthType
,KbkdfCounterFormat
,DerivedKey
)mechanism::make_mechanism()
Design Justifications
Since there's a decent amount of changes in this PR, I figured I'd start by justifying some of the choices I made, to preemptively answer any questions that might pop up.
KbkdfParams
/KbkdfFeedbackParams
transparent wrappers around the corresponding PKCS#11 struct, because it simply wasn't possible. Since we need to pass a&[Attribute]
to the constructor forDerivedKey
(unless we want to expose the end-user to the internalCK_ATTRIBUTE
struct), andAttribute
itself isn't a transparent wrapper aroundCK_ATTRIBUTE
, we have to convert to a slice ofCK_ATTRIBUTE
s and store it internally, so that that contiguous section of memory stays valid for the needed duration. The consequences of this choice bubble up to the top level, and requires that theKbkdf[Feedback]Params
themselves can not be transparent for the same reason. Because of this, I had to add an accessor for the wrapped PKCS#11 type to get things to work withmake_mechanism()
. I have suggestions below about making this more flexible for the futureDerivedKey
structs the end-user creates to update them with the handle of the created key, and so it is up to that user to go back and collect theseObjectHandle
s for the additional derived keys after callingderive_key()
(see this test for an example). While slightly less convenient, this more closely matches the paradigm of the spec itself (where pointers to handles are passed in via the params, and these are then mutated when the corresponding key is derived). Theadditional_derived_keys
argument is also correspondingly markedmut
, to signal that it will be mutated on the PKCS#11 backendderive_keys()
function, not present in the spec, as well as aHasAdditionalDerivedKeys
trait to allow accessing such data on arbitrary param types. This clunky solution was significantly less attractive tooDerivedKey
andKbkdfParams
/KbkdfFeedbackParams
in aPin
. While pinning a box does nothing, I figured it was important to mark that this data should have a stable address, since the C side relies on pointers to it remaining validQuestions/Feedback Wanted
General Questions
HkdfParams
that return different parts of the internal data. I'm unsure of their purpose, however. Should I also add these toKbkdf[Feedback]Params
?Coding Style Questions
Endianness
enum is quite general, should I place this somewhere else?Design Questions
Kbkdf(...)
instead of something likeSp800108(...)
, because the naming of the latter is atrocious. However, the term KBKDF is never used in the spec and may lead to people not easily discovering the functionality they need. How do you feel about this naming?PrfCounterDataParam
andPrfDataParam
types, as there are some differences between the data parameters allowed by the KDF in counter-mode vs the other modes. Notably, in counter-mode,IterationVariable
must take aKbkdfCounterFormat
argument, and theCounter
data parameter type is not allowed. While distinguishing these two types of params ensures the end-user could not make mistakes when configuring the KDF, it goes against the spec, which has just a singleCK_PRF_DATA_PARAM
type. Would the type-safety focus of the original design be preferred over the current 1-to-1 mapping with the spec? In the latter case, the backend should returnCKR_MECHANISM_PARAM_INVALID
for these types of errors anyway, but it may be nice to stop these errors at the type system-levelSuggestions for Future
Both of these came up while trying to work around the limitations I ran into on Design Justifications (1).
Attribute
a transparent wrapper aroundCK_ATTRIBUTE
? That way we could get rid of the inconsistencies between constructing aKbkdf[Feedback]Params
vs other parameter types, i.e. have a tree of just transparent wrappers all the way to the root params object, which we can directly pass as a pointer to the C side, as was done up until nowAsPkcsParams
defining aas_pkcs_params_ptr()
method with the following default implementation that covers 99% of the cases so far (i.e. all cases except this one):While this partially overlaps with (1) in terms of solving some inconsistencies that I've had to introduce to make my changes fit, I think it has other benefits. While not so important because
make_mechanism()
is internal, it would still prevent developers from making a mistake and enforce that the function is only used with parameter objects that explicitly implement the trait, instead of arbitrary types. As well, the impact of implementing this is quite small. I could even make that a part of this PR, if you're interested!