dotnet/runtime

COSE_Sign1 messages (single signer) can be read, validated, and created

bartonjs opened this issue ยท 11 comments

Implement COSE Signing, IETF RFC 8152, section 4

Minimum Viable Product - Single Signer

  • Read and expose metadata for tagged and untagged COSE_Sign1 messages
  • Validate signature with both embedded and detached content, from both formats.
  • Create new embedded- and detached- content signature.

This minimum viable product can be shipped as a NuGet package for verification by early adopters before continuing into the multiple signers scope.

Things that won't be covered on the MVP:

  • Validation of Critical headers
  • A model to interact with Counter Signatures headers.

We're using this in the wild with the EU Covid certificates; is this feature something that could figure in the coming 6-12 months? :-)

ref: https://github.com/ehn-dcc-development/hcert-spec/blob/main/hcert_spec.md

@ryanbnl We originally thought we had a highly engaged partner, then they turned out to need CBOR and one data structure from COSE, but not COSE itself, so we put this on the back burner.

The CBOR reader and writer are available: https://www.nuget.org/packages/System.Formats.Cbor.

I can't say how this will stack up against other things we want for 7, given that you're the first request for it. It's probably not too much work (I forget how big the spec is) to make it work in your own library code, given System.Formats.Cbor and the built-in System.Security.Cryptography types.

The harder problem for us will be the classic one: coming up with public API that we're happy with. (So far we've done not so great with SignedXml or SignedCms... third time's the charm?)

@bartonjs Would it help if I made myself the second request? We're planning to use COSE_Sign1 as the signature format for an upcoming standard. In our C# prototype we're currently making do with the library at https://github.com/cose-wg/COSE-csharp.

Thanks for replying @bartonjs :-)

The context of this is the EU Covid Vaccination project - we've used COSE/CBOR as part of the HCERT specifications. You can find the info here:

https://github.com/ehn-dcc-development/hcert-spec

We will start to see this implemented more broadly, especially as commercial players come on to the market. For now we are using the community implementations - so we're not blocked - but it would be great to have an official implementation. Especially given that you can share a single .Net implementation over apps (Xamarin), backend (asp.net etc) and web frontend (Blazor).

The .Net implementation is here (warning: it's very much proof-of-concept code to work as a quick example):

https://github.com/ehn-dcc-development/hcert-dotnet

SignedCms: the API isn't fantastic, but there's a lot of value having good crypto support on our platform. Between the Microsoft implementations, BouncyCastle ports and libsodium wrappers there's a lot of good, but I keep hearing from the crypto experts that Go and Python are amazing because of their crypto support.

Would it help if I made myself the second request?

Certainly doesn't hurt ๐Ÿ˜„.

Putting a thumbs-up on the top post for the issue is helpful as it gives us a quick way to compare (however inacurately) the demand for one thing over another.

Could be interesting also at https://github.com/lumoin/Verifiable. Partially relatd to things @ryanbnl mentioned. So my thumb for CBOR too. :)

(Also that lib is still being established a few days in case someone goes to click around.)

@ryanbnl

SignedCms: the API isn't fantastic, but there's a lot of value having good crypto support on our platform. Between the Microsoft implementations, BouncyCastle ports and libsodium wrappers there's a lot of good, but I keep hearing from the crypto experts that Go and Python are amazing because of their crypto support.

Maybe they drop to Rust libraries undernath. Rust ecosystem seem to be where the action is. :)

I just wrote a simple wrapper to BouncyCastle, NSec and .NET libraries in https://github.com/lumoin/Verifiable. Basically the idea is an extension method with delegates (that can take Span<byte>) that abstract the different forms of the libraries and allow adding more libraries agnostic to these core libraries.

You can take a look if the idea is useful. This is like alpha stuff still (and other design that occurred to me could be something like Func<Delegate, Delegate>). Maybe this isn't the place to design. :) Sometime next week things ought to be put in place, readmes better written etc.

Nice I'll have a look! I expect this to be a growth area in the future - particularly in Europe, but also because we're seeing a lot more systems in the wild that are built on crypto. I'm certainly extremely thankful to have spent the last year and a half building cool stuff that ships to millions of people with it :)

Growth surely as things are legislated -- and .NET is lacking here a bit. :)

The crypto related is at https://github.com/lumoin/Verifiable/blob/main/test/Verifiable.Tests/CryptographicCrossTests.cs. But a noted, I move things around, add comments and that sort of things next week. I think it boils down to looking at if this idea of abstracting could work for you too (I probably add a few tests that make the library specific plumbing go unvisible too, as the idea is ultimately to rely on general signature funcs that delegate to the libraries). :)

#32080 is a design/implementation detail of this item. That issue has been closed and captured here.

COSE_Key comes from IETF RFC 8152, section 7.

Some specs are already known that use COSE Key encodings that don't use all of COSE, so at a high level we should support going from AsymmetricAlgorithm to an encoded COSE_Key (byte[] / write to a Span<byte>), and going from a COSE_Key to AsymmetricAlgorithm types.

In order to maintain type safety, and reduce the risk of over-casting by callers, it probably should be something like

partial class CoseKey
{
    public CoseKeyType KeyType { get; }
    // For symmetry with certs, returns null when the key type isn't an RSA type.
    public RSA? GetRSAKey() => throw null;
    public static bool TryParseRSAKey(ReadOnlySpan<byte> encodedKey, out RSA key) => throw null;
    public static byte[] EncodeKey(RSA key);
}

Repeat for ECDsa and ECDiffieHellman. Technically it could also be defined for HMAC and symmetric key wrap, but it seems less valuable.

(Reading is probably needed before writing)