-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Study: pk: handle pk_ec()
safely
#7570
Comments
I'm not familiar with the recent evolution of ECP light. Can you please link to how those
That would seem to defeat the purpose of having another copy of the key, but I don't know what the purpose of that copy is. |
Good question! The primary motivation for having this copy is to incrementally prepare for a case where it will no longer be copy, but the only way the key material is held. This is to support builds of PK with ECC support but without So, after a bit more work, we'd like to get to a point where things look like:
So, with
Note: this is something we can change. For example, we could introduce a new internal function that only gives a read-only pointer and doesn't invalidate the
Also, I'd really like to mark |
Note: initially I didn't realise that the existence of |
The prototype of that function is: |
Indeed |
Ok, so it seems to me that we have 2 options here:
Wdyt? |
Doesn't this completely defeat the purpose of having a PSA-friendly copy? It seems to me (but I've only looked at #7073 superficially) that the PSA-friendly copy is effectively a cache, and it's pointless to have a cache that needs to be refreshed each time.
I'm not sure what that means exactly. But so far I think we can't change how we read the pk context when When ECP-light is enabled but not ECP-full, we can either add a PSA copy of the context, or treat the context as a PSA opaque context with |
Ouch, you're right. That kills the whole "dirty bit" approach. And as Gilles says, then it makes little sense to have a cache if it needs refreshing each time. But again, if we take a step back, having a cache is not the ultimate goal here: the ultimate goal is to have something that works when we remove not only So, I think indeed we should abandon this caching idea, and re-focus on the larger goal: have something that works when
I don't think treating the context as opaque is the way to go here. As much as possible, applications should work the same regardless of whether things are provided by drivers or the built-in implementation. We have to make an exception for Also, opaque contexts only support private keys for now, and store it in a PSA key slot. I'm not comfortable starting to store all public keys in a PSA key slot, as there are functions that create a large number a PK context holding public keys (for example, |
Ok, that makes sense. Then we have three cases:
|
Indeed. That's where we want to get. Now, we need to find an incremental path towards that. We're not ready to remove I think probably the easiest way, considering the work that's been done so far, would be to temporarily have a PSA-friendly copy in addition to the @valeriosetti Do you think that works? The first step would be to modify #7554 so that @gilles-peskine-arm Do you have an objection to having temporary states where a PK context can hold two copies of the key material, provided we pay attention to keeping everything functionally correct at each step, and the only problem is memory inefficiency? Of course we'd try to get out of that temporary state ASAP. |
Ok, but then it still needs to be a private function, otherwise we now know that the PSA context isn't usable. And it can only exist if ECP_LIGHT is enabled, otherwise there's no ECP context to extract. So that intermediate stage is what I describe here, right? (Not sure if we're in violent agreement or if I'm missing something) |
Yes, that should not be a problem. I just pushed my proposal on #7554 |
Ok, I'm sorry bot not everything is clear in my mind yet, so I have trouble saying whether we're all in agreement or not. Before we discuss what intermediate states are desirable/convenient, I'd like to think again about what we want the final state to look like. Using a modification from this message where I disregard the case with no ECC at all as uninteresting, but instead split the first point depending on whether
There's also Opaque keys (private only for now, exist in cases 2 and 3), which store the key in a PSA key slot, that functions in We want to share as much code as possible between cases 2 and 3 and Opaque keys. What I had in mind before yesterday was that when Now that I write it our explicitly, this is quite suboptimal, it would be better to not use the PSA-friendly fields in case 2 for non-opaque keys - having them while they need updating before each use wastes RAM and is just begging for subtle bugs that would be easy to miss in testing. So, yesterday I change my mind from this being the final state to being an intermediate state. (And now I'm not sure I'm happy with it even as an intermediate state.) Instead, I think what we should do is that we should have internal functions in I'm now thinking this can probably be achieved directly without an intermediate step. |
I think where we probably want an intermediate state is uses of
So, compared to the current state of #7554, 2 is OK, but for 1 I'd like to add a clear distinction between read-only access and read-write access. |
I agree. The presence of the extra PSA fields in case 2 has been bothering me ever since you pointed out the problem with
Indeed I think we have to do that as long as a 3.x-compatible |
Actually following Gilles' proposal I think that for backward compatibility as long as the user defines |
Using the same cases as in my previous comment, trying to make it explicit what it would mean for each field:
|
Trying to flesh that out with the example of the sign wrapper: #if defined(MBEDTLS_USE_PSA_CRYPTO)
/* internal function used by wrappers below */
int ecdsa_sign_psa(psa_key_id_t id, ...) {
// do it by calling psa_sign_hash() + format conversion
}
/* now the actual wrappers to be registered in pk_info structures */
// opaque (cases 2 and 3) + case 3 non-opaque
int ecdsa_sign_wrapper(mbedtls_pk_context_t pk, ... ) {
return ecdsa_sign_psa(pk->priv_id, ... );
}
// case 2 non-opaque
#if defined(MBEDTLS_ECP_C)
int ecdsa_sign_wrapper(mbedtls_pk_context_t pk, ...) {
const mbedtls_ecp_keypair *ecp_kp = pk->pk_ctx;
psa_key_id_t psa_key;
// populate psa_key from ecp_kp
ret = ecdsa_sign_psa(psa_key, ...);
// cleanup: destroy psa_key etc.
return ret;
}
#endif
#else
// legacy non-PSA wrapper
#endif |
For a public operation - if we choose to populate #if defined(MBEDTLS_USE_PSA_CRYPTO)
/* internal functon used by wrappers below */
int ecdsa_verify_psa(const uint8_t *pub_raw, ...) {
// do it by doing format conversion + calling psa_verify_hash()
}
/* now the actual wrappers to be registered in pk_info structures */
// opaque (cases 2 and 3) + case 3 non-opaque
int ecdsa_verify_wrapper(mbedtls_pk_context_t pk, ... ) {
return ecdsa_verify_psa(pk->pub_raw, ... );
}
// case 2 non-opaque
#if defined(MBEDTLS_ECP_C)
int ecdsa_verify_wrapper(mbedtls_pk_context_t pk, ...) {
const mbedtls_ecp_keypair *ecp_kp = pk->pk_ctx;
uint8_t pub_raw[MAX_SIZE];
// populate pub_raw from ecp_kp
ret = ecdsa_verify_psa(pub_raw, ...);
// cleanup?
return ret;
}
#endif
#else
// legacy non-PSA wrapper
#endif For public operations if we choose not to populate #if defined(MBEDTLS_USE_PSA_CRYPTO)
/* internal functon used by wrappers below */
int ecdsa_verify_psa(psa_key_id_t key, ...) {
// do it by doing format conversion + calling psa_verify_hash()
}
/* now the actual wrappers to be registered in pk_info structures */
// opaque (cases 2 and 3)
int ecdsa_verify_wrapper(mbedtls_pk_context_t pk, ... ) {
return ecdsa_verify_psa(pk->pub_raw, ... );
}
#if defined(MBEDTLS_ECP_C)
// case 2 non-opaque
int ecdsa_verify_wrapper(mbedtls_pk_context_t pk, ...) {
const mbedtls_ecp_keypair *ecp_kp = pk->pk_ctx;
psa_key_id_t psa_key;
// populate psa_key from ecp_kp
ret = ecdsa_verify_psa(psa_key, ...);
// cleanup: destroy psa_key
return ret;
}
#else /* ECP_C */
// case 3 non-opaque
int ecdsa_verify_wrapper(mbedtls_pk_context_t pk, ...) {
psa_key_id_t pub_key;
if (pk->priv_id != PSA_KEY_NONE) {
return ecdsa_verify_psa(pk->priv_id, ...);
}
psa_key_id_t psa_key;
// populate psa_key from pk->pub_raw etc.
ret = ecdsa_verify_psa(psa_key, ...);
// cleanup: destroy psa_key
return ret;
}
#endif /* ECP_C */
#else /* USE_PSA */
// legacy non-PSA wrapper
#endif /* USE_PSA */ Note: there are two things we want to minimize:
|
Sorry for the verbosity, I felt I needed to spell things out as precisely as possible first to make sure things were clear in my mind, and hopefully this might help prevent misunderstandings as well. I want to clarify a secondary goal in this: currently Opaque (PSA-held) keys have a number of limitations (can't write out the key even if the policy allows export, can't do |
We seem to have an agreement on what the final state should be:
The case with Based on what's been started so far, I think we could divide the work up as follows:
In the above, it is always assumed that |
pk_ec()
safelypk_ec()
safely
Following #7073 and #7074, when both
ECP_LIGHT
andUSE_PSA
are defined, PK contexts for (non-opaque) ECC keys store two copies of the key material: one in andecp_keypair
structure, one in the newpriv_id
/pub_raw
fields.However, the API contains a problematic function:
mbedtls_pk_ec()
, which gives user direct access to theecp_keypair
structure - worse, this access is read and write. If the user modifies the content of theecp_keypair
, then the new fieldspriv_id
/pub_raw
& Co need to be updated before each operation. OTOH, if theecp_keypair
object is not modified, no updating is needed.I suggest the following course of action:
mbedtls_pk_ec()
depend onECP_C
again - that's the dependency in the last release so we don't it to keep it available in more builds than that.dirty
topk_context
, present whenUSE_PSA
andECP_C
are defined. This filed is initialized to 0, and whenmbedtls_pk_ec()
is used, it it set to 1 and remains set until the context is freed.mbedtls_pk_ensure_priv_id()
/pub_raw()
(or better names) that check ifdirty
is set, and if so, update the material inpriv_id
(resp.pub_raw
& Co). When!USE_PSA || !ECP_C
, this function does nothing and is static inline.priv_id
(except Opaque keys) orpub_raw
.mbedtls_pk_update_public_key_from_keypair()
(and thepriv_id
equivalent) from `test_suite_pk.function as they are no longer needed.The text was updated successfully, but these errors were encountered: