- Add dotnet7 nuget feed (https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet7/nuget/v3/index.json) in your Package Sources.
- Search and install System.Security.Cryptography.Cose. Don't forget to check the "Include prerelease" checkbox.
using System.Security.Cryptography;
using System.Security.Cryptography.Cose;
using System.Text;
byte[] content = Encoding.UTF8.GetBytes("This is the content.");
ECDsa myKey = ECDsa.Create();
byte[] encodedMsg = CoseSign1Message.Sign(content, myKey, HashAlgorithmName.SHA256);
// You have a CBOR encoded message signed with ECDSA w/SHA256 (ES256).
// that you can share with anyone and they can verify in with your public key.
// Having a CBOR encoded message (encodedMsg) a key (myKey) and a content in case of detached content (myContent),
// you decode it and then verify it using the key and the content in case of detached content.
CoseSign1Message msg = CoseMessage.DecodeSign1(encodedMsg);
// The COSE message doesn't carry the content (required for verification), it needs to be supplied.
bool isContentDetached = !msg.Content.HasValue;
bool verified;
if (isContentDetached)
{
verified = msg.Verify(myKey, myContent);
}
else
{
verified = msg.Verify(myKey);
}
if (!verified)
{
throw new Exception("Failed to verify COSE message.");
}
Console.WriteLine("Message verified!");
Console.WriteLine(isContentDetached ? "COSE message doesn't contain content" : "Content: " + Encoding.UTF8.GetString(msg.Content!.Value.Span));
ECDsa myKey = ECDsa.Create();
var myArrayHeader = new CoseHeaderLabel("my-array-header");
CoseHeaderMap unprotectedHeaders = new CoseHeaderMap();
unprotectedHeaders.SetValue(CoseHeaderLabel.ContentType, "text/plain; charset=utf-8");
unprotectedHeaders.SetEncodedValue(myArrayHeader, GetCborArray());
// Encode but with user-defined headers.
byte[] encodedMsg = CoseSign1Message.Sign(content, protectedHeaders: new CoseHeaderMap(), unprotectedHeaders, myKey, HashAlgorithmName.SHA256);
// -- handle a message like what we just encoded ------------------------
CoseSign1Message msg = CoseMessage.DecodeSign1(encodedMsg);
// Get info from the unprotected headers.
Console.WriteLine("============ Unprotected headers ============");
// Read header value expecting it to be string (tstr).
Console.WriteLine(msg.UnprotectedHeaders.GetValueAsString(CoseHeaderLabel.ContentType));
// Read header value as CBOR encoded bytes, you need to pass the bytes to a CborReader and read it manually.
PrintCborArray(msg.UnprotectedHeaders.GetEncodedValue(myArrayHeader));
// Get info from the unprotected headers - using the CoseHeaderMap enumerator.
Console.WriteLine("=== Unprotected headers using enumeration ===");
foreach ((CoseHeaderLabel label, ReadOnlyMemory<byte> encodedValue) in msg.UnprotectedHeaders)
{
if (label == CoseHeaderLabel.ContentType)
{
Console.WriteLine(DecodeCborTextString(encodedValue));
}
else if (label == myArrayHeader)
{
PrintCborArray(encodedValue);
}
else
{
throw new Exception("Unknown header found");
}
}
// Helpers
static ReadOnlySpan<byte> GetCborArray()
{
var writer = new CborWriter();
writer.WriteStartArray(definiteLength: 3);
writer.WriteInt32(42);
writer.WriteTextString("foo");
writer.WriteTextString("bar");
writer.WriteEndArray();
return writer.Encode();
}
static void PrintCborArray(ReadOnlyMemory<byte> encodedArray)
{
var reader = new CborReader(encodedArray);
int? length = reader.ReadStartArray();
Console.WriteLine($"Array length: {length?.ToString() ?? "Indefinite"}");
Console.Write("[");
int elementsCount = 0;
while (true)
{
CborReaderState state = reader.PeekState();
if (state == CborReaderState.EndArray) // this wouldn't work with nested arrays.
break;
string valueAsStr = state switch
{
CborReaderState.NegativeInteger or CborReaderState.UnsignedInteger => reader.ReadInt32().ToString(),
CborReaderState.TextString => reader.ReadTextString(),
_ => throw new InvalidOperationException()
};
Console.Write($"{valueAsStr}{(++elementsCount == length ? "" : ", ")}");
}
Console.WriteLine("]");
if (reader.BytesRemaining > 0)
{
throw new Exception("Malformed CBOR - Trailing data after reading array");
}
}
static string DecodeCborTextString(ReadOnlyMemory<byte> encodedString)
{
var reader = new CborReader(encodedString);
string retVal = reader.ReadTextString();
if (reader.BytesRemaining > 0)
{
throw new Exception("Malformed CBOR - Trailing data after reading string");
}
return retVal;
}
Output of this code:
============ Unprotected headers ============
content-type: text/plain; charset=utf-8
my-array-header:
Array length: 3
[42, foo, bar]
=== Unprotected headers using enumeration ===
content-type: text/plain; charset=utf-8
my-array-header:
Array length: 3
[42, foo, bar]
Use GetEncodedValue and SetEncodedValue to operate with any CBOR type.