ML-DSA driver: support the expanded private key format #834
No reviewers
Labels
No labels
api-break
approved
bug
component-docs
component-platform
component-test
dependencies
deprecation
DO-NOT-MERGE
enhancement
good first issue
help wanted
needs-adoption
needs-backports
needs-ci
needs-design-approval
needs-info
needs-preceding-pr
needs-review
needs-reviewer
needs-work
priority-high
priority-low
priority-medium
priority-scheduled
priority-very-high
resolved-after-1.1
single-reviewer
size-l
size-m
size-optimisation
size-s
size-xs
No milestone
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gh-Mbed-TLS/TF-PSA-Crypto!834
Loading…
Reference in a new issue
No description provided.
Delete branch "mldsa-driver-expanded-key"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
In the ML-DSA driver, support export-public and signing where the key input is the concatenation of the seed and the expanded private key, in addition to supporting the seed only. Resolves https://github.com/Mbed-TLS/TF-PSA-Crypto/issues/774.
Needs preceding PR: framework prerequisite https://github.com/Mbed-TLS/mbedtls-framework/pull/312
PR checklist
Please remove the segment/s on either side of the | symbol as appropriate, and add any relevant link/s to the end of the line.
If the provided content is part of the present PR remove the # symbol.
One question on the scope, though otherwise looks good.
https://github.com/pq-code-package/mldsa-native/pull/1309 will remove the changing meaning of
MLD_ERR_FAIL. So we can revert this change once that patch reaches our mldsa-native submodule.Does this need to be mapped to the PSA error codes?
I'm not sure what you mean.
psa_status_t statusis a PSA error code.So, this PR adds sign_from_expanded() which returns a raw mldsa RC. It is correctly converted to a PSA error at psa_crypto_mldsa.c:282 using pqcp_to_psa_error. However in this case the return of sign_from_expanded is assigned directly to status which is returned here. Does this need a similar conversion, or have I missed something?
Got it, fixed.
This alerted me to a test gap: I hadn't added poisoning tests with the private key in joined format (now done). Though it actually doesn't catch this specific mistake, because for an out-of-memory error, we return the allocator error code in preference to the mistakenly unconverted error code.
I'll push another commit to fix the code style. I'm waiting for the CI to complete, to see if there's anything else to fix.
Looks good and functionally does what it should. I have only two concerns one about the library changes, and one for adding testing coverage.
@ -186,0 +236,4 @@} else {return export_public_from_expanded(psa_get_key_bits(attributes),key_buffer, key_buffer_size,data, data_size, data_length);Could we add a test covering the MLD_ERR_FAIL path from pk_from_sk() here? The non-matching-seed test only changes the leading seed, which this path skips, so the expanded private key remains valid.
We could achieve that using the current tests by corrupting a byte in tr, and checking that the test returns
PSA_ERROR_INVALID_ARGUMENTChecking for key_size after we have called the
tf_psa_crypto_pqcp_alloc_start. Effectively we permit input to unnecessarily acquire a shared allocator/mutex. Not sure if that can be exploited but I would make it to closely align the multipart setup. which reject invalid key lengths before allocating.e.g
So ideally we should be doing the following:
I don't understand the concern here. Why does it matter if the code acquires a mutex? Especially one for which we don't expect contention in practice, since the mutex only exists when using a custom allocator, and that only happens in some very constrained environments?
Now that the framework prerequisite is merged, I'm going to rebase this branch. I'll add extra tests in a subsequent commit.
Actually, there's no conflict on the framework, so I'll keep the existing history for now, plus a new framework update commit. It's better to keep the history because there were several changes of the test generator, and if I collapse all the framework updates into one, the generated code and
generate_mldsa_tests.pywon't be consistent with the submodule state anymore.Mostly I pointed that out as a disparity to the way it is being handled in the other two places in the code. Also this change in order is introduced in this pr, so I am wondering if that was accidental.
My main concern is weather it would be a good idea to call
tf_psa_crypto_pqcp_alloc_start()when not validating a validkey_buffer_sizeand weather it could result in a dos, from a miss-configured client.I won't block merging this pr for it, but I wanted us to take a closer look to the allocator.
@ -186,0 +236,4 @@} else {return export_public_from_expanded(psa_get_key_bits(attributes),key_buffer, key_buffer_size,data, data_size, data_length);Thank you for adressing this
Ok, it's good in principle to avoid more complex processing on error paths. But it's not like we can avoid having errors pop up with the allocator active. So it would only be a very minor robustness improvement. I don't think it's worth duplicating the
key_buffer_sizechecks over this.I'm still fiddling with the follow-up related to expanded keys https://github.com/Mbed-TLS/TF-PSA-Crypto/issues/862 and I might end up refactoring this function (as part of being a bit more systematic about deducing seed/joined/invalid from the private key size and the bits value). Since it's only a minor robustness issue, I'd prefer to do it in the follow-up, if at all.
I've done the refactoring I was thinking of when consuming a private key, and that actually fits into this PR thematically. So I might push it here. But I'm not sure I actually like it.
It pushes the (bits, private_key_size) → (validity, format) decision into a single function, so it's now fully uniform. Here, it does have the effect of moving one validation outside the buffer lock/unlock.
In terms of code size, it's a wash.
I'm not sure I like it because I find the new paths more convoluted. On the one hand, we now have a single point of validation, which is a good thing in principle. On the other hand, when you follow the individual paths, I originally had buffer size checks in the same function that relied on them. Now I find it harder to verify that we're passing valid buffer sizes when we call mldsa-native functions. That argument makes me still prefer the original design.
What do you think?
Having thought about it overnight, I don't like having the buffer size checks distant from where they matter. Keeping them close means that they will sometimes happen with the buffer mutex held. So I don't plan to make any changes here.
We might want to do some refactoring of buffer size checks when we support multiple parameter sets, but even then, I think the checks may happen with the lock held.
LGTM
Thank you for addressing my comments
LGTM (incremental review of commits https://github.com/Mbed-TLS/TF-PSA-Crypto/pull/834/changes/4af5f6cc6a9f6d40c5cf922ad10c40440d5be77e..31748d72247c564bfa6166cea173ee7d300163f2)
In the merge queue, one Windows job just disappeared into the ether. This seems like a glitch. I'm requeuing.