Skip to content
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

KBKDF: add CounterLocation.MiddleFixed #7489

Merged
merged 10 commits into from Aug 15, 2022
Merged

KBKDF: add CounterLocation.MiddleFixed #7489

merged 10 commits into from Aug 15, 2022

Conversation

jeanpaulgalea
Copy link
Contributor

Hi,

Would you consider adding support for CounterLocation.MiddleFixed in KBKDFHMAC and KBKDFCMAC?

Currently the counter bytes can only be added at the beginning (CounterLocation.BeforeFixed) or end (CounterLocation.AfterFixed) of the input. This patch adds support for CounterLocation.MiddleFixed, which along with blocation=, allows the user to specify a particular byte offset where the counter bytes are to be added to input.

blocation is shorthand for "break location".

The MR is based off an internal patch that we make use of. It breaks backward compatibility due to the introduction of the new blocation= argument. If it's something worth your while, happy to discuss alternative implementations (to remain backward compatible) and help with adding tests and updating documentation.

Thank you

@reaperhulk
Copy link
Member

I wouldn't be opposed to supporting middle fixed. However, I'd like to look at ways to make the API backwards compatible and we should also expand the tests to use the middle_fixed vectors (they're currently skipped).

@jeanpaulgalea
Copy link
Contributor Author

Cool, thanks.

I did try the following earlier today. It should retain backwards compatibility, but it's a little crude... perhaps there are better ways of doing this in Python?

from enum import Enum
from functools import partial

def f(pos: int):
    return pos


class CounterLocation(Enum):
    BeforeFixed = 0
    AfterFixed = -1
    MiddleFixed = partial(f)

    def __call__(self, pos: int = None):
        return self.value(pos)


def _check(location: CounterLocation):
    if location == CounterLocation.BeforeFixed:
        print("before")
    elif location == CounterLocation.AfterFixed:
        print("after")
    else:
        print(location)


location = CounterLocation.BeforeFixed
_check(location)

location = CounterLocation.AfterFixed
_check(location)

location = CounterLocation.MiddleFixed(12)
_check(location)

@reaperhulk
Copy link
Member

That would definitely work but it's probably more discoverable to add blocation (is that the name in the KBKDF documentation?) as an optional kwarg to the end. The kwarg approach requires users to either use kwargs-only calling convention or explicitly pass a backend (None being the default value though) before the blocation though.

@alex
Copy link
Member

alex commented Aug 9, 2022 via email

@jeanpaulgalea
Copy link
Contributor Author

Updated to use an optional kwarg at the end: e98c7aa

@jeanpaulgalea
Copy link
Contributor Author

NIST SP 800-108 doesn't specify a name for the counter location (or I missed it). It simply states that the length and order is arbitrary and defined by the protocol where the KDF is used (page 12):

The length for each data field and an order shall be defined unambiguously. For example,
the length and the order may be defined as a part of a KDF specification or by the protocol
where the KDF is used. In each of the following sections, a specific order for the feedback
value, the counter, the Label, the separation indicator 0x00, the Context, and [L]2 is used,
assuming that each of them is represented with a specific length. This Recommendation
specifies several families of KDFs. Alternative orders for the input data fields may be used
for different KDFs.

However, NIST's own test vectors use breakLocation:
https://github.com/usnistgov/ACVP-Server/blob/master/gen-val/json-files/KDF-1.0/internalProjection.json

Perhaps you'd rather use that name? But snake case? i.e. break_location ?

@jeanpaulgalea
Copy link
Contributor Author

Added tests in b013a7b and 9b3652d. Could use your help to verify they're actually running and passing, though.

Also attempted to update documentation in a9cbef4.

@alex
Copy link
Member

alex commented Aug 10, 2022

break_location seems reasonable to me.

"blocation is ignored when location is not"
" CounterLocation.MiddleFixed"
)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to check that blocation is within 0..len(fixed or label+context) ?

In its present state, it's allowed to set blocation=-1 or =1000. This works fine with fixed[blocation:] and fixed[:blocation] further below, but not sure how pedantic we should be.

Also not sure if you prefer to require location=BeforeFixed instead of allowing location=MiddleFixed && blocation=0, and similarly location=AfterFixed instead of location=MiddleFixed && blocation=len(fixed or label+context).

My personal preference is to do none of these length checks and leave it up to the user, but obviously it's your call.

@jeanpaulgalea jeanpaulgalea changed the title WIP: KBKDF: add CounterLocation.MiddleFixed KBKDF: add CounterLocation.MiddleFixed Aug 10, 2022
@@ -51,6 +53,21 @@ def __init__(
if not isinstance(location, CounterLocation):
raise TypeError("location must be of type CounterLocation")

if break_location is None and location == CounterLocation.MiddleFixed:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if break_location is None and location == CounterLocation.MiddleFixed:
if break_location is None and location is CounterLocation.MiddleFixed:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, updated.

Copy link
Member

@reaperhulk reaperhulk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't completely reviewed this yet, but we should add a changelog entry as well.

@jeanpaulgalea
Copy link
Contributor Author

I haven't completely reviewed this yet, but we should add a changelog entry as well.

Thanks, changelog entry added 🚀

docs/hazmat/primitives/key-derivation-functions.rst Outdated Show resolved Hide resolved
docs/hazmat/primitives/key-derivation-functions.rst Outdated Show resolved Hide resolved
h.update(fixed)
h.update(counter)
else:
h.update(fixed[: self._break_location])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should validate break location, if it's negative or > len(fixed) dumb things happen.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, will do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Quite a bit had to change, might be best to review the MR in its entirety again... 😢

Comment on lines 499 to 500
params["fixedinputdata"] = params.pop("databeforectrdata")
params["fixedinputdata"] += params.pop("dataafterctrdata")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
params["fixedinputdata"] = params.pop("databeforectrdata")
params["fixedinputdata"] += params.pop("dataafterctrdata")
params["fixedinputdata"] = params.pop("databeforectrdata") + params.pop("dataafterctrdata")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, I tried this approach, but black reformats it into:

-        params["fixedinputdata"] = params.pop("databeforectrdata")
-        params["fixedinputdata"] += params.pop("dataafterctrdata")
+        params["fixedinputdata"] = params.pop(
+            "databeforectrdata"
+        ) + params.pop("dataafterctrdata")

Which I found a little less clear.

Would you still prefer this approach?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think I still prefer that, even if it's a bit stupid :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@@ -114,17 +135,27 @@ def derive(self, key_material: bytes, prf_output_size: int) -> bytes:
if rounds > pow(2, len(r_bin) * 8) - 1:
raise ValueError("There are too many iterations.")

fixed = self._generate_fixed_input()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be fine to move outside the loop, the fixed input doesn't change across iterations, except for the counter value.

- Update tests to assert exception is raised when
    break_location < 0 or > len(fixed)

- When asserting for "break_location is ignored when MiddleFixed",
    use break_location=0 instead of =10,
    to ensure we don't raise because of break_location > len(fixed)

- Assert that the right error messages are returned to the user.
@jeanpaulgalea
Copy link
Contributor Author

Should be ready for another round of review 🙏

Will refrain from pushing any further changes, unless specifically requested.

Cheers

@reaperhulk
Copy link
Member

This looks basically ready, but if we could make break_location a kwarg-only argument that would make life simpler in the distant future when we look to remove the backend arg. I don't recall when kwarg only syntax became available, but I suppose CI can test it for us 😂. Would you mind giving that a shot (it should just be backend=None, *, break_location=None in the type definition + the docs of course)?

@jeanpaulgalea
Copy link
Contributor Author

Thanks for the feedback!

TIL about kwarg-only arguments and it's been in Python since 3.0 apparently! Initially I thought you meant to simply move break_location to the end of the argument list.

Anyway, PR updated 🚀

@alex alex merged commit da1a30b into pyca:main Aug 15, 2022
@jeanpaulgalea
Copy link
Contributor Author

Thanks @alex and @reaperhulk!

Wish more upstreams were this easy to work with 😄

@jeanpaulgalea jeanpaulgalea deleted the jeanpaul/blocation branch August 15, 2022 12:42
@alex
Copy link
Member

alex commented Aug 15, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants