Skip to content

Commit 321cec8

Browse files
authored
Use ROS<byte> instead of byte[] where it makes sense on S.S.C.Cose (#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
1 parent 3cec42f commit 321cec8

12 files changed

+977
-599
lines changed

src/libraries/System.Security.Cryptography.Cose/ref/System.Security.Cryptography.Cose.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,22 @@ internal CoseMessage() { }
5656
public System.Security.Cryptography.Cose.CoseHeaderMap ProtectedHeaders { get { throw null; } }
5757
public System.Security.Cryptography.Cose.CoseHeaderMap UnprotectedHeaders { get { throw null; } }
5858
public static System.Security.Cryptography.Cose.CoseSign1Message DecodeSign1(byte[] cborPayload) { throw null; }
59+
public static System.Security.Cryptography.Cose.CoseSign1Message DecodeSign1(ReadOnlySpan<byte> cborPayload) { throw null; }
5960
}
6061
public sealed partial class CoseSign1Message : System.Security.Cryptography.Cose.CoseMessage
6162
{
6263
internal CoseSign1Message() { }
6364
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
65+
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; }
66+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
6467
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; }
6568
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
6669
public static byte[] Sign(byte[] content, System.Security.Cryptography.ECDsa key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, bool isDetached = false) { throw null; }
6770
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
6871
public static byte[] Sign(byte[] content, System.Security.Cryptography.RSA key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, bool isDetached = false) { throw null; }
6972
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
73+
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; }
74+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
7075
public bool Verify(System.Security.Cryptography.ECDsa key) { throw null; }
7176
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
7277
public bool Verify(System.Security.Cryptography.ECDsa key, System.ReadOnlySpan<byte> content) { throw null; }

src/libraries/System.Security.Cryptography.Cose/src/System.Security.Cryptography.Cose.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
4+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
45
<Nullable>enable</Nullable>
56
<IsPackable>true</IsPackable>
67
<!-- Disabling baseline validation since this is a brand new package.
@@ -13,8 +14,10 @@
1314
</PropertyGroup>
1415

1516
<ItemGroup>
17+
<Compile Include="$(CommonPath)System\Memory\PointerMemoryManager.cs" Link="Common\System\Memory\PointerMemoryManager.cs" />
1618
<Compile Include="System\Security\Cryptography\Cose\CoseHeaderLabel.cs" />
1719
<Compile Include="System\Security\Cryptography\Cose\CoseHeaderMap.cs" />
20+
<Compile Include="System\Security\Cryptography\Cose\CoseHelpers.cs" />
1821
<Compile Include="System\Security\Cryptography\Cose\CoseMessage.cs" />
1922
<Compile Include="System\Security\Cryptography\Cose\CoseSign1Message.cs" />
2023
<Compile Include="System\Security\Cryptography\Cose\KnownCoseAlgorithms.cs" />
@@ -31,10 +34,12 @@
3134

3235
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
3336
<Reference Include="System.Collections" />
37+
<Reference Include="System.Memory" />
3438
<Reference Include="System.Security.Cryptography.Primitives" />
3539
<Reference Include="System.Security.Cryptography.Algorithms" />
3640
<Reference Include="System.Runtime" />
3741
<Reference Include="System.Runtime.Numerics" />
42+
<Reference Include="System.Text.Encoding.Extensions" />
3843
</ItemGroup>
3944

4045
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">

src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderLabel.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ namespace System.Security.Cryptography.Cose
2424

2525
internal int LabelAsInt32 { get; }
2626
internal string? LabelAsString { get; }
27+
internal int EncodedSize { get; }
2728

2829
public CoseHeaderLabel(int label)
2930
{
3031
this = default;
3132
LabelAsInt32 = label;
33+
EncodedSize = CoseHelpers.GetIntegerEncodedSize(label);
3234
}
3335

3436
public CoseHeaderLabel(string label)
@@ -40,6 +42,7 @@ public CoseHeaderLabel(string label)
4042

4143
this = default;
4244
LabelAsString = label;
45+
EncodedSize = CoseHelpers.GetTextStringEncodedSize(label);
4346
}
4447

4548
public bool Equals(CoseHeaderLabel other)

src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderMap.cs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,15 @@ static void ValidateKnownHeaderValue(int label, CborReaderState? initialState, C
192192
}
193193
}
194194

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

200200
if (map._headerParameters.Count == 0 && mustReturnEmptyBstrIfEmpty && !shouldSlipAlgHeader)
201201
{
202-
return s_emptyBstrEncoded;
202+
s_emptyBstrEncoded.CopyTo(destination);
203+
return s_emptyBstrEncoded.Length;
203204
}
204205

205206
int mapLength = map._headerParameters.Count;
@@ -231,7 +232,33 @@ internal static byte[] Encode(CoseHeaderMap? map, bool mustReturnEmptyBstrIfEmpt
231232
writer.WriteEncodedValue(encodedValue.Span);
232233
}
233234
writer.WriteEndMap();
234-
return writer.Encode();
235+
236+
return writer.Encode(destination);
237+
}
238+
239+
internal static int ComputeEncodedSize(CoseHeaderMap? map, int? algHeaderValueToSlip = null)
240+
{
241+
map ??= s_emptyMap;
242+
243+
// encoded map length => map length + (label + value)*
244+
int encodedSize = 0;
245+
int mapLength = map._headerParameters.Count;
246+
247+
if (algHeaderValueToSlip != null)
248+
{
249+
mapLength += 1;
250+
encodedSize += CoseHeaderLabel.Algorithm.EncodedSize;
251+
encodedSize += CoseHelpers.GetIntegerEncodedSize(algHeaderValueToSlip.Value);
252+
}
253+
254+
encodedSize += CoseHelpers.GetIntegerEncodedSize(mapLength);
255+
256+
foreach ((CoseHeaderLabel label, ReadOnlyMemory<byte> encodedValue) in map)
257+
{
258+
encodedSize += label.EncodedSize + encodedValue.Length;
259+
}
260+
261+
return encodedSize;
235262
}
236263

237264
public Enumerator GetEnumerator() => new Enumerator(_headerParameters.GetEnumerator());
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text;
5+
6+
namespace System.Security.Cryptography.Cose
7+
{
8+
internal static class CoseHelpers
9+
{
10+
private static readonly UTF8Encoding s_utf8EncodingStrict = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
11+
12+
internal static int GetByteStringEncodedSize(int bstrLength)
13+
{
14+
return GetIntegerEncodedSize(bstrLength) + bstrLength;
15+
}
16+
17+
internal static int GetTextStringEncodedSize(string value)
18+
{
19+
int strEncodedLength = s_utf8EncodingStrict.GetByteCount(value);
20+
return GetIntegerEncodedSize(strEncodedLength) + strEncodedLength;
21+
}
22+
23+
internal static int GetIntegerEncodedSize(long value)
24+
{
25+
if (value < 0)
26+
{
27+
ulong unsignedRepresentation = (value == long.MinValue) ? (ulong)long.MaxValue : (ulong)(-value) - 1;
28+
return GetIntegerEncodedSize(unsignedRepresentation);
29+
}
30+
else
31+
{
32+
return GetIntegerEncodedSize((ulong)value);
33+
}
34+
}
35+
36+
internal static int GetIntegerEncodedSize(ulong value)
37+
{
38+
if (value < 24)
39+
{
40+
return 1;
41+
}
42+
else if (value <= byte.MaxValue)
43+
{
44+
return 1 + sizeof(byte);
45+
}
46+
else if (value <= ushort.MaxValue)
47+
{
48+
return 1 + sizeof(ushort);
49+
}
50+
else if (value <= uint.MaxValue)
51+
{
52+
return 1 + sizeof(uint);
53+
}
54+
else
55+
{
56+
return 1 + sizeof(ulong);
57+
}
58+
}
59+
}
60+
}

src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseMessage.cs

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Buffers;
5+
using System.Diagnostics;
46
using System.Formats.Cbor;
7+
using System.Runtime.InteropServices;
58
using System.Runtime.Versioning;
69

710
namespace System.Security.Cryptography.Cose
811
{
912
public abstract class CoseMessage
1013
{
11-
internal const string PreviewFeatureMessage = "COSE is in preview.";
1214
private const byte EmptyStringByte = 0xa0;
15+
internal const int SizeOfArrayOfFour = 1;
16+
1317
// COSE tags https://datatracker.ietf.org/doc/html/rfc8152#page-8 Table 1.
1418
internal const CborTag Sign1Tag = (CborTag)18;
1519

@@ -46,19 +50,58 @@ public ReadOnlyMemory<byte>? Content
4650
}
4751
}
4852

49-
public static CoseSign1Message DecodeSign1(byte[] cborPayload)
53+
public static CoseSign1Message DecodeSign1(byte[] cborPayload!!)
54+
=> DecodeCoseSign1Core(new CborReader(cborPayload));
55+
56+
public static CoseSign1Message DecodeSign1(ReadOnlySpan<byte> cborPayload)
57+
{
58+
unsafe
59+
{
60+
fixed (byte* ptr = &MemoryMarshal.GetReference(cborPayload))
61+
{
62+
using (MemoryManager<byte> manager = new PointerMemoryManager<byte>(ptr, cborPayload.Length))
63+
{
64+
return DecodeCoseSign1Core(new CborReader(manager.Memory));
65+
}
66+
}
67+
}
68+
}
69+
70+
private static CoseSign1Message DecodeCoseSign1Core(CborReader reader)
5071
{
5172
try
5273
{
53-
var reader = new CborReader(cborPayload);
5474
CborTag? tag = DecodeTag(reader);
5575
if (tag != null && tag != Sign1Tag)
5676
{
5777
throw new CryptographicException(SR.Format(SR.DecodeSign1IncorrectTag, tag));
5878
}
5979

60-
CoseSign1Message message = DecodeCoseSign1Core(reader);
61-
return reader.BytesRemaining == 0 ? message : throw new CryptographicException(SR.Format(SR.DecodeSign1ErrorWhileDecoding, SR.DecodeSign1MesageContainedTrailingData));
80+
int? arrayLength = reader.ReadStartArray();
81+
if (arrayLength != 4)
82+
{
83+
throw new CryptographicException(SR.Format(SR.DecodeSign1ErrorWhileDecoding, SR.DecodeSign1ArrayLengthMustBeFour));
84+
}
85+
86+
var protectedHeader = new CoseHeaderMap();
87+
DecodeProtectedBucket(reader, protectedHeader, out byte[] protectedHeaderAsBstr);
88+
protectedHeader.IsReadOnly = true;
89+
90+
var unprotectedHeader = new CoseHeaderMap();
91+
DecodeUnprotectedBucket(reader, unprotectedHeader);
92+
93+
ThrowIfDuplicateLabels(protectedHeader, unprotectedHeader);
94+
95+
byte[]? payload = DecodePayload(reader);
96+
byte[] signature = DecodeSignature(reader);
97+
reader.ReadEndArray();
98+
99+
if (reader.BytesRemaining != 0)
100+
{
101+
throw new CryptographicException(SR.Format(SR.DecodeSign1ErrorWhileDecoding, SR.DecodeSign1MesageContainedTrailingData));
102+
}
103+
104+
return new CoseSign1Message(protectedHeader, unprotectedHeader, payload, signature, protectedHeaderAsBstr);
62105
}
63106
catch (Exception ex) when (ex is CborContentException or InvalidOperationException)
64107
{
@@ -75,30 +118,6 @@ public static CoseSign1Message DecodeSign1(byte[] cborPayload)
75118
};
76119
}
77120

78-
private static CoseSign1Message DecodeCoseSign1Core(CborReader reader)
79-
{
80-
int? arrayLength = reader.ReadStartArray();
81-
if (arrayLength != 4)
82-
{
83-
throw new CryptographicException(SR.Format(SR.DecodeSign1ErrorWhileDecoding, SR.DecodeSign1ArrayLengthMustBeFour));
84-
}
85-
86-
var protectedHeader = new CoseHeaderMap();
87-
DecodeProtectedBucket(reader, protectedHeader, out byte[] protectedHeaderAsBstr);
88-
protectedHeader.IsReadOnly = true;
89-
90-
var unprotectedHeader = new CoseHeaderMap();
91-
DecodeUnprotectedBucket(reader, unprotectedHeader);
92-
93-
ThrowIfDuplicateLabels(protectedHeader, unprotectedHeader);
94-
95-
byte[]? payload = DecodePayload(reader);
96-
byte[] signature = DecodeSignature(reader);
97-
reader.ReadEndArray();
98-
99-
return new CoseSign1Message(protectedHeader, unprotectedHeader, payload, signature, protectedHeaderAsBstr);
100-
}
101-
102121
private static void DecodeProtectedBucket(CborReader reader, CoseHeaderMap headerParameters, out byte[] protectedHeaderAsBstr)
103122
{
104123
protectedHeaderAsBstr = reader.ReadByteString();
@@ -162,7 +181,7 @@ private static byte[] DecodeSignature(CborReader reader)
162181
return reader.ReadByteString();
163182
}
164183

165-
internal static byte[] CreateToBeSigned(string context, ReadOnlySpan<byte> encodedProtectedHeader, ReadOnlySpan<byte> content)
184+
internal static int CreateToBeSigned(string context, ReadOnlySpan<byte> encodedProtectedHeader, ReadOnlySpan<byte> content, Span<byte> destination)
166185
{
167186
var writer = new CborWriter();
168187
writer.WriteStartArray(4);
@@ -171,9 +190,19 @@ internal static byte[] CreateToBeSigned(string context, ReadOnlySpan<byte> encod
171190
writer.WriteByteString(Span<byte>.Empty); // external_aad
172191
writer.WriteByteString(content); //payload or content
173192
writer.WriteEndArray();
174-
return writer.Encode();
193+
int bytesWritten = writer.Encode(destination);
194+
195+
Debug.Assert(bytesWritten == writer.BytesWritten && bytesWritten == ComputeToBeSignedEncodedSize(context, encodedProtectedHeader, content));
196+
return bytesWritten;
175197
}
176198

199+
internal static int ComputeToBeSignedEncodedSize(string context, ReadOnlySpan<byte> encodedProtectedHeader, ReadOnlySpan<byte> content)
200+
=> SizeOfArrayOfFour +
201+
CoseHelpers.GetTextStringEncodedSize(context) +
202+
CoseHelpers.GetByteStringEncodedSize(encodedProtectedHeader.Length) +
203+
CoseHelpers.GetByteStringEncodedSize(Span<byte>.Empty.Length) +
204+
CoseHelpers.GetByteStringEncodedSize(content.Length);
205+
177206
// Validate duplicate labels https://datatracker.ietf.org/doc/html/rfc8152#section-3.
178207
internal static void ThrowIfDuplicateLabels(CoseHeaderMap? protectedHeaders, CoseHeaderMap? unprotectedHeaders)
179208
{

0 commit comments

Comments
 (0)