Skip to content

Commit

Permalink
Address a few TODOs on S.S.C.Cose. (dotnet#66641)
Browse files Browse the repository at this point in the history
* Make HeaderMaps optionals

* Improve handling of integers larger/smaller than int32 in Alg header
  • Loading branch information
jozkee authored and radekdoulik committed Mar 30, 2022
1 parent 6f106d1 commit c6e0dc8
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public sealed partial class CoseSign1Message : System.Security.Cryptography.Cose
{
internal CoseSign1Message() { }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public static byte[] Sign(byte[] content, System.Security.Cryptography.Cose.CoseHeaderMap protectedHeaders, System.Security.Cryptography.Cose.CoseHeaderMap unprotectedHeaders, System.Security.Cryptography.AsymmetricAlgorithm key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, bool isDetached = false) { throw null; }
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")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<Reference Include="System.Security.Cryptography.Primitives" />
<Reference Include="System.Security.Cryptography.Algorithms" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Numerics" />
</ItemGroup>

<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ namespace System.Security.Cryptography.Cose
public sealed class CoseHeaderMap : IEnumerable<(CoseHeaderLabel Label, ReadOnlyMemory<byte> EncodedValue)>
{
private static readonly byte[] s_emptyBstrEncoded = new byte[] { 0x40 };
private static readonly CoseHeaderMap s_emptyMap = new CoseHeaderMap(isReadOnly: true);

public bool IsReadOnly { get; internal set; }

private readonly Dictionary<CoseHeaderLabel, ReadOnlyMemory<byte>> _headerParameters = new Dictionary<CoseHeaderLabel, ReadOnlyMemory<byte>>();
Expand Down Expand Up @@ -190,16 +192,17 @@ static void ValidateKnownHeaderValue(int label, CborReaderState? initialState, C
}
}

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

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

int mapLength = _headerParameters.Count;
int mapLength = map._headerParameters.Count;
if (shouldSlipAlgHeader)
{
mapLength++;
Expand All @@ -210,14 +213,13 @@ internal byte[] Encode(bool mustReturnEmptyBstrIfEmpty = false, int? algHeaderVa

if (shouldSlipAlgHeader)
{
Debug.Assert(!TryGetEncodedValue(CoseHeaderLabel.Algorithm, out _));
Debug.Assert(!map.TryGetEncodedValue(CoseHeaderLabel.Algorithm, out _));
writer.WriteInt32(KnownHeaders.Alg);
writer.WriteInt32(algHeaderValueToSlip!.Value);
}

foreach ((CoseHeaderLabel Label, ReadOnlyMemory<byte> EncodedValue) header in this)
foreach ((CoseHeaderLabel label, ReadOnlyMemory<byte> encodedValue) in map)
{
CoseHeaderLabel label = header.Label;
if (label.LabelAsString == null)
{
writer.WriteInt32(label.LabelAsInt32);
Expand All @@ -226,7 +228,7 @@ internal byte[] Encode(bool mustReturnEmptyBstrIfEmpty = false, int? algHeaderVa
{
writer.WriteTextString(label.LabelAsString);
}
writer.WriteEncodedValue(header.EncodedValue.Span);
writer.WriteEncodedValue(encodedValue.Span);
}
writer.WriteEndMap();
return writer.Encode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,13 @@ internal static byte[] CreateToBeSigned(string context, ReadOnlySpan<byte> encod
}

// Validate duplicate labels https://datatracker.ietf.org/doc/html/rfc8152#section-3.
internal static void ThrowIfDuplicateLabels(CoseHeaderMap protectedHeaders, CoseHeaderMap unprotectedHeaders)
internal static void ThrowIfDuplicateLabels(CoseHeaderMap? protectedHeaders, CoseHeaderMap? unprotectedHeaders)
{
if (protectedHeaders == null || unprotectedHeaders == null)
{
return;
}

foreach ((CoseHeaderLabel Label, ReadOnlyMemory<byte>) header in protectedHeaders)
{
if (unprotectedHeaders.TryGetEncodedValue(header.Label, out _))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static byte[] Sign(byte[] content!!, RSA key!!, HashAlgorithmName hashAlg
}

[UnsupportedOSPlatform("browser")]
public static byte[] Sign(byte[] content!!, CoseHeaderMap protectedHeaders!!, CoseHeaderMap unprotectedHeaders!!, AsymmetricAlgorithm key!!, HashAlgorithmName hashAlgorithm, bool isDetached = false)
public static byte[] Sign(byte[] content!!, AsymmetricAlgorithm key!!, HashAlgorithmName hashAlgorithm, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null, bool isDetached = false)
{
KeyType keyType = key switch
{
Expand All @@ -47,7 +47,7 @@ public static byte[] Sign(byte[] content!!, CoseHeaderMap protectedHeaders!!, Co

int? algHeaderValueToSlip = ValidateOrSlipAlgorithmHeader(protectedHeaders, unprotectedHeaders, keyType, hashAlgorithm);

byte[] encodedProtetedHeaders = protectedHeaders.Encode(mustReturnEmptyBstrIfEmpty: true, algHeaderValueToSlip);
byte[] encodedProtetedHeaders = CoseHeaderMap.Encode(protectedHeaders, mustReturnEmptyBstrIfEmpty: true, algHeaderValueToSlip);
byte[] toBeSigned = CreateToBeSigned(SigStructureCoxtextSign1, encodedProtetedHeaders, content);

byte[] signature;
Expand All @@ -60,7 +60,7 @@ public static byte[] Sign(byte[] content!!, CoseHeaderMap protectedHeaders!!, Co
signature = SignWithRSA((RSA)key, toBeSigned, hashAlgorithm);
}

return SignCore(encodedProtetedHeaders, unprotectedHeaders.Encode(), signature, content, isDetached);
return SignCore(encodedProtetedHeaders, CoseHeaderMap.Encode(unprotectedHeaders), signature, content, isDetached);
}

private static byte[] SignCore(
Expand Down Expand Up @@ -177,20 +177,20 @@ private static ReadOnlyMemory<byte> GetCoseAlgorithmFromProtectedHeaders(CoseHea
// If we Validate: The caller did specify a COSE Algorithm, we will make sure it matches the specified key and hash algorithm.
// If we Slip: The caller did not specify a COSE Algorithm, we will write the header for them, rather than throw.
private static int? ValidateOrSlipAlgorithmHeader(
CoseHeaderMap protectedHeaders,
CoseHeaderMap unprotectedHeaders,
CoseHeaderMap? protectedHeaders,
CoseHeaderMap? unprotectedHeaders,
KeyType keyType,
HashAlgorithmName hashAlgorithm)
{
int algHeaderValue = GetCoseAlgorithmHeaderFromKeyTypeAndHashAlgorithm(keyType, hashAlgorithm);

if (protectedHeaders.TryGetEncodedValue(CoseHeaderLabel.Algorithm, out ReadOnlyMemory<byte> encodedAlg))
if (protectedHeaders != null && protectedHeaders.TryGetEncodedValue(CoseHeaderLabel.Algorithm, out ReadOnlyMemory<byte> encodedAlg))
{
ValidateAlgorithmHeader(encodedAlg, algHeaderValue, keyType, hashAlgorithm);
return null;
}

if (unprotectedHeaders.TryGetEncodedValue(CoseHeaderLabel.Algorithm, out _))
if (unprotectedHeaders != null && unprotectedHeaders.TryGetEncodedValue(CoseHeaderLabel.Algorithm, out _))
{
throw new CryptographicException(SR.Sign1SignAlgMustBeProtected);
}
Expand All @@ -214,17 +214,28 @@ static void ValidateAlgorithmHeader(ReadOnlyMemory<byte> encodedAlg, int expecte
var reader = new CborReader(encodedAlg);
CborReaderState state = reader.PeekState();

if (state == CborReaderState.NegativeInteger || state == CborReaderState.UnsignedInteger)
if (state == CborReaderState.UnsignedInteger)
{
int alg = reader.ReadInt32();
KnownCoseAlgorithms.ThrowUnsignedIntegerNotSupported(reader.ReadUInt64());
}
else if (state == CborReaderState.NegativeInteger)
{
ulong cborNegativeIntRepresentation = reader.ReadCborNegativeIntegerRepresentation();

if (cborNegativeIntRepresentation > long.MaxValue)
{
KnownCoseAlgorithms.ThrowCborNegativeIntegerNotSupported(cborNegativeIntRepresentation);
}

long alg = checked(-1L - (long)cborNegativeIntRepresentation);
KnownCoseAlgorithms.ThrowIfNotSupported(alg);

if (reader.BytesRemaining != 0)
{
throw new CryptographicException(SR.Sign1VerifyAlgHeaderWasIncorrect);
}

return alg;
return (int)alg;
}

if (state == CborReaderState.TextString)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Numerics;

namespace System.Security.Cryptography.Cose
{
// https://www.iana.org/assignments/cose/cose.xhtml#algorithms
Expand All @@ -15,14 +17,20 @@ internal static class KnownCoseAlgorithms
internal const int PS384 = -38;
internal const int PS512 = -39;

internal static void ThrowIfNotSupported(int alg)
internal static void ThrowIfNotSupported(long alg)
{
if (alg != ES256 && alg > ES384 && alg < PS512)
{
throw new CryptographicException(SR.Format(SR.Sign1UnknownCoseAlgorithm, alg));
}
}

internal static void ThrowUnsignedIntegerNotSupported(ulong alg) // All algorithm valid values are negatives.
=> throw new CryptographicException(SR.Format(SR.Sign1UnknownCoseAlgorithm, alg));

internal static void ThrowCborNegativeIntegerNotSupported(ulong alg) // Cbor Negative Integer Representation is too big.
=> throw new CryptographicException(SR.Format(SR.Sign1UnknownCoseAlgorithm, BigInteger.MinusOne - new BigInteger(alg)));

internal static int FromString(string algString)
{
return algString switch
Expand Down
Loading

0 comments on commit c6e0dc8

Please sign in to comment.