Skip to content

Commit

Permalink
Use ROS<byte> instead of byte[] where it makes sense on S.S.C.Cose (#…
Browse files Browse the repository at this point in the history
…66741)

* Use ROS<byte> instead of byte[] on S.S.C.Cose

* Add TrySign and improve Sign implementation to use less byte[]

* Add TrySign tests

* Address using scope feedback

* Address src feedback

* Refactor tests to avoid duplicated ones

* Remove invalid asserts in Crypto code

* Fix 'new()' without the type on the left-hand side

* Fix ThreadStatic issues in tests

* * Don't use ArrayPool in SignCore
* Add comment describing reusability of encoded protected headers
* Cache toBeSigned

* Don't cache toBeSigned for detached content

* Address nits in tests
  • Loading branch information
jozkee authored Apr 1, 2022
1 parent 3cec42f commit 321cec8
Show file tree
Hide file tree
Showing 12 changed files with 977 additions and 599 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,22 @@ internal CoseMessage() { }
public System.Security.Cryptography.Cose.CoseHeaderMap ProtectedHeaders { get { throw null; } }
public System.Security.Cryptography.Cose.CoseHeaderMap UnprotectedHeaders { get { throw null; } }
public static System.Security.Cryptography.Cose.CoseSign1Message DecodeSign1(byte[] cborPayload) { throw null; }
public static System.Security.Cryptography.Cose.CoseSign1Message DecodeSign1(ReadOnlySpan<byte> cborPayload) { throw null; }
}
public sealed partial class CoseSign1Message : System.Security.Cryptography.Cose.CoseMessage
{
internal CoseSign1Message() { }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public static byte[] Sign(ReadOnlySpan<byte> content, System.Security.Cryptography.AsymmetricAlgorithm key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.Cose.CoseHeaderMap? protectedHeaders = null, System.Security.Cryptography.Cose.CoseHeaderMap? unprotectedHeaders = null, bool isDetached = false) { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public static byte[] Sign(byte[] content, System.Security.Cryptography.AsymmetricAlgorithm key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.Cose.CoseHeaderMap? protectedHeaders = null, System.Security.Cryptography.Cose.CoseHeaderMap? unprotectedHeaders = null, bool isDetached = false) { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public static byte[] Sign(byte[] content, System.Security.Cryptography.ECDsa key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, bool isDetached = false) { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public static byte[] Sign(byte[] content, System.Security.Cryptography.RSA key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, bool isDetached = false) { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public static bool TrySign(ReadOnlySpan<byte> content, Span<byte> destination, System.Security.Cryptography.AsymmetricAlgorithm key!!, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, out int bytesWritten, System.Security.Cryptography.Cose.CoseHeaderMap? protectedHeaders = null, System.Security.Cryptography.Cose.CoseHeaderMap? unprotectedHeaders = null, bool isDetached = false) { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public bool Verify(System.Security.Cryptography.ECDsa key) { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public bool Verify(System.Security.Cryptography.ECDsa key, System.ReadOnlySpan<byte> content) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>
<!-- Disabling baseline validation since this is a brand new package.
Expand All @@ -13,8 +14,10 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="$(CommonPath)System\Memory\PointerMemoryManager.cs" Link="Common\System\Memory\PointerMemoryManager.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseHeaderLabel.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseHeaderMap.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseHelpers.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseMessage.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseSign1Message.cs" />
<Compile Include="System\Security\Cryptography\Cose\KnownCoseAlgorithms.cs" />
Expand All @@ -31,10 +34,12 @@

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
<Reference Include="System.Collections" />
<Reference Include="System.Memory" />
<Reference Include="System.Security.Cryptography.Primitives" />
<Reference Include="System.Security.Cryptography.Algorithms" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Numerics" />
<Reference Include="System.Text.Encoding.Extensions" />
</ItemGroup>

<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ namespace System.Security.Cryptography.Cose

internal int LabelAsInt32 { get; }
internal string? LabelAsString { get; }
internal int EncodedSize { get; }

public CoseHeaderLabel(int label)
{
this = default;
LabelAsInt32 = label;
EncodedSize = CoseHelpers.GetIntegerEncodedSize(label);
}

public CoseHeaderLabel(string label)
Expand All @@ -40,6 +42,7 @@ public CoseHeaderLabel(string label)

this = default;
LabelAsString = label;
EncodedSize = CoseHelpers.GetTextStringEncodedSize(label);
}

public bool Equals(CoseHeaderLabel other)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,15 @@ static void ValidateKnownHeaderValue(int label, CborReaderState? initialState, C
}
}

internal static byte[] Encode(CoseHeaderMap? map, bool mustReturnEmptyBstrIfEmpty = false, int? algHeaderValueToSlip = null)
internal static int Encode(CoseHeaderMap? map, Span<byte> destination, bool mustReturnEmptyBstrIfEmpty = false, int? algHeaderValueToSlip = null)
{
map ??= s_emptyMap;
bool shouldSlipAlgHeader = algHeaderValueToSlip.HasValue;

if (map._headerParameters.Count == 0 && mustReturnEmptyBstrIfEmpty && !shouldSlipAlgHeader)
{
return s_emptyBstrEncoded;
s_emptyBstrEncoded.CopyTo(destination);
return s_emptyBstrEncoded.Length;
}

int mapLength = map._headerParameters.Count;
Expand Down Expand Up @@ -231,7 +232,33 @@ internal static byte[] Encode(CoseHeaderMap? map, bool mustReturnEmptyBstrIfEmpt
writer.WriteEncodedValue(encodedValue.Span);
}
writer.WriteEndMap();
return writer.Encode();

return writer.Encode(destination);
}

internal static int ComputeEncodedSize(CoseHeaderMap? map, int? algHeaderValueToSlip = null)
{
map ??= s_emptyMap;

// encoded map length => map length + (label + value)*
int encodedSize = 0;
int mapLength = map._headerParameters.Count;

if (algHeaderValueToSlip != null)
{
mapLength += 1;
encodedSize += CoseHeaderLabel.Algorithm.EncodedSize;
encodedSize += CoseHelpers.GetIntegerEncodedSize(algHeaderValueToSlip.Value);
}

encodedSize += CoseHelpers.GetIntegerEncodedSize(mapLength);

foreach ((CoseHeaderLabel label, ReadOnlyMemory<byte> encodedValue) in map)
{
encodedSize += label.EncodedSize + encodedValue.Length;
}

return encodedSize;
}

public Enumerator GetEnumerator() => new Enumerator(_headerParameters.GetEnumerator());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text;

namespace System.Security.Cryptography.Cose
{
internal static class CoseHelpers
{
private static readonly UTF8Encoding s_utf8EncodingStrict = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

internal static int GetByteStringEncodedSize(int bstrLength)
{
return GetIntegerEncodedSize(bstrLength) + bstrLength;
}

internal static int GetTextStringEncodedSize(string value)
{
int strEncodedLength = s_utf8EncodingStrict.GetByteCount(value);
return GetIntegerEncodedSize(strEncodedLength) + strEncodedLength;
}

internal static int GetIntegerEncodedSize(long value)
{
if (value < 0)
{
ulong unsignedRepresentation = (value == long.MinValue) ? (ulong)long.MaxValue : (ulong)(-value) - 1;
return GetIntegerEncodedSize(unsignedRepresentation);
}
else
{
return GetIntegerEncodedSize((ulong)value);
}
}

internal static int GetIntegerEncodedSize(ulong value)
{
if (value < 24)
{
return 1;
}
else if (value <= byte.MaxValue)
{
return 1 + sizeof(byte);
}
else if (value <= ushort.MaxValue)
{
return 1 + sizeof(ushort);
}
else if (value <= uint.MaxValue)
{
return 1 + sizeof(uint);
}
else
{
return 1 + sizeof(ulong);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics;
using System.Formats.Cbor;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

namespace System.Security.Cryptography.Cose
{
public abstract class CoseMessage
{
internal const string PreviewFeatureMessage = "COSE is in preview.";
private const byte EmptyStringByte = 0xa0;
internal const int SizeOfArrayOfFour = 1;

// COSE tags https://datatracker.ietf.org/doc/html/rfc8152#page-8 Table 1.
internal const CborTag Sign1Tag = (CborTag)18;

Expand Down Expand Up @@ -46,19 +50,58 @@ public ReadOnlyMemory<byte>? Content
}
}

public static CoseSign1Message DecodeSign1(byte[] cborPayload)
public static CoseSign1Message DecodeSign1(byte[] cborPayload!!)
=> DecodeCoseSign1Core(new CborReader(cborPayload));

public static CoseSign1Message DecodeSign1(ReadOnlySpan<byte> cborPayload)
{
unsafe
{
fixed (byte* ptr = &MemoryMarshal.GetReference(cborPayload))
{
using (MemoryManager<byte> manager = new PointerMemoryManager<byte>(ptr, cborPayload.Length))
{
return DecodeCoseSign1Core(new CborReader(manager.Memory));
}
}
}
}

private static CoseSign1Message DecodeCoseSign1Core(CborReader reader)
{
try
{
var reader = new CborReader(cborPayload);
CborTag? tag = DecodeTag(reader);
if (tag != null && tag != Sign1Tag)
{
throw new CryptographicException(SR.Format(SR.DecodeSign1IncorrectTag, tag));
}

CoseSign1Message message = DecodeCoseSign1Core(reader);
return reader.BytesRemaining == 0 ? message : throw new CryptographicException(SR.Format(SR.DecodeSign1ErrorWhileDecoding, SR.DecodeSign1MesageContainedTrailingData));
int? arrayLength = reader.ReadStartArray();
if (arrayLength != 4)
{
throw new CryptographicException(SR.Format(SR.DecodeSign1ErrorWhileDecoding, SR.DecodeSign1ArrayLengthMustBeFour));
}

var protectedHeader = new CoseHeaderMap();
DecodeProtectedBucket(reader, protectedHeader, out byte[] protectedHeaderAsBstr);
protectedHeader.IsReadOnly = true;

var unprotectedHeader = new CoseHeaderMap();
DecodeUnprotectedBucket(reader, unprotectedHeader);

ThrowIfDuplicateLabels(protectedHeader, unprotectedHeader);

byte[]? payload = DecodePayload(reader);
byte[] signature = DecodeSignature(reader);
reader.ReadEndArray();

if (reader.BytesRemaining != 0)
{
throw new CryptographicException(SR.Format(SR.DecodeSign1ErrorWhileDecoding, SR.DecodeSign1MesageContainedTrailingData));
}

return new CoseSign1Message(protectedHeader, unprotectedHeader, payload, signature, protectedHeaderAsBstr);
}
catch (Exception ex) when (ex is CborContentException or InvalidOperationException)
{
Expand All @@ -75,30 +118,6 @@ public static CoseSign1Message DecodeSign1(byte[] cborPayload)
};
}

private static CoseSign1Message DecodeCoseSign1Core(CborReader reader)
{
int? arrayLength = reader.ReadStartArray();
if (arrayLength != 4)
{
throw new CryptographicException(SR.Format(SR.DecodeSign1ErrorWhileDecoding, SR.DecodeSign1ArrayLengthMustBeFour));
}

var protectedHeader = new CoseHeaderMap();
DecodeProtectedBucket(reader, protectedHeader, out byte[] protectedHeaderAsBstr);
protectedHeader.IsReadOnly = true;

var unprotectedHeader = new CoseHeaderMap();
DecodeUnprotectedBucket(reader, unprotectedHeader);

ThrowIfDuplicateLabels(protectedHeader, unprotectedHeader);

byte[]? payload = DecodePayload(reader);
byte[] signature = DecodeSignature(reader);
reader.ReadEndArray();

return new CoseSign1Message(protectedHeader, unprotectedHeader, payload, signature, protectedHeaderAsBstr);
}

private static void DecodeProtectedBucket(CborReader reader, CoseHeaderMap headerParameters, out byte[] protectedHeaderAsBstr)
{
protectedHeaderAsBstr = reader.ReadByteString();
Expand Down Expand Up @@ -162,7 +181,7 @@ private static byte[] DecodeSignature(CborReader reader)
return reader.ReadByteString();
}

internal static byte[] CreateToBeSigned(string context, ReadOnlySpan<byte> encodedProtectedHeader, ReadOnlySpan<byte> content)
internal static int CreateToBeSigned(string context, ReadOnlySpan<byte> encodedProtectedHeader, ReadOnlySpan<byte> content, Span<byte> destination)
{
var writer = new CborWriter();
writer.WriteStartArray(4);
Expand All @@ -171,9 +190,19 @@ internal static byte[] CreateToBeSigned(string context, ReadOnlySpan<byte> encod
writer.WriteByteString(Span<byte>.Empty); // external_aad
writer.WriteByteString(content); //payload or content
writer.WriteEndArray();
return writer.Encode();
int bytesWritten = writer.Encode(destination);

Debug.Assert(bytesWritten == writer.BytesWritten && bytesWritten == ComputeToBeSignedEncodedSize(context, encodedProtectedHeader, content));
return bytesWritten;
}

internal static int ComputeToBeSignedEncodedSize(string context, ReadOnlySpan<byte> encodedProtectedHeader, ReadOnlySpan<byte> content)
=> SizeOfArrayOfFour +
CoseHelpers.GetTextStringEncodedSize(context) +
CoseHelpers.GetByteStringEncodedSize(encodedProtectedHeader.Length) +
CoseHelpers.GetByteStringEncodedSize(Span<byte>.Empty.Length) +
CoseHelpers.GetByteStringEncodedSize(content.Length);

// Validate duplicate labels https://datatracker.ietf.org/doc/html/rfc8152#section-3.
internal static void ThrowIfDuplicateLabels(CoseHeaderMap? protectedHeaders, CoseHeaderMap? unprotectedHeaders)
{
Expand Down
Loading

0 comments on commit 321cec8

Please sign in to comment.