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

Make COSE deserialization more robust #8

Merged
merged 2 commits into from
Nov 21, 2023

Conversation

robin-nitrokey
Copy link
Member

@robin-nitrokey robin-nitrokey commented Feb 16, 2023

Previously, COSE keys could only be deserialized if the map entries were in the correct order. With this patch, the deserialization is made more robust (and also more concise) by introducing a helper struct, RawPublicKey, that is used as an intermediate step during the deserialization. It also adds support for parsing keys without an algorithm field.

@robin-nitrokey robin-nitrokey marked this pull request as ready for review February 16, 2023 22:45
@daringer
Copy link
Member

lgtm, tests ran w/o issues

robin-nitrokey added a commit to Nitrokey/cosey that referenced this pull request Feb 27, 2023
Previously, COSE keys could only be deserialized if the map entries were
in the correct order. With this patch, the deserialization is made more
robust (and also more concise) by introducing a helper struct,
RawPublicKey, that is used as an intermediate step during the
deserialization. Also, we make the algorithm field optional to conform
with the spec.

This is a port from ctap-types:
    trussed-dev/ctap-types#8
trait PublicKeyConstants {
const KTY: Kty;
const ALG: Alg;
const CRV: Crv;
}

#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(into = "RawPublicKey")]
Copy link
Member

Choose a reason for hiding this comment

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

Why is there no with = "RawPublicKey" here?

Copy link
Member Author

Choose a reason for hiding this comment

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

There is no with argument for containers. Do you mean from/try_from? I chose to manually implement Deserialize to make error handling easier, but we can also add our own error type and use TryFrom.

@nickray
Copy link
Member

nickray commented Mar 8, 2023

@robin-nitrokey can you add more context to this? Did you run into a specific issue?

The reason we're currently not doing this is related to CTAP2 canonical CBOR encoding form. Now I agree that, while we need to be sure we emit "canonical" CBOR, it might be helpful to be less strict when receiving. Is there a kind of "non-exhaustive" issue? Generally, I thought that encoding/decoding these CTAP types was a matter of getting it right "once and for all".

}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(into = "RawPublicKey")]
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as above, wouldn't we want Deserialize via with = "RawPublicKey" too?

@robin-nitrokey
Copy link
Member Author

@nickray A user reported an issue with fido2luks that is fixed by this patch, see Nitrokey/nitrokey-3-firmware#177. Besides the order of map keys, the old implementation also requires the algorithm field that should be optional.

@robin-nitrokey
Copy link
Member Author

I wasn’t aware that the standard also discourages accepting standard but non-canonical CBOR. If you want to keep this behavior, the approach in this PR could still be used to simplify the serde trait implementations.

@sosthene-nitrokey
Copy link
Contributor

When reviewing this PR i checked the COSE spec and it says that canonical encoding rules applies do not apply to public keys.

https://datatracker.ietf.org/doc/html/rfc8152#section-14

  • The restriction applies to the encoding of the Sig_structure, the
    Enc_structure, and the MAC_structure.

But the FIDO2 spec appears to be more constrained than that.

@nickray
Copy link
Member

nickray commented Apr 6, 2023

Yes, you need to distinguish upstream COSE canonical encoding and "CTAP2 canonical CBOR encoding form" unfortunately. I think it's better to stick with the strict (applicable) standard and consider issues arising from this a bug - unless you are strongly of a different opinion.

@robin-nitrokey
Copy link
Member Author

robin-nitrokey commented Nov 15, 2023

I’ve rebased this PR and rewrote it based on the previous discussion. This means we still require canonical encoding (i. e. correct order), but also keep the simplified de-/serialization with RawPublicKey and the support for optional algorithms.

@robin-nitrokey
Copy link
Member Author

I found one final problem during the implementation: The CTAP spec requires us to ignore unknown map keys. The documentation for MapAccess does not require next_key to be followed by next_value, so I assumed that just calling next_key until we see a key that we know should work.

Yet it does not work – cbor_smol requires a next_value call after each next_key. But we cannot call next_value because we do not know which type this unknown value has, and cbor_smol requires the type to be known at compile time.

So I think we have to stick with the current implementation here, failing for unknown keys until we have found all keys we want.

@sosthene-nitrokey
Copy link
Contributor

So we'd need to implement support for deserialize_ignored_any in cbor-smol ?

@sosthene-nitrokey
Copy link
Contributor

There is already a pull request for it: trussed-dev/cbor-smol#5

@robin-nitrokey
Copy link
Member Author

robin-nitrokey commented Nov 16, 2023

Yes, with that we should be able to avoid the problem. But we could also do that separately as this implementation does not change the existing (working) behavior AFAIS.

@sosthene-nitrokey
Copy link
Contributor

sosthene-nitrokey commented Nov 16, 2023

A PR that works on top of the latest Nitrokey/ctap-types main: trussed-dev/cbor-smol#6

@robin-nitrokey
Copy link
Member Author

Nice! Here is an improved implementation using deserialize_any based on this PR: #22

This patch introduces the RawPublicKey helper type that takes care of
serialization and deserialization of the key type.  The *PublicKey
structs now only need to check if all required fields are present and
have the correct value.  It also adds extensive tests to make sure that
serialization and deserialization work correctly.
The algorithm field is optional, see RFC 8152 § 7:

   COSE_Key = {
       1 => tstr / int,          ; kty
       ? 2 => bstr,              ; kid
       ? 3 => tstr / int,        ; alg
       ? 4 => [+ (tstr / int) ], ; key_ops
       ? 5 => bstr,              ; Base IV
       * label => values
   }

   alg:  This parameter is used to restrict the algorithm that is used
      with the key.  If this parameter is present in the key structure,
      the application MUST verify that this algorithm matches the
      algorithm for which the key is being used.
@robin-nitrokey robin-nitrokey merged commit 2a563c8 into trussed-dev:main Nov 21, 2023
4 checks passed
robin-nitrokey added a commit to Nitrokey/cosey that referenced this pull request May 2, 2024
This patch introduces the RawPublicKey helper type that takes care of
serialization and deserialization of the key type.  The *PublicKey
structs now only need to check if all required fields are present and
have the correct value.  It also adds extensive tests to make sure that
serialization and deserialization work correctly.

This patch is ported from: trussed-dev/ctap-types#8
robin-nitrokey added a commit to Nitrokey/cosey that referenced this pull request May 2, 2024
This patch introduces the RawPublicKey helper type that takes care of
serialization and deserialization of the key type.  The *PublicKey
structs now only need to check if all required fields are present and
have the correct value.  It also adds extensive tests to make sure that
serialization and deserialization work correctly.

This patch is ported from: trussed-dev/ctap-types#8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants