Add mbedtls_pk_get_key_type() #532

Merged
davidhorstmann-arm merged 44 commits from pk-get-key-type-function into development 2025-12-01 07:32:10 -06:00
davidhorstmann-arm commented 2025-10-17 10:18:18 -05:00 (Migrated from github.com)

Introduce a new function, mbedtls_pk_get_key_type(), to get the PSA key type of a PK context.

Note: Currently this uses mbedtls_pk_type_t and mbedtls_pk_get_type() internally, but I intend to refactor it to avoid using this soon.

Fixes #509

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.

  • changelog provided
  • framework PR not required
  • mbedtls development PR not required because: Crypto changes only
  • mbedtls 3.6 PR not required because: New API so will not backport
  • tests provided
Introduce a new function, `mbedtls_pk_get_key_type()`, to get the PSA key type of a PK context. ~Note: Currently this uses `mbedtls_pk_type_t` and `mbedtls_pk_get_type()` internally, but I intend to refactor it to avoid using this soon.~ Fixes #509 ## 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. - [x] **changelog** provided - [x] **framework PR** not required - [x] **mbedtls development PR** not required because: Crypto changes only - [x] **mbedtls 3.6 PR** not required because: New API so will not backport - **tests** provided
davidhorstmann-arm commented 2025-11-04 06:09:48 -06:00 (Migrated from github.com)

Rebased and conflicts resolved, this should be ready for review!

Rebased and conflicts resolved, this should be ready for review!
mpg commented 2025-11-05 05:31:33 -06:00 (Migrated from github.com)

Just a general comment as this is the first time I'm looking at this PR since the approach changed: the fact that you've had to add calls to mbedtls_pk_set_psa_type_from_private() in so many places in pkparse.c makes me a bit nervous: how do we know we haven't forgotten one, or that we won't forget it when adding a new code path in the future (which we probably will some time in 2026 for ML-DSA)?

Hopefully the tests should catch this, but that just move the problem to a different place: how do we know we're testing all cases, and that we will keep doing so in the future?

I wonder if we could structure the code differently to mitigate these risks. I'm thinking of two different approaches:

  • From the bottom: can we find places in pk_rsa.c and pk_ecc.c that would be the single point (for each key type) where priv_id gets populated while parsing a key, and set psa_type there? (That is, the way you did it for public keys.)
  • From the top: rename the existing public (or semi-public) parsing function to _internal and make the public function a wrapper that just calls the internal function then mbedtls_pk_set_psa_type_from_private().

Wdyt?

Just a general comment as this is the first time I'm looking at this PR since the approach changed: the fact that you've had to add calls to `mbedtls_pk_set_psa_type_from_private()` in so many places in `pkparse.c` makes me a bit nervous: how do we know we haven't forgotten one, or that we won't forget it when adding a new code path in the future (which we probably will some time in 2026 for ML-DSA)? Hopefully the tests should catch this, but that just move the problem to a different place: how do we know we're testing all cases, and that we will keep doing so in the future? I wonder if we could structure the code differently to mitigate these risks. I'm thinking of two different approaches: - From the bottom: can we find places in `pk_rsa.c` and `pk_ecc.c` that would be the single point (for each key type) where `priv_id` gets populated while parsing a key, and set `psa_type` there? (That is, the way you did it for public keys.) - From the top: rename the existing public (or semi-public) parsing function to `_internal` and make the public function a wrapper that just calls the internal function then `mbedtls_pk_set_psa_type_from_private()`. Wdyt?
davidhorstmann-arm commented 2025-11-05 05:55:06 -06:00 (Migrated from github.com)

I'm happy with either of those two approaches, but the approach from the top seems to be the more robust.

If we were to do the first (from the bottom) approach and then add ML-DSA, we'd need to add a new call to mbedtls_pk_set_psa_type_from_private() for the new key type, which we might miss.

I think the 'right' thing to do here would be to see if mbedtls_pk_parse_key() can be restructured so that it's easier to do post-parsing actions (and to make it easier to follow the control flow, as it's quite difficult to read at the moment). However, this would take too much time and I'm sure there would be some complications to it.

I'm happy with either of those two approaches, but the approach from the top seems to be the more robust. If we were to do the first (from the bottom) approach and then add ML-DSA, we'd need to add a new call to `mbedtls_pk_set_psa_type_from_private()` for the new key type, which we might miss. I think the 'right' thing to do here would be to see if `mbedtls_pk_parse_key()` can be restructured so that it's easier to do post-parsing actions (and to make it easier to follow the control flow, as it's quite difficult to read at the moment). However, this would take too much time and I'm sure there would be some complications to it.
valeriosetti commented 2025-11-05 07:30:17 -06:00 (Migrated from github.com)

Actually I would be in favor of an approach "from the bottom". Unless I missed something, in the end mbedtls_pk_[ecc|rsa]_set_key is always called for setting the key from mbedtls_pk_parse_key. Can't we set the psa_type there?
This would be more uniform with what is done in case of public keys.

With this solution you can also remove change in copy_from_psa because that one calls mbedtls_pk_[ecc|rsa]_set_key as well.

mbedtls_pk_[ecc|rsa]_set_key are the only point where they key pair is set, so setting psa_type should guarantee that we're not missing any corner case.

mbedtls_pk_parse_key() can be restructured ... it's quite difficult to read at the moment.

Agree

Actually I would be in favor of an approach "from the bottom". Unless I missed something, in the end `mbedtls_pk_[ecc|rsa]_set_key` is always called for setting the key from `mbedtls_pk_parse_key`. Can't we set the `psa_type` there? This would be more uniform with what is done in case of public keys. With this solution you can also remove change in `copy_from_psa` because that one calls `mbedtls_pk_[ecc|rsa]_set_key` as well. `mbedtls_pk_[ecc|rsa]_set_key` are the only point where they key pair is set, so setting `psa_type` should guarantee that we're not missing any corner case. > mbedtls_pk_parse_key() can be restructured ... it's quite difficult to read at the moment. Agree
valeriosetti (Migrated from github.com) requested changes 2025-11-05 07:46:35 -06:00
valeriosetti (Migrated from github.com) commented 2025-11-05 07:45:43 -06:00

If we keep this solution (i.e. we don't opt for the "from bottom" approach), no psa_reset_key_attributes before returning?

If we keep this solution (i.e. we don't opt for the "[from bottom"](https://github.com/Mbed-TLS/TF-PSA-Crypto/pull/532#issuecomment-3491235988) approach), no `psa_reset_key_attributes` before returning?
@ -2164,3 +2164,243 @@ exit:
PSA_DONE();
valeriosetti (Migrated from github.com) commented 2025-11-05 07:43:35 -06:00

Generating keys at every iteration might slow down test case. I think that pk_psa_setup() can be helpful here (and also in pk_get_key_type_copy_from_psa_keypair, pk_get_key_type_copy_from_psa_public and pk_get_key_type_copy_public_from_psa). It uses predefined keys so you don't need to generate them every time. This also relaxes requirements for the test function as key generation is no more required (i.e. no need for PSA_WANT_KEY_TYPE_[ECC|RSA]_KEY_PAIR_GENERATE).

Generating keys at every iteration might slow down test case. I think that `pk_psa_setup()` can be helpful here (and also in `pk_get_key_type_copy_from_psa_keypair`, `pk_get_key_type_copy_from_psa_public` and `pk_get_key_type_copy_public_from_psa`). It uses predefined keys so you don't need to generate them every time. This also relaxes requirements for the test function as key generation is no more required (i.e. no need for `PSA_WANT_KEY_TYPE_[ECC|RSA]_KEY_PAIR_GENERATE`).
valeriosetti (Migrated from github.com) requested changes 2025-11-05 08:16:01 -06:00
@ -403,6 +408,21 @@ int mbedtls_pk_can_do_psa(const mbedtls_pk_context *pk, psa_algorithm_t alg,
int mbedtls_pk_get_psa_attributes(const mbedtls_pk_context *pk,
psa_key_usage_t usage,
valeriosetti (Migrated from github.com) commented 2025-11-05 08:12:26 -06:00

Should we mention that the context must have been at least initialized first?

Should we mention that the context must have been at least initialized first?
davidhorstmann-arm commented 2025-11-05 10:40:40 -06:00 (Migrated from github.com)

In the last 2 commits I have fixed a couple of very small size calculation issues that I noticed when fixing CI failures.

This is a little cheeky of me as they are technically unrelated to this PR, so I will happily remove those commits if requested.

In the last 2 commits I have fixed a couple of very small size calculation issues that I noticed when fixing CI failures. This is a little cheeky of me as they are technically unrelated to this PR, so I will happily remove those commits if requested.
mpg (Migrated from github.com) reviewed 2025-11-06 04:21:20 -06:00
mpg (Migrated from github.com) left a comment

Just reviewing the last two commits. Will review the rest as my next step.

Just reviewing the last two commits. Will review the rest as my next step.
@ -750,1 +746,3 @@
* - 0 to 1 bytes of leading 0 due to the sign bit.
* is at most 3 bytes. The length of the encoding is overestimated as follows:
*
* - Take int(bits / 8) as the number of full bytes taken by the value.
mpg (Migrated from github.com) commented 2025-11-06 04:05:33 -06:00

Nit: I'm more used to ceil(x/8) = (x + 7) / 8. Of course, both are equivalent, unless x is near the end of the type's range: that is, your formula may have a problem for x == 0 while mine may have a problem when x > UINT_MAX - 7. Here we already assume bits <= 2^19 - 9 (see above) and in general I think we already assume UINT_MAX >= 2^32 -1 so we should be good. OTOH, x == 0 seems a bit unlikely but not explicitly ruled out by existing comments. Wdyt?

Nit: I'm more used to ceil(x/8) = (x + 7) / 8. Of course, both are equivalent, unless x is near the end of the type's range: that is, your formula may have a problem for `x == 0` while mine may have a problem when `x > UINT_MAX - 7`. Here we already assume `bits <= 2^19 - 9` (see above) and in general I think we already assume `UINT_MAX >= 2^32 -1` so we should be good. OTOH, `x == 0` seems a bit unlikely but not explicitly ruled out by existing comments. Wdyt?
mpg (Migrated from github.com) commented 2025-11-06 04:19:58 -06:00

I think the previous formula was correct, for reasons that are not obvious from the comment:

  • if bits is a multiple of 8, then bits / 8 is correct;
  • if bits is not a multiple of 8, then bits / 8 underestimates by 1, but OTOH we know for sure we won't need a leading 0 (because the top bit of the top byte is unset), so the +5u at the end overestimates by 1, which exactly compensates for the underestimation in the division by 8.

If you agree with this reasoning, I think we can restore the previous formula, but update the comment because we really shouldn't leave such things to the reader.

I think the previous formula was correct, for reasons that are not obvious from the comment: - if bits is a multiple of 8, then `bits / 8` is correct; - if bits is not a multiple of 8, then `bits / 8` underestimates by 1, but OTOH we know for sure we won't need a leading 0 (because the top bit of the top byte is unset), so the `+5u` at the end overestimates by 1, which exactly compensates for the underestimation in the division by 8. If you agree with this reasoning, I think we can restore the previous formula, but update the comment because we really shouldn't leave such things to the reader.
mpg commented 2025-11-06 04:27:58 -06:00 (Migrated from github.com)

In the last 2 commits I have fixed a couple of very small size calculation issues that I noticed when fixing CI failures.

This is a little cheeky of me as they are technically unrelated to this PR, so I will happily remove those commits if requested.

For reference, in general when we notice and fix an issue unrelated to the original goal of the PR, we should ask ourselves:

  1. Should this be backported? (That is: (a) does it apply to an LTS branch, and (b) is it worth it?)
  2. Am I fixing an observable bug? If so, this needs a ChangeLog entry and regression tests (and backports unless it's new in this branch). (Also, consider if this has a security impact.)

This is especially relevant when the original goal of the PR is something that's development-only and/or doesn't require a ChangeLog.

Here, after reviewing the changes, I think the only issues were in the comments, so the answer to (2) would be no, and regarding (1) I'm not sure but I'd say yes (especially the 2nd one which looks really suspicious and had me a bit worried for a moment).

> In the last 2 commits I have fixed a couple of very small size calculation issues that I noticed when fixing CI failures. > > This is a little cheeky of me as they are technically unrelated to this PR, so I will happily remove those commits if requested. For reference, in general when we notice and fix an issue unrelated to the original goal of the PR, we should ask ourselves: 1. Should this be backported? (That is: (a) does it apply to an LTS branch, and (b) is it worth it?) 2. Am I fixing an observable bug? If so, this needs a ChangeLog entry and regression tests (and backports unless it's new in this branch). (Also, consider if this has a security impact.) This is especially relevant when the original goal of the PR is something that's development-only and/or doesn't require a ChangeLog. Here, after reviewing the changes, I think the only issues were in the comments, so the answer to (2) would be no, and regarding (1) I'm not sure but I'd say yes (especially the 2nd one which looks really suspicious and had me a bit worried for a moment).
mpg (Migrated from github.com) reviewed 2025-11-06 04:30:04 -06:00
mpg (Migrated from github.com) commented 2025-11-06 04:30:04 -06:00

I think last time I asked Gilles, he said calling reset_key_attributes() is good practice but it's not an actual bug if we don't call it.

I think last time I asked Gilles, he said calling `reset_key_attributes()` is good practice but it's not an actual bug if we don't call it.
mpg commented 2025-11-06 04:44:00 -06:00 (Migrated from github.com)

I'm happy with either of those two approaches, but the approach from the top seems to be the more robust.

If we were to do the first (from the bottom) approach and then add ML-DSA, we'd need to add a new call to mbedtls_pk_set_psa_type_from_private() for the new key type, which we might miss.

Intuitively, I think I had a preference for the approach "from the bottom", for the same reasons as Valerio. But you have a point that it means when we add ML-DSA we have to remember to set psa_type in the equivalent "bottom" function - or equivalently, we have to remember adding a test that calls pk_get_key_type() on a parsed ML-DSA key.

Hopefully the tests should catch this, but that just move the problem to a different place: how do we know we're testing all cases, and that we will keep doing so in the future?

Well, if I may answer my own question: what if we take the existing test functions for parsing and add a mandatory parameter for "expected key type" (or just a check if the function is already specialised for one type)? I mean, presumably (hopefully!) we already have tests covering all cases that can happen during parsing, so we can just take advantage of that. Also, when we add support for parsing ML-DSA keys, surely we'll want to add test cases using pk_parse_key() and if this has a parameter expected_key_type (that it checks against calling pk_get_type() on the parsed key) then we can't forget to provide it.

Wdyt?

> I'm happy with either of those two approaches, but the approach from the top seems to be the more robust. > > If we were to do the first (from the bottom) approach and then add ML-DSA, we'd need to add a new call to `mbedtls_pk_set_psa_type_from_private()` for the new key type, which we might miss. Intuitively, I think I had a preference for the approach "from the bottom", for the same reasons as Valerio. But you have a point that it means when we add ML-DSA we have to remember to set `psa_type` in the equivalent "bottom" function - or equivalently, we have to remember adding a test that calls `pk_get_key_type()` on a parsed ML-DSA key. > Hopefully the tests should catch this, but that just move the problem to a different place: how do we know we're testing all cases, and that we will keep doing so in the future? Well, if I may answer my own question: what if we take the existing test functions for parsing and add a mandatory parameter for "expected key type" (or just a check if the function is already specialised for one type)? I mean, presumably (hopefully!) we already have tests covering all cases that can happen during parsing, so we can just take advantage of that. Also, when we add support for parsing ML-DSA keys, surely we'll want to add test cases using `pk_parse_key()` and if this has a parameter `expected_key_type` (that it checks against calling `pk_get_type()` on the parsed key) then we can't forget to provide it. Wdyt?
mpg commented 2025-11-06 04:48:45 -06:00 (Migrated from github.com)

Alternative approach: psa_type is only set for public keys, but for private keys pk_get_type() always retrieves it from priv_id.

Dream: we store all keys (public or private) in priv_id, then we don't need a separate psa_type field. I'm explicitly not suggesting we make that change in this PR though :) But I think once we've progressed enough on the cleanups planned for this quarter, that's something I'd like to consider (should be easier to do by then).

Alternative approach: `psa_type` is only set for public keys, but for private keys `pk_get_type()` always retrieves it from `priv_id`. Dream: we store all keys (public or private) in `priv_id`, then we don't need a separate `psa_type` field. I'm explicitly _not_ suggesting we make that change in this PR though :) But I think once we've progressed enough on the cleanups planned for this quarter, that's something I'd like to consider (should be easier to do by then).
davidhorstmann-arm (Migrated from github.com) reviewed 2025-11-06 04:52:03 -06:00
@ -750,1 +746,3 @@
* - 0 to 1 bytes of leading 0 due to the sign bit.
* is at most 3 bytes. The length of the encoding is overestimated as follows:
*
* - Take int(bits / 8) as the number of full bytes taken by the value.
davidhorstmann-arm (Migrated from github.com) commented 2025-11-06 04:52:03 -06:00

Yes, that sounds correct to me, I'll update it.

Yes, that sounds correct to me, I'll update it.
davidhorstmann-arm commented 2025-11-06 05:15:13 -06:00 (Migrated from github.com)

Alternative approach: psa_type is only set for public keys, but for private keys pk_get_type() always retrieves it from priv_id.

That would work well, I think! Should I change the name of the new field to something like pk->public_psa_type in that case?

> Alternative approach: `psa_type` is only set for public keys, but for private keys `pk_get_type()` always retrieves it from `priv_id`. That would work well, I think! Should I change the name of the new field to something like `pk->public_psa_type` in that case?
mpg commented 2025-11-06 05:35:45 -06:00 (Migrated from github.com)

Should I change the name of the new field to something like pk->public_psa_type in that case?

Yes, I think that would be a better name if we go with this approach!

> Should I change the name of the new field to something like `pk->public_psa_type` in that case? Yes, I think that would be a better name if we go with this approach!
gilles-peskine-arm (Migrated from github.com) reviewed 2025-11-06 10:14:01 -06:00
gilles-peskine-arm (Migrated from github.com) commented 2025-11-06 10:14:01 -06:00

I confirm. Since 3.6.0, there are no cases where psa_reset_key_attributes() does anything more than memset(0).

I confirm. Since 3.6.0, there are no cases where `psa_reset_key_attributes()` does anything more than `memset(0)`.
gilles-peskine-arm commented 2025-11-06 10:18:35 -06:00 (Migrated from github.com)

Well, if I may answer my own question: what if we take the existing test functions for parsing and add a mandatory parameter for "expected key type" (or just a check if the function is already specialised for one type)?

I don't think we need even that. I would suggest, rather, that every parsing function should call a test function pk_exercise_key(), and that function should make some consistency checks which include validating the psa_type field against the content (and complains if it sees a type or a content format that it doesn't know about).

Dream: we store all keys (public or private) in priv_id, then we don't need a separate psa_type field.

I think we should always have psa_type. It's a straightfoward way to know what the context contains. It costs very little memory and it avoids having to make a client-server call each time we want the type when PSA is client-server. Even when PSA is local, it avoids having to allocate an attribute structure for a call to psa_get_key_attributes. psa_type should be 0 only for a blank context.

> Well, if I may answer my own question: what if we take the existing test functions for parsing and add a mandatory parameter for "expected key type" (or just a check if the function is already specialised for one type)? I don't think we need even that. I would suggest, rather, that every parsing function should call a test function `pk_exercise_key()`, and that function should make some consistency checks which include validating the `psa_type` field against the content (and complains if it sees a type or a content format that it doesn't know about). > Dream: we store all keys (public or private) in priv_id, then we don't need a separate psa_type field. I think we should always have `psa_type`. It's a straightfoward way to know what the context contains. It costs very little memory and it avoids having to make a client-server call each time we want the type when PSA is client-server. Even when PSA is local, it avoids having to allocate an attribute structure for a call to `psa_get_key_attributes`. `psa_type` should be 0 only for a blank context.
mpg commented 2025-11-12 05:39:21 -06:00 (Migrated from github.com)

I would suggest, rather, that every parsing function should call a test function pk_exercise_key(), and that function should make some consistency checks which include validating the psa_type field against the content (and complains if it sees a type or a content format that it doesn't know about).

I like that approach. Then actually every test function that's about a key creation method (not just parsing, also wrapping or copying from psa) can call this "consistency checks" function, which also serves as executable documentation for the invariants of a populated pk_context structure.

However, this might be a bit more work and I'd like to avoid delaying this PR, as there are other tasks that depend on it. So here's my suggestion:

  1. In this PR: always set psa_type, using the approach "from the bottom" favoured by myself and Valerio, but don't expand the testing beyond what's already in this PR (ie, we're confident this is correct now, but might not be fully confident this will remain correct when we add new key types).
  2. In a follow-up PR, create a "consistency checks" function in test_suite_pk.function and use it in all existing test functions that are about parsing, or other methods of key creation (ie, future-proof our testing).
> I would suggest, rather, that every parsing function should call a test function `pk_exercise_key()`, and that function should make some consistency checks which include validating the `psa_type` field against the content (and complains if it sees a type or a content format that it doesn't know about). I like that approach. Then actually every test function that's about a key creation method (not just parsing, also wrapping or copying from psa) can call this "consistency checks" function, which also serves as executable documentation for the invariants of a populated `pk_context` structure. However, this might be a bit more work and I'd like to avoid delaying this PR, as there are other tasks that depend on it. So here's my suggestion: 1. In this PR: always set `psa_type`, using the approach "from the bottom" favoured by [myself](https://github.com/Mbed-TLS/TF-PSA-Crypto/pull/532#issuecomment-3490688329) and [Valerio](https://github.com/Mbed-TLS/TF-PSA-Crypto/pull/532#issuecomment-3491235988), but don't expand the testing beyond what's already in this PR (ie, we're confident this is correct now, but might not be fully confident this will remain correct when we add new key types). 2. In a follow-up PR, create a "consistency checks" function in `test_suite_pk.function` and use it in all existing test functions that are about parsing, or other methods of key creation (ie, future-proof our testing).
davidhorstmann-arm (Migrated from github.com) reviewed 2025-11-19 04:54:34 -06:00
@ -750,1 +746,3 @@
* - 0 to 1 bytes of leading 0 due to the sign bit.
* is at most 3 bytes. The length of the encoding is overestimated as follows:
*
* - Take int(bits / 8) as the number of full bytes taken by the value.
davidhorstmann-arm (Migrated from github.com) commented 2025-11-19 04:54:33 -06:00

Nullified by other changes (the old formula was correct).

Nullified by other changes (the old formula was correct).
davidhorstmann-arm (Migrated from github.com) reviewed 2025-11-19 08:29:28 -06:00
davidhorstmann-arm (Migrated from github.com) commented 2025-11-19 08:29:28 -06:00

If we keep this solution

In the end, we did not, so resolving this.

> If we keep this solution In the end, we did not, so resolving this.
davidhorstmann-arm commented 2025-11-19 08:37:00 -06:00 (Migrated from github.com)

I believe I have addressed everything here, it should be ready for another round of review.

One thing I am unsure of the approach on is this: The function mbedtls_pk_ecc_set_pubkey() is used in 2 situations:

  1. To set the public key when parsing a plain public key. In this case we want to update pk->psa_type to PSA_KEY_TYPE_ECC_PUBLIC_KEY(family)
  2. To populate the public key part of an existing key pair in an extra post-parsing step. In this case we want to leave pk->psa_type as it is, since it is set already by mbedtls_pk_ecc_set_key() to PSA_KEY_TYPE_ECC_KEY_PAIR(family)

I have solved this by adding a boolean parameter is_keypair to mbedtls_pk_ecc_set_pubkey() (allowed as it is internal-only) but reviewers may disagree as to whether this is a suitably neat solution.

I believe I have addressed everything here, it should be ready for another round of review. One thing I am unsure of the approach on is this: The function `mbedtls_pk_ecc_set_pubkey()` is used in 2 situations: 1. To set the public key when parsing a plain public key. In this case we want to update `pk->psa_type` to `PSA_KEY_TYPE_ECC_PUBLIC_KEY(family)` 2. To populate the public key part of an existing key pair in an extra post-parsing step. In this case we want to leave `pk->psa_type` as it is, since it is set already by `mbedtls_pk_ecc_set_key()` to `PSA_KEY_TYPE_ECC_KEY_PAIR(family)` I have solved this by adding a boolean parameter `is_keypair` to `mbedtls_pk_ecc_set_pubkey()` (allowed as it is internal-only) but reviewers may disagree as to whether this is a suitably neat solution.
mpg (Migrated from github.com) reviewed 2025-11-20 06:09:42 -06:00
mpg (Migrated from github.com) left a comment
No description provided.
LGTM, thanks!
valeriosetti (Migrated from github.com) requested changes 2025-11-20 07:27:02 -06:00
valeriosetti (Migrated from github.com) left a comment

A part from really 2 minor suggestions, I'm not super happy about the is_keypair parameter in mbedtls_pk_ecc_set_pubkey so I proposed an alternative solution (through a patch). Let me know what you think about it?

A part from really 2 minor suggestions, I'm not super happy about the `is_keypair` parameter in `mbedtls_pk_ecc_set_pubkey` so I proposed an alternative solution (through a patch). Let me know what you think about it?
@ -163,6 +165,16 @@ int mbedtls_pk_ecc_set_pubkey(mbedtls_pk_context *pk, const unsigned char *pub,
return MBEDTLS_ERR_PK_INVALID_PUBKEY;
valeriosetti (Migrated from github.com) commented 2025-11-20 07:24:36 -06:00

I'm not a huge fan of the addition of the is_keypair parameter. It caused changes in drivers (ex: pkparse) and test code and IMO it's not strictly necessary. Similar scenarios are already handled in other functions, for example:

  • mbedtls_pk_ecc_set_group here
  • mbedtls_pk_rsa_set_key here

I know that in this case mbedtls_pk_ecc_set_key set the key-pair key type while mbedtls_pk_ecc_set_pubkey tries to set the public key type and that the 2 are not the same, but wouldn't this patch pk_ecc.c.patch do the same without extra is_keypair parameter?

I'm not a huge fan of the addition of the `is_keypair` parameter. It caused changes in drivers (ex: pkparse) and test code and IMO it's not strictly necessary. Similar scenarios are already handled in other functions, for example: - `mbedtls_pk_ecc_set_group` [here](https://github.com/Mbed-TLS/TF-PSA-Crypto/blob/19f22a994da0f8980f35bb6beec7461ed1fb6b7a/drivers/builtin/src/pk_ecc.c#L26) - `mbedtls_pk_rsa_set_key` [here](https://github.com/Mbed-TLS/TF-PSA-Crypto/blob/19f22a994da0f8980f35bb6beec7461ed1fb6b7a/drivers/builtin/src/pk_rsa.c#L46) I know that in this case `mbedtls_pk_ecc_set_key` set the key-pair key type while `mbedtls_pk_ecc_set_pubkey` tries to set the public key type and that the 2 are not the same, but wouldn't this patch [pk_ecc.c.patch](https://github.com/user-attachments/files/23655189/pk_ecc.c.patch) do the same without extra `is_keypair` parameter?
@ -134,6 +134,11 @@ typedef struct mbedtls_pk_context {
/* Public key information. */
valeriosetti (Migrated from github.com) commented 2025-11-20 06:57:01 -06:00

Perhaps it's just me not reading it properly, but the way it's written now seem to suggest that there are other possibilities a part from public keys to get a PK context which is not backed by a PSA key, but this is not correct.
I would have rephrased it as:

     * Note: Valid even for public keys which are not backed by a PSA key */

This is really minor though and perhaps I overthought about it, so if you think this is not important feel free to ignore it :)

Perhaps it's just me not reading it properly, but the way it's written now seem to suggest that there are other possibilities a part from public keys to get a PK context which is not backed by a PSA key, but this is not correct. I would have rephrased it as: ```suggestion * Note: Valid even for public keys which are not backed by a PSA key */ ``` This is really minor though and perhaps I overthought about it, so if you think this is not important feel free to ignore it :)
valeriosetti (Migrated from github.com) commented 2025-11-20 06:50:56 -06:00

While reading about this fix, I checked also the line below and it says 7 bytes for the public exponent, but then above it's publicExponent INTEGER, -- 32-bit. The two don't match IMO. Wdyt?

While reading about this fix, I checked also the line below and it says `7 bytes for the public exponent`, but then above it's `publicExponent INTEGER, -- 32-bit`. The two don't match IMO. Wdyt?
valeriosetti (Migrated from github.com) reviewed 2025-11-20 10:23:05 -06:00
@ -163,6 +165,16 @@ int mbedtls_pk_ecc_set_pubkey(mbedtls_pk_context *pk, const unsigned char *pub,
return MBEDTLS_ERR_PK_INVALID_PUBKEY;
valeriosetti (Migrated from github.com) commented 2025-11-20 10:23:05 -06:00

On a second thought another thing that seems suspect to me is: why doesn't RSA keys need this fix?

On a second thought another thing that seems suspect to me is: why doesn't RSA keys need this fix?
mpg (Migrated from github.com) reviewed 2025-11-24 16:47:56 -06:00
@ -163,6 +165,16 @@ int mbedtls_pk_ecc_set_pubkey(mbedtls_pk_context *pk, const unsigned char *pub,
return MBEDTLS_ERR_PK_INVALID_PUBKEY;
mpg (Migrated from github.com) commented 2025-11-24 16:47:56 -06:00

I think it's because in RSA the keys are encoded differently: the private key encoding (n, e, d, p, q, dp, dq, qp) is naturally a superset of the public key encoding (n, e). While with ECC the private key can be encoded just on its own (d) and optionally the public part (Q) can be encoded, or it will be recomputed from the private key (Q = d * G). So for private keys we just parse d, and only then set Q as a 2nd step.

Not sure if that makes sense?

I think it's because in RSA the keys are encoded differently: the private key encoding (n, e, d, p, q, dp, dq, qp) is naturally a superset of the public key encoding (n, e). While with ECC the private key can be encoded just on its own (d) and optionally the public part (Q) can be encoded, or it will be recomputed from the private key (Q = d * G). So for private keys we just parse d, and only then set Q as a 2nd step. Not sure if that makes sense?
davidhorstmann-arm (Migrated from github.com) reviewed 2025-11-26 11:07:46 -06:00
davidhorstmann-arm (Migrated from github.com) commented 2025-11-26 11:07:46 -06:00

I believe this is correct because the 32-bit Integer is encoded in ASN.1 format, so we have:

  • 1 byte for the tag
  • 1 byte for the length
  • 5 bytes for the value (allowing for the possibility that it is unsigned 0xFFFFFFFF, which would require a leading zero byte because of signed-ness)
I believe this is correct because the 32-bit Integer is encoded in ASN.1 format, so we have: * 1 byte for the tag * 1 byte for the length * 5 bytes for the value (allowing for the possibility that it is unsigned 0xFFFFFFFF, which would require a leading zero byte because of signed-ness)
davidhorstmann-arm (Migrated from github.com) reviewed 2025-11-26 11:11:23 -06:00
@ -134,6 +134,11 @@ typedef struct mbedtls_pk_context {
/* Public key information. */
davidhorstmann-arm (Migrated from github.com) commented 2025-11-26 11:11:23 -06:00

I've reworded it with a comma for extra clarity 🙂

I've reworded it with a comma for extra clarity 🙂
valeriosetti (Migrated from github.com) reviewed 2025-11-27 02:40:04 -06:00
valeriosetti (Migrated from github.com) commented 2025-11-27 02:40:04 -06:00

Oh good point! Thanks!

Oh good point! Thanks!
valeriosetti (Migrated from github.com) requested changes 2025-11-27 02:51:41 -06:00
valeriosetti (Migrated from github.com) left a comment

It's almost OK to me. I only have 1 question about a dependency in some tests. Once this is resolved I will approve.

It's almost OK to me. I only have 1 question about a dependency in some tests. Once this is resolved I will approve.
@ -1497,0 +1525,4 @@
PK get type from parsed key, RSA, DER
depends_on:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC
pk_get_key_type_parse_key:"../framework/data_files/server1.key.der":PSA_KEY_TYPE_RSA_KEY_PAIR
valeriosetti (Migrated from github.com) commented 2025-11-27 02:50:08 -06:00

Initially I thought this was a duplicate of the test on line 1510, but then I realized that this uses files in DER format whereas the other is in PEM format. So now the question is instead: don't we need MBEDTLS_PEM_PARSE_C dependency on tests using PEM format? And also: why CI is not failing for this?

Initially I thought this was a duplicate of the test on line 1510, but then I realized that this uses files in DER format whereas the other is in PEM format. So now the question is instead: don't we need `MBEDTLS_PEM_PARSE_C` dependency on tests using PEM format? And also: why CI is not failing for this?
valeriosetti (Migrated from github.com) approved these changes 2025-11-28 07:36:51 -06:00
valeriosetti (Migrated from github.com) left a comment

Thanks for your patience in addressing all my comments. LGTM now!

Thanks for your patience in addressing all my comments. LGTM now!
mpg (Migrated from github.com) approved these changes 2025-12-01 05:20:47 -06:00
mpg (Migrated from github.com) left a comment

LGTM, thanks!

LGTM, thanks!
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-Mbed-TLS/TF-PSA-Crypto!532
No description provided.