Should a key derivation continue working after a verify failure?
gilles-peskine-arm opened this issue · comments
In a nutshell: if psa_key_derivation_verify_bytes
detects incorrect output (INVALID_SIGNATURE
condition), may this abort the operation?
(Everything about verify_bytes
here applies equally to verify_key
.)
Out-of-capacity state
Generally, when a function fails in a multipart operation, the multipart operation enters an error state.
The specification of psa_key_derivation_output_bytes
suggests, but does not clearly mandate a different behavior for PSA_ERROR_INSUFFICIENT_DATA
:
The operation’s capacity was less than output_length bytes. In this case, the following occurs:
- No output is written to the output buffer.
- The operation’s capacity is set to zero — subsequent calls to this function will not succeed, even with a smaller output buffer.
And in general:
If this function returns an error status other than
PSA_ERROR_INSUFFICIENT_DATA
, the operation enters an error state and must be aborted by callingpsa_key_derivation_abort()
.
The intent is that on an INSUFFICIENT_DATA
condition, the operation object should enter an out-of-capacity state. In that state, subsequent calls to functions that request more output (output_bytes
, output_key
, verify_bytes
, etc.) will return PSA_ERROR_INSUFFICIENT_DATA
, rather than PSA_ERROR_BAD_STATE
. However the specification does not explicitly state that it would be wrong for an implementation to return PSA_ERROR_BAD_STATE
for subsequent output calls in this scenario.
Proposed action: explicitly describe the out-of-capacity state and require that an INSUFFICIENT_DATA
condition enters it (absent other errors). Applies to psa_key_derivation_output_bytes
, psa_key_derivation_output_key
, psa_key_derivation_verify_bytes
, psa_key_derivation_verify_key
.
Note that there is an edge case of requesting 0 bytes when the remaining capacity is 0. Should this succeeds because the whole requested output can be produced? Or should this fail so as not to make a distinction between capacity-0 and capacity-exceeded? In other words, if the initial capacity is 1 then what happens with the following sequence of calls?
output_bytes(1)
→ successoutput_bytes(0)
→ success orINSUFFICIENT_DATA
?output_bytes(0)
→ success orINSUFFICIENT_DATA
?
Behavior after a failed verification
Generally, when a function fails in a multipart operation, the multipart operation enters an error state.
The specification of psa_key_derivation_verify_bytes
suggests, but does not actually mandate different behavior on an INVALID_SIGNATURE
condition:
PSA_ERROR_INVALID_SIGNATURE
The output of the key derivation operation does not match the value in expected_output.
(…)
If this function returns an error status other than
PSA_ERROR_INSUFFICIENT_DATA
orPSA_ERROR_INVALID_SIGNATURE
, the operation enters an error state and must be aborted by callingpsa_key_derivation_abort()
.
Based on this sentence, and on the code snippet that approximates the intended behavior of psa_key_derivation_verify_bytes
, it seems that the intent is that an INVALID_SIGNATURE
condition consumes the output, but does not affect the state of the object.
However, the specification does not state this clearly. It seems to me that an implementation could make the operation object enter an error state when it reaches an INVALID_SIGNATURE
condition.
Which behavior is desirable? I can see the appeal for an implementation to treat verify_bytes
as a wrapper around output_bytes
which does not otherwise affect the state of the object. However, this isn't quite right since a PSA_ERROR_NOT_PERMITTED
condition does enter an error state, and the permission requirements are different for output_bytes
and verify_bytes
. Furthermore, it's inconsistent with the fact that the specification does require putting the operation in an error state if psa_key_derivation_verify_key
fails to access the key to compare against (e.g. PSA_ERROR_INVALID_HANDLE
condition).
From a usage perspective, I can't think of a use case where it's desirable to keep the operation going after an INVALID_SIGNATURE
condition. It's somewhat uncommon to use the same inputs to both validate part of the output, and construct a key or other material with another part of the output. A typical case where it does happen is using the first half of the output of a password hashing function as a password hash, and the second half as a key encryption key). In this case, a verification failure indicates a wrong password, so the user is not authenticated and the putative key encryption key should not be used.
Proposed action: don't give any special treatment to PSA_ERROR_INVALID_SIGNATURE
. Thus it will cause the operation to enter the error state. Applies to psa_key_derivation_verify_bytes
, psa_key_derivation_verify_key
.
It's not clear to me whether the specification intended to mandate or allow implementations to preserve the operation's validity on INVALID_SIGNATURE
. So I'm not sure if this proposed action is merely restricting the permitted behaviors, or if it would be an incompatible change.
Proposed action: explicitly describe the out-of-capacity state and require that an
INSUFFICIENT_DATA
condition enters it (absent other errors). Applies topsa_key_derivation_output_bytes
,psa_key_derivation_output_key
,psa_key_derivation_verify_bytes
,psa_key_derivation_verify_key
.
I agree that this would be better than the currently ambiguous descriptions.
Note that there is an edge case of requesting 0 bytes when the remaining capacity is 0. Should this succeeds because the whole requested output can be produced? Or should this fail so as not to make a distinction between capacity-0 and capacity-exceeded? In other words, if the initial capacity is 1 then what happens with the following sequence of calls?
output_bytes(1)
→ successoutput_bytes(0)
→ success orINSUFFICIENT_DATA
?output_bytes(0)
→ success orINSUFFICIENT_DATA
?
I suggest that the out-of-capacity state is only entered after a request to output more than the available capacity. I.e. following an error return of INSUFFICIENT_DATA
to one of the above functions. Thus the above sequence will return SUCCESS
for all three calls, but the following should be observed:
output_bytes(1)
→ successoutput_bytes(1)
→INSUFFICIENT_DATA
(enter out-of-capacity state)output_bytes(0)
→INSUFFICIENT_DATA
Proposed action: don't give any special treatment to
PSA_ERROR_INVALID_SIGNATURE
. Thus it will cause the operation to enter the error state. Applies topsa_key_derivation_verify_bytes
,psa_key_derivation_verify_key
.It's not clear to me whether the specification intended to mandate or allow implementations to preserve the operation's validity on
INVALID_SIGNATURE
. So I'm not sure if this proposed action is merely restricting the permitted behaviors, or if it would be an incompatible change.
I'm not sure where this approach originated when we added these functions in v1.1, as it is not present in the Hash, MAC or AEAD multi-part operations, nor in the newly proposed interruptible signature operations (#107).
I agree that PSA_ERROR_INVALID_SIGNATURE
does not require special handling (no solid use case) here, and just complicates the specification and implementation.
I would like to second the proposal, and mandate that returning PSA_ERROR_INVALID_SIGNATURE
from either of these APIs, will also put the operation into the error state, which requires a call to psa_key_derivation_abort()
.
I suggest that the out-of-capacity state is only entered after a request to output more than the available capacity. I.e. following an error return of
INSUFFICIENT_DATA
to one of the above functions. Thus the above sequence will returnSUCCESS
for all three calls, but the following should be observed:
output_bytes(1)
→ successoutput_bytes(1)
→INSUFFICIENT_DATA
(enter out-of-capacity state)output_bytes(0)
→INSUFFICIENT_DATA
After further consideration - although this approach simplifies the programming model for the application developer, it demands some additional complexity in the implementation beyond tracking the remaining capacity. This only benefits an unlikely corner case of application usage, so a better balance here is favor the implementations with the following rules for key derivation output:
- A request to extract more data than the remaining capacity -
output_length > psa_key_derivation_get_capacity()
- fails withINSUFFICIENT_DATA
and sets the remaining capacity to zero. A subsequent call topsa_key_derivation_get_capacity()
returns0
. - If the remaining capacity is zero -
psa_key_derivation_get_capacity() == 0
- then it is implementation-defined whether a call to output zero bytes -output_length == 0
- returnsSUCCESS
orINSUFFICIENT_DATA
.
For applications that might be caught by the impdef behavior, it is easy to detect and avoid calling the output function when the request would be for zero bytes.
Since there are no objections, we'll go ahead and require implementations to enter an error state after PSA_ERROR_INVALID_SIGNATURE
.