laurencelundblade / t_cose

Commercial quality COSE_Sign1 implementation in C for constrained environment. Works with MbtedTLS and OpenSSL Crypto.
BSD 3-Clause "New" or "Revised" License
26 stars 24 forks source link

Allow access of protected header, payload, and signature as byte strings #91

Open letmaik opened 2 years ago

letmaik commented 2 years ago

Being able to access protected header, payload, and signature bytes is useful for implementing things like countersignature validation in external libraries.

For inspiration, this is how go-cose exposes headers both decoded and as bytes: https://pkg.go.dev/github.com/veraison/go-cose#Headers And here the overall structure: https://pkg.go.dev/github.com/veraison/go-cose#Sign1Message

laurencelundblade commented 2 years ago

My ability to read go is limited, but I did look at it.

I think what is there in https://github.com/laurencelundblade/t_cose/blob/dev/inc/t_cose/t_cose_parameters.h is still fairly reasonable. I was thinking that counter signatures would be an important test too. While we eventually want that support built right into t_cose, I think what is there would allow them. There are the header read and write callbacks that allow headers to be arbitrarily complex.

Go is intrinsically object-oriented and garbage collected, so t_cose can't ever be the same as an implementation in go without using a lot more memory for both code and data.

Is there anything in particular you want to suggest?

letmaik commented 2 years ago

There is no issue accessing a countersignature (or other int-labelled parameters). The issue rather is that currently the protected header cannot be accessed as a buffer, but only decoded. The signature cannot be accessed at all: https://github.com/laurencelundblade/t_cose/blob/66beb64e298a863ad4ea42c0da5dd1d101672d12/inc/t_cose/t_cose_sign1_verify.h#L317-L320

Validating COSE countersignatures for example requires constructing this:

         Countersign_structure = [
           context : "CounterSignature" / "CounterSignature0" /
                     "CounterSignatureV2" / "CounterSignature0V2" /,
           body_protected : empty_or_serialized_map,
           ? sign_protected : empty_or_serialized_map,
           external_aad : bstr,
           payload : bstr,
           ? other_fields : [ + bstr ]
         ]

other_fields would contain the signature. So the inputs here are the protected header bytes, the payload bytes, and the signature bytes of the original message, plus inputs from the countersignature itself (like its own protected header).

I think t_cose should allow this kind of validation to be implemented in a third-party library.

laurencelundblade commented 2 years ago

I see. Thanks for point that out.

Part of a solution might be this change that is in-the-works for QCBOR: https://github.com/laurencelundblade/QCBOR/pull/117, but that still wouldn't make the payload and signature available to header processors.

But rather than complicate the custom header processor API, I think it's better if implementors of counter signatures implemented a signer/verifier as in a struct t_cose_signature_sign with its callbacks. That does pass all the right stuff. It is run-time linkable to the rest of t_cose.

The one weakness of it is that the crypto adaptor layer is not public, so the implementor of counter signatures would have to replicate it or do something different.

It would also be good to just add counter signatures to t_cose 2.0. Maybe sooner rather than later to validate the 2.0 design better.

letmaik commented 2 years ago

https://github.com/laurencelundblade/QCBOR/pull/117 wouldn't be needed for this since COSE bstr-wraps all relevant parts.

I had a look at the struct t_cose_signature_sign in the dev branch. Some observations:

Since countersignatures sign an existing signature, the current interface wouldn't quite work. The signature bytes aren't available, and in the case of COSE_Sign there would have to be a way to select which COSE_Signature to countersign (I don't think countersigning a signature within a COSE_Sign is a common usecase, but COSE_Sign1 may be a use case). The I-D says "The countersignature needs to be treated as a separate operation from the initial operation even if it is applied by the same user".

Countersignatures are either stored in the unprotected header or somewhere externally. I think the current API is mostly to support multiple signatures for COSE_Sign.

There will be new types of countersignatures apart from https://datatracker.ietf.org/doc/html/draft-ietf-cose-countersign-06. For example, there is https://datatracker.ietf.org/doc/draft-birkholz-scitt-receipts/ which defines a COSE countersignature based on Merkle trees, using a different data type and stored in a different header parameter, but relying on the same components as regular countersignatures: protected header bytes, payload bytes, signature bytes.

For reference, see how the .NET preview COSE library is extended to expose those bits: https://github.com/dotnet/runtime/issues/72611

laurencelundblade commented 2 years ago

Yeah, that the counter signature is inserted far earlier in the message than the thing its counter signing kind of up ends things for the current t_cose. I think the current API can work for a counter signature in a COSE_Sign, but probably nothing else.

The problem is how to design this without losing the code size, simplicity and low stack-only memory use. Dunno yet....