未验证 提交 97d96f2a 编写于 作者: D David Cantú 提交者: GitHub

S.S.C.Cose: Add new MultiSign APIs and address API review feedback on existing ones (#71390)

* Make full assembly unsupported on browser

* Add new MultiSign APIs and address API review feedback on existing ones

* Add SignaturePadding tests and address #70189

* Address feedback (typos and leftover code)

* Add tests for CoseHeaderValue and wrap CborReader/Writer errors

* nits and line chopping

* Move alg. validations to CoseSigner and add remaining AddSignatureFor* APIs
上级 dd438664
......@@ -2,5 +2,6 @@
<Import Project="..\Directory.Build.props" />
<PropertyGroup>
<IncludePlatformAttributes>true</IncludePlatformAttributes>
<UnsupportedOSPlatforms>browser</UnsupportedOSPlatforms>
</PropertyGroup>
</Project>
......@@ -117,6 +117,18 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Argument_EncodeDestinationTooSmall" xml:space="preserve">
<value>The destination is too small to hold the encoded value.</value>
</data>
<data name="ContentWasDetached" xml:space="preserve">
<value>Content was not included in the message (detached message), provide a content to verify.</value>
</data>
<data name="ContentWasEmbedded" xml:space="preserve">
<value>Content was included in the message (embedded message) and yet another content was provided for verification.</value>
</data>
<data name="CoseHeaderMapArgumentCoseHeaderValueIncorrect" xml:space="preserve">
<value>Not a valid CBOR encoded value on CoseHeaderValue on header '{0}', see inner exception for details.</value>
</data>
<data name="CoseHeaderMapCborEncodedValueNotValid" xml:space="preserve">
<value>Not a valid CBOR encoded value, it must be a single value with no trailing data.</value>
</data>
......@@ -126,21 +138,39 @@
<data name="CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue" xml:space="preserve">
<value>Header '{0}' does not accept the specified value.</value>
</data>
<data name="CoseHeaderMapLabelDoeNotExist" xml:space="preserve">
<value>Label '{0}' does not exist is the map.</value>
<data name="CoseHeaderValueErrorWhileDecoding" xml:space="preserve">
<value>Error while decoding CBOR encoded value, see inner exception for details.</value>
</data>
<data name="DecodeSign1ArrayLengthMustBeFour" xml:space="preserve">
<value>Array length for COSE_Sign1 must be four.</value>
<data name="CoseSignerRSAKeyNeedsPadding" xml:space="preserve">
<value>RSA key needs a signature padding.</value>
</data>
<data name="DecodeSign1EncodedProtectedMapIncorrect" xml:space="preserve">
<value>Protected map was incorrect.</value>
<data name="CriticalHeaderMissing" xml:space="preserve">
<value>Critical Header '{0}' missing from protected map.</value>
</data>
<data name="CriticalHeadersLabelWasIncorrect" xml:space="preserve">
<value>Label in Critical Headers array was incorrect.</value>
</data>
<data name="DecodeSign1ErrorWhileDecoding" xml:space="preserve">
<data name="CriticalHeadersMustBeArrayOfAtLeastOne" xml:space="preserve">
<value>Critical Headers must be a definite-length CBOR array of at least one element.</value>
</data>
<data name="DecodeCoseSignatureMustBeArrayOfThree" xml:space="preserve">
<value>COSE Signature must be a definite-length array of 3 elements.</value>
</data>
<data name="DecodeErrorWhileDecoding" xml:space="preserve">
<value>Error while decoding COSE message. {0}</value>
</data>
<data name="DecodeSign1ErrorWhileDecodingSeeInnerEx" xml:space="preserve">
<data name="DecodeErrorWhileDecodingSeeInnerEx" xml:space="preserve">
<value>Error while decoding COSE message. See the inner exception for details.</value>
</data>
<data name="DecodeMultiSignIncorrectTag" xml:space="preserve">
<value>Incorrect tag. Expected Sign(98) or Untagged, Actual '{0}'.</value>
</data>
<data name="DecodeSign1ArrayLengthMustBeFour" xml:space="preserve">
<value>Array length for COSE_Sign1 must be four.</value>
</data>
<data name="DecodeSign1EncodedProtectedMapIncorrect" xml:space="preserve">
<value>Protected map was incorrect.</value>
</data>
<data name="DecodeSign1IncorrectTag" xml:space="preserve">
<value>Incorrect tag. Expected Sign1(18) or Untagged, Actual '{0}'.</value>
</data>
......@@ -153,6 +183,9 @@
<data name="DecodeSign1PayloadWasIncorrect" xml:space="preserve">
<value>Payload was incorrect.</value>
</data>
<data name="MultiSignMessageMustCarryAtLeastOneSignature" xml:space="preserve">
<value>COSE Sign message must carry at least one signature.</value>
</data>
<data name="Sign1AlgDoesNotMatchWithTheOnesSupportedByTypeOfKey" xml:space="preserve">
<value>COSE algorithm '{0}' doesn't match with the supported algorithms of '{1}'.</value>
</data>
......@@ -168,6 +201,9 @@
<data name="Sign1SignCoseAlgorithDoesNotMatchSpecifiedKeyAndHashAlgorithm" xml:space="preserve">
<value>COSE Algorithm '{0}' doesn't match with the specified Key '{1}' and Hash Algorithm '{2}'.</value>
</data>
<data name="Sign1SignCoseAlgorithDoesNotMatchSpecifiedKeyHashAlgorithmAndPadding" xml:space="preserve">
<value>COSE Algorithm '{0}' doesn't match with the specified Key '{1}', Hash Algorithm '{2}', and Signature Padding {3}.</value>
</data>
<data name="Sign1SignHeaderDuplicateLabels" xml:space="preserve">
<value>Protected and Unprotected buckets must not contain duplicate labels.</value>
</data>
......@@ -186,16 +222,4 @@
<data name="Sign1VerifyAlgIsRequired" xml:space="preserve">
<value>Algorithm (alg) header is required and it must be a protected header.</value>
</data>
<data name="Sign1VerifyAlgorithmHeaderParameterWasMissing" xml:space="preserve">
<value>Algorithm (alg) header parameter was missing.</value>
</data>
<data name="Sign1VerifyContentWasDetached" xml:space="preserve">
<value>Content was not included in the message (detached message), provide a content to verify.</value>
</data>
<data name="Sign1VerifyContentWasEmbedded" xml:space="preserve">
<value>Content was included in the message (embedded message) and yet another content was provided for verification.</value>
</data>
<data name="Sign1VerifyCriticalAndCounterSignNotSupported" xml:space="preserve">
<value>Critical and Counter Signature headers are currently not supported.</value>
</data>
</root>
\ No newline at end of file
......@@ -18,11 +18,17 @@
<Compile Include="$(LibrariesProjectRoot)System.Formats.Cbor\src\System\Formats\Cbor\CborInitialByte.cs" Link="System\Formats\Cbor\CborInitialByte.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseHeaderLabel.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseHeaderMap.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseHeaderValue.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseHelpers.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseMessage.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseMultiSignMessage.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseSign1Message.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseSignature.cs" />
<Compile Include="System\Security\Cryptography\Cose\CoseSigner.cs" />
<Compile Include="System\Security\Cryptography\Cose\KeyType.cs" />
<Compile Include="System\Security\Cryptography\Cose\KnownCoseAlgorithms.cs" />
<Compile Include="System\Security\Cryptography\Cose\KnownHeaders.cs" />
<Compile Include="System\Security\Cryptography\Cose\SigStructureContext.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
......
......@@ -10,17 +10,14 @@ namespace System.Security.Cryptography.Cose
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public readonly struct CoseHeaderLabel : IEquatable<CoseHeaderLabel>
{
internal string LabelName => LabelAsString ?? LabelAsInt32.ToString();
internal string LabelName => LabelAsString != null ? $"\"{LabelAsString}\"" : LabelAsInt32.ToString();
private string DebuggerDisplay => $"Label = {LabelName}, Type = {(LabelAsString != null ? typeof(string) : typeof(int))}";
// https://www.iana.org/assignments/cose/cose.xhtml#header-parameters
public static CoseHeaderLabel Algorithm => new CoseHeaderLabel(KnownHeaders.Alg);
public static CoseHeaderLabel Critical => new CoseHeaderLabel(KnownHeaders.Crit);
public static CoseHeaderLabel CriticalHeaders => new CoseHeaderLabel(KnownHeaders.Crit);
public static CoseHeaderLabel ContentType => new CoseHeaderLabel(KnownHeaders.ContentType);
public static CoseHeaderLabel KeyIdentifier => new CoseHeaderLabel(KnownHeaders.Kid);
public static CoseHeaderLabel IV => new CoseHeaderLabel(KnownHeaders.IV);
public static CoseHeaderLabel PartialIV => new CoseHeaderLabel(KnownHeaders.PartialIV);
public static CoseHeaderLabel CounterSignature => new CoseHeaderLabel(KnownHeaders.CounterSignature);
internal int LabelAsInt32 { get; }
internal string? LabelAsString { get; }
......
......@@ -4,108 +4,106 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Formats.Cbor;
using System.Runtime.Versioning;
namespace System.Security.Cryptography.Cose
{
public sealed class CoseHeaderMap : IEnumerable<(CoseHeaderLabel Label, ReadOnlyMemory<byte> EncodedValue)>
public sealed class CoseHeaderMap : IDictionary<CoseHeaderLabel, CoseHeaderValue>, IReadOnlyDictionary<CoseHeaderLabel, CoseHeaderValue>
{
private static readonly CoseHeaderMap s_emptyMap = new CoseHeaderMap(isReadOnly: true);
public bool IsReadOnly { get; internal set; }
private readonly Dictionary<CoseHeaderLabel, CoseHeaderValue> _headerParameters = new Dictionary<CoseHeaderLabel, CoseHeaderValue>();
private readonly Dictionary<CoseHeaderLabel, ReadOnlyMemory<byte>> _headerParameters = new Dictionary<CoseHeaderLabel, ReadOnlyMemory<byte>>();
public bool IsReadOnly { get; internal set; }
public CoseHeaderMap() : this (isReadOnly: false) { }
public CoseHeaderMap() : this(isReadOnly: false) { }
internal CoseHeaderMap(bool isReadOnly)
private CoseHeaderMap(bool isReadOnly)
{
IsReadOnly = isReadOnly;
}
public bool TryGetEncodedValue(CoseHeaderLabel label, out ReadOnlyMemory<byte> encodedValue)
=> _headerParameters.TryGetValue(label, out encodedValue);
private ICollection<KeyValuePair<CoseHeaderLabel, CoseHeaderValue>> HeaderParametersAsCollection => _headerParameters;
public ICollection<CoseHeaderLabel> Keys => _headerParameters.Keys;
public ICollection<CoseHeaderValue> Values => _headerParameters.Values;
public ReadOnlyMemory<byte> GetEncodedValue(CoseHeaderLabel label)
public int Count => _headerParameters.Count;
IEnumerable<CoseHeaderLabel> IReadOnlyDictionary<CoseHeaderLabel, CoseHeaderValue>.Keys => _headerParameters.Keys;
IEnumerable<CoseHeaderValue> IReadOnlyDictionary<CoseHeaderLabel, CoseHeaderValue>.Values => _headerParameters.Values;
public CoseHeaderValue this[CoseHeaderLabel key]
{
if (TryGetEncodedValue(label, out ReadOnlyMemory<byte> encodedValue))
get => _headerParameters[key];
set
{
return encodedValue;
ValidateIsReadOnly();
ValidateInsertion(key, value);
_headerParameters[key] = value;
}
throw new InvalidOperationException(SR.Format(SR.CoseHeaderMapLabelDoeNotExist, label.LabelName));
}
public int GetValueAsInt32(CoseHeaderLabel label)
{
var reader = new CborReader(GetEncodedValue(label));
int retVal = reader.ReadInt32();
Debug.Assert(reader.BytesRemaining == 0);
return retVal;
}
public int GetValueAsInt32(CoseHeaderLabel label) => _headerParameters[label].GetValueAsInt32();
public string GetValueAsString(CoseHeaderLabel label)
{
var reader = new CborReader(GetEncodedValue(label));
string retVal = reader.ReadTextString();
Debug.Assert(reader.BytesRemaining == 0);
return retVal;
}
public string GetValueAsString(CoseHeaderLabel label) => _headerParameters[label].GetValueAsString();
public ReadOnlySpan<byte> GetValueAsBytes(CoseHeaderLabel label)
{
var reader = new CborReader(GetEncodedValue(label));
ReadOnlySpan<byte> retVal = reader.ReadByteString();
Debug.Assert(reader.BytesRemaining == 0);
return retVal;
}
public byte[] GetValueAsBytes(CoseHeaderLabel label) => _headerParameters[label].GetValueAsBytes();
public void SetEncodedValue(CoseHeaderLabel label, ReadOnlySpan<byte> encodedValue)
=> SetEncodedValue(label, new ReadOnlyMemory<byte>(encodedValue.ToArray()));
public int GetValueAsBytes(CoseHeaderLabel label, Span<byte> destination) => _headerParameters[label].GetValueAsBytes(destination);
internal void SetEncodedValue(CoseHeaderLabel label, ReadOnlyMemory<byte> encodedValue)
public void Add(CoseHeaderLabel key, CoseHeaderValue value)
{
ValidateIsReadOnly();
ValidateHeaderValue(label, null, encodedValue);
_headerParameters[label] = encodedValue;
ValidateInsertion(key, value);
_headerParameters.Add(key, value);
}
public void SetValue(CoseHeaderLabel label, int value)
{
ValidateIsReadOnly();
ValidateHeaderValue(label, value < 0 ? CborReaderState.NegativeInteger : CborReaderState.UnsignedInteger, null);
public void Add(KeyValuePair<CoseHeaderLabel, CoseHeaderValue> item) => Add(item.Key, item.Value);
var writer = new CborWriter();
writer.WriteInt32(value);
_headerParameters[label] = writer.Encode();
}
public void Add(CoseHeaderLabel label, int value) => Add(label, CoseHeaderValue.FromInt32(value));
public void Add(CoseHeaderLabel label, string value) => Add(label, CoseHeaderValue.FromString(value));
public void Add(CoseHeaderLabel label, byte[] value) => Add(label, CoseHeaderValue.FromBytes(value));
public void Add(CoseHeaderLabel label, ReadOnlySpan<byte> value) => Add(label, CoseHeaderValue.FromBytes(value));
public void SetValue(CoseHeaderLabel label, string value)
public bool ContainsKey(CoseHeaderLabel key) => _headerParameters.ContainsKey(key);
public bool TryGetValue(CoseHeaderLabel key, out CoseHeaderValue value) => _headerParameters.TryGetValue(key, out value);
public void Clear()
{
ValidateIsReadOnly();
ValidateHeaderValue(label, CborReaderState.TextString, null);
var writer = new CborWriter();
writer.WriteTextString(value);
_headerParameters[label] = writer.Encode();
_headerParameters.Clear();
}
public void SetValue(CoseHeaderLabel label, ReadOnlySpan<byte> value)
public bool Contains(KeyValuePair<CoseHeaderLabel, CoseHeaderValue> item)
=> HeaderParametersAsCollection.Contains(item);
public void CopyTo(KeyValuePair<CoseHeaderLabel, CoseHeaderValue>[] array, int arrayIndex)
=> HeaderParametersAsCollection.CopyTo(array, arrayIndex);
public IEnumerator<KeyValuePair<CoseHeaderLabel, CoseHeaderValue>> GetEnumerator()
=> _headerParameters.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> _headerParameters.GetEnumerator();
public bool Remove(CoseHeaderLabel label)
{
ValidateIsReadOnly();
ValidateHeaderValue(label, CborReaderState.ByteString, null);
var writer = new CborWriter();
writer.WriteByteString(value);
_headerParameters[label] = writer.Encode();
return _headerParameters.Remove(label);
}
public void Remove(CoseHeaderLabel label)
public bool Remove(KeyValuePair<CoseHeaderLabel, CoseHeaderValue> item)
{
ValidateIsReadOnly();
_headerParameters.Remove(label);
return HeaderParametersAsCollection.Remove(item);
}
private void ValidateIsReadOnly()
......@@ -116,91 +114,94 @@ private void ValidateIsReadOnly()
}
}
private static void ValidateHeaderValue(CoseHeaderLabel label, CborReaderState? state, ReadOnlyMemory<byte>? encodedValue)
private static void ValidateInsertion(CoseHeaderLabel label, CoseHeaderValue headerValue)
{
if (state != null)
var reader = new CborReader(headerValue.EncodedValue);
try
{
Debug.Assert(encodedValue == null);
if (label.LabelAsString == null)
if (label.LabelAsString != null) // all known headers are integers.
{
// all known headers are integers.
ValidateKnownHeaderValue(label.LabelAsInt32, state, null);
}
}
else
{
Debug.Assert(encodedValue != null);
var reader = new CborReader(encodedValue.Value);
if (label.LabelAsString == null)
{
// all known headers are integers.
ValidateKnownHeaderValue(label.LabelAsInt32, reader.PeekState(), reader);
reader.SkipValue();
}
else
{
reader.SkipValue();
CborReaderState initialState = reader.PeekState();
switch (label.LabelAsInt32)
{
case KnownHeaders.Alg:
if (initialState != CborReaderState.NegativeInteger &&
initialState != CborReaderState.UnsignedInteger &&
initialState != CborReaderState.TextString)
{
throw new ArgumentException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label.LabelName));
}
reader.SkipValue();
break;
case KnownHeaders.Crit:
int length = reader.ReadStartArray().GetValueOrDefault();
if (length < 1)
{
throw new ArgumentException(SR.CriticalHeadersMustBeArrayOfAtLeastOne);
}
for (int i = 0; i < length; i++)
{
CborReaderState state = reader.PeekState();
if (state == CborReaderState.UnsignedInteger || state == CborReaderState.NegativeInteger)
{
reader.ReadInt32();
}
else if (state == CborReaderState.TextString)
{
reader.ReadTextString();
}
else
{
throw new ArgumentException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label.LabelName));
}
}
reader.SkipToParent();
break;
case KnownHeaders.ContentType:
if (initialState != CborReaderState.TextString &&
initialState != CborReaderState.UnsignedInteger)
{
throw new ArgumentException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label.LabelName));
}
reader.SkipValue();
break;
case KnownHeaders.Kid:
if (initialState != CborReaderState.ByteString)
{
throw new ArgumentException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label.LabelName));
}
reader.SkipValue();
break;
default:
reader.SkipValue();
break;
}
}
if (reader.BytesRemaining != 0)
{
throw new InvalidOperationException(SR.Format(SR.CoseHeaderMapCborEncodedValueNotValid, label));
throw new CborContentException(SR.CoseHeaderMapCborEncodedValueNotValid);
}
}
static void ValidateKnownHeaderValue(int label, CborReaderState? initialState, CborReader? reader)
catch (Exception ex) when (ex is CborContentException or InvalidOperationException)
{
switch (label)
{
case KnownHeaders.Alg:
if (initialState != CborReaderState.NegativeInteger &&
initialState != CborReaderState.UnsignedInteger &&
initialState != CborReaderState.TextString)
{
throw new InvalidOperationException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label));
}
reader?.SkipValue();
break;
case KnownHeaders.Crit:
reader?.SkipValue(); // TODO
break;
case KnownHeaders.CounterSignature:
reader?.SkipValue(); // TODO
break;
case KnownHeaders.ContentType:
if (initialState != CborReaderState.TextString &&
initialState != CborReaderState.UnsignedInteger)
{
throw new InvalidOperationException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label));
}
reader?.SkipValue();
break;
case KnownHeaders.Kid:
case KnownHeaders.IV:
case KnownHeaders.PartialIV:
if (initialState != CborReaderState.ByteString)
{
throw new InvalidOperationException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label));
}
reader?.SkipValue();
break;
default:
reader?.SkipValue();
break;
}
throw new ArgumentException(SR.Format(SR.CoseHeaderMapArgumentCoseHeaderValueIncorrect, label.LabelName), ex);
}
}
internal static int Encode(CoseHeaderMap? map, Span<byte> destination, bool mustReturnEmptyBstrIfEmpty = false, int? algHeaderValueToSlip = null)
internal static int Encode(CoseHeaderMap? map, Span<byte> destination, bool isProtected = false, int? algHeaderValueToSlip = null)
{
map ??= s_emptyMap;
bool shouldSlipAlgHeader = algHeaderValueToSlip.HasValue;
if (map._headerParameters.Count == 0 && mustReturnEmptyBstrIfEmpty && !shouldSlipAlgHeader)
if (map._headerParameters.Count == 0 && isProtected && !shouldSlipAlgHeader)
{
// Empty Bstr encoded
destination[0] = 0x40;
return 1;
return 0;
}
int mapLength = map._headerParameters.Count;
......@@ -214,13 +215,16 @@ internal static int Encode(CoseHeaderMap? map, Span<byte> destination, bool must
if (shouldSlipAlgHeader)
{
Debug.Assert(!map.TryGetEncodedValue(CoseHeaderLabel.Algorithm, out _));
Debug.Assert(!map.ContainsKey(CoseHeaderLabel.Algorithm));
writer.WriteInt32(KnownHeaders.Alg);
writer.WriteInt32(algHeaderValueToSlip!.Value);
}
foreach ((CoseHeaderLabel label, ReadOnlyMemory<byte> encodedValue) in map)
foreach (KeyValuePair<CoseHeaderLabel, CoseHeaderValue> kvp in map)
{
CoseHeaderLabel label = kvp.Key;
CoseHeaderValue value = kvp.Value;
if (label.LabelAsString == null)
{
writer.WriteInt32(label.LabelAsInt32);
......@@ -229,11 +233,15 @@ internal static int Encode(CoseHeaderMap? map, Span<byte> destination, bool must
{
writer.WriteTextString(label.LabelAsString);
}
writer.WriteEncodedValue(encodedValue.Span);
writer.WriteEncodedValue(value.EncodedValue.Span);
}
writer.WriteEndMap();
return writer.Encode(destination);
int bytesWritten = writer.Encode(destination);
Debug.Assert(bytesWritten == ComputeEncodedSize(map, algHeaderValueToSlip));
return bytesWritten;
}
internal static int ComputeEncodedSize(CoseHeaderMap? map, int? algHeaderValueToSlip = null)
......@@ -253,34 +261,15 @@ internal static int ComputeEncodedSize(CoseHeaderMap? map, int? algHeaderValueTo
encodedSize += CoseHelpers.GetIntegerEncodedSize(mapLength);
foreach ((CoseHeaderLabel label, ReadOnlyMemory<byte> encodedValue) in map)
foreach (KeyValuePair<CoseHeaderLabel, CoseHeaderValue> kvp in map)
{
encodedSize += label.EncodedSize + encodedValue.Length;
}
return encodedSize;
}
CoseHeaderLabel label = kvp.Key;
CoseHeaderValue value = kvp.Value;
public Enumerator GetEnumerator() => new Enumerator(_headerParameters.GetEnumerator());
IEnumerator<(CoseHeaderLabel Label, ReadOnlyMemory<byte> EncodedValue)> IEnumerable<(CoseHeaderLabel Label, ReadOnlyMemory<byte> EncodedValue)>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public struct Enumerator : IEnumerator<(CoseHeaderLabel Label, ReadOnlyMemory<byte> EncodedValue)>
{
private Dictionary<CoseHeaderLabel, ReadOnlyMemory<byte>>.Enumerator _dictionaryEnumerator;
internal Enumerator(Dictionary<CoseHeaderLabel, ReadOnlyMemory<byte>>.Enumerator dictionaryEnumerator)
{
_dictionaryEnumerator = dictionaryEnumerator;
encodedSize += label.EncodedSize + value.EncodedValue.Length;
}
public readonly (CoseHeaderLabel Label, ReadOnlyMemory<byte> EncodedValue) Current => (_dictionaryEnumerator.Current.Key, _dictionaryEnumerator.Current.Value);
object IEnumerator.Current => Current;
public void Dispose() => _dictionaryEnumerator.Dispose();
public bool MoveNext() => _dictionaryEnumerator.MoveNext();
public void Reset() => ((IEnumerator)_dictionaryEnumerator).Reset();
return encodedSize;
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using System.Formats.Cbor;
namespace System.Security.Cryptography.Cose
{
public readonly struct CoseHeaderValue : IEquatable<CoseHeaderValue>
{
public readonly ReadOnlyMemory<byte> EncodedValue { get; }
private CoseHeaderValue(ReadOnlyMemory<byte> encodedValue)
{
EncodedValue = encodedValue;
}
private static CoseHeaderValue FromEncodedValue(ReadOnlyMemory<byte> encodedValue)
{
// We don't validate here as we need to know in which label the value is going to be used to validate even more semantics.
CoseHeaderValue value = new CoseHeaderValue(encodedValue);
return value;
}
public static CoseHeaderValue FromEncodedValue(ReadOnlySpan<byte> encodedValue)
{
var encodedValueCopy = new ReadOnlyMemory<byte>(encodedValue.ToArray());
return FromEncodedValue(encodedValueCopy);
}
public static CoseHeaderValue FromEncodedValue(byte[] encodedValue)
{
if (encodedValue == null)
{
throw new ArgumentNullException(nameof(encodedValue));
}
return FromEncodedValue(encodedValue.AsSpan());
}
private static ReadOnlyMemory<byte> Encode(CborWriter writer)
{
byte[] buffer = new byte[writer.BytesWritten];
writer.Encode(buffer);
return buffer.AsMemory();
}
public static CoseHeaderValue FromInt32(int value)
{
var writer = new CborWriter();
writer.WriteInt32(value);
return FromEncodedValue(Encode(writer));
}
public static CoseHeaderValue FromString(string value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
var writer = new CborWriter();
writer.WriteTextString(value);
return FromEncodedValue(Encode(writer));
}
public static CoseHeaderValue FromBytes(ReadOnlySpan<byte> value)
{
var writer = new CborWriter();
writer.WriteByteString(value);
return FromEncodedValue(Encode(writer));
}
public static CoseHeaderValue FromBytes(byte[] value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
return FromBytes(value.AsSpan());
}
public int GetValueAsInt32()
{
var reader = new CborReader(EncodedValue);
int retVal;
try
{
retVal = reader.ReadInt32();
}
catch (Exception ex) when (ex is CborContentException or InvalidOperationException or OverflowException)
{
throw new InvalidOperationException(SR.CoseHeaderValueErrorWhileDecoding, ex);
}
if (reader.BytesRemaining != 0)
{
throw new InvalidOperationException(SR.Format(SR.CoseHeaderMapCborEncodedValueNotValid));
}
return retVal;
}
public string GetValueAsString()
{
var reader = new CborReader(EncodedValue);
string retVal;
try
{
retVal = reader.ReadTextString();
}
catch (Exception ex) when (ex is CborContentException or InvalidOperationException)
{
throw new InvalidOperationException(SR.CoseHeaderValueErrorWhileDecoding, ex);
}
if (reader.BytesRemaining != 0)
{
throw new InvalidOperationException(SR.Format(SR.CoseHeaderMapCborEncodedValueNotValid));
}
return retVal;
}
public byte[] GetValueAsBytes()
{
var reader = new CborReader(EncodedValue);
byte[] retVal;
try
{
retVal = reader.ReadByteString();
}
catch (Exception ex) when (ex is CborContentException or InvalidOperationException)
{
throw new InvalidOperationException(SR.CoseHeaderValueErrorWhileDecoding, ex);
}
if (reader.BytesRemaining != 0)
{
throw new InvalidOperationException(SR.Format(SR.CoseHeaderMapCborEncodedValueNotValid));
}
return retVal;
}
public int GetValueAsBytes(Span<byte> destination)
{
var reader = new CborReader(EncodedValue);
int bytesWritten;
try
{
if (!reader.TryReadByteString(destination, out bytesWritten))
{
throw new ArgumentException(SR.Argument_EncodeDestinationTooSmall);
}
}
catch (Exception ex) when (ex is CborContentException or InvalidOperationException)
{
throw new InvalidOperationException(SR.CoseHeaderValueErrorWhileDecoding, ex);
}
if (reader.BytesRemaining != 0)
{
throw new InvalidOperationException(SR.Format(SR.CoseHeaderMapCborEncodedValueNotValid));
}
return bytesWritten;
}
public override bool Equals([NotNullWhen(true)] object? obj) => obj is CoseHeaderValue otherObj && Equals(otherObj);
public bool Equals(CoseHeaderValue other) => EncodedValue.Span.SequenceEqual(other.EncodedValue.Span);
public override int GetHashCode()
{
HashCode hashCode = default;
foreach (byte b in EncodedValue.Span)
{
hashCode.Add(b);
}
return hashCode.ToHashCode();
}
public static bool operator ==(CoseHeaderValue left, CoseHeaderValue right) => left.Equals(right);
public static bool operator !=(CoseHeaderValue left, CoseHeaderValue right) => !left.Equals(right);
}
}
......@@ -4,13 +4,15 @@
using System.Buffers.Binary;
using System.Diagnostics;
using System.Formats.Cbor;
using System.Runtime.Versioning;
using System.Text;
namespace System.Security.Cryptography.Cose
{
internal static class CoseHelpers
{
internal const int SizeOfNull = 1;
internal const int SizeOfArrayOfLessThan24 = 1;
private static readonly UTF8Encoding s_utf8EncodingStrict = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
internal static int GetByteStringEncodedSize(int bstrLength)
......@@ -102,8 +104,24 @@ internal static int GetIntegerEncodedSize(ulong value)
}
}
[UnsupportedOSPlatform("browser")]
internal static int SignHashWithECDsa(ECDsa key, IncrementalHash hasher, Span<byte> destination)
internal static int SignHash(CoseSigner signer, IncrementalHash hasher, Span<byte> destination)
{
AsymmetricAlgorithm key = signer.Key;
KeyType keyType = signer._keyType;
if (keyType == KeyType.ECDsa)
{
return SignHashWithECDsa((ECDsa)key, hasher, destination);
}
else
{
Debug.Assert(keyType == KeyType.RSA);
Debug.Assert(signer.RSASignaturePadding != null);
return SignHashWithRSA((RSA)key, hasher, signer.HashAlgorithm, signer.RSASignaturePadding, destination);
}
}
private static int SignHashWithECDsa(ECDsa key, IncrementalHash hasher, Span<byte> destination)
{
#if NETSTANDARD2_0 || NETFRAMEWORK
byte[] signature = key.SignHash(hasher.GetHashAndReset());
......@@ -124,11 +142,10 @@ internal static int SignHashWithECDsa(ECDsa key, IncrementalHash hasher, Span<by
#endif
}
[UnsupportedOSPlatform("browser")]
internal static int SignHashWithRSA(RSA key, IncrementalHash hasher, HashAlgorithmName hashAlgorithm, Span<byte> destination)
private static int SignHashWithRSA(RSA key, IncrementalHash hasher, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding, Span<byte> destination)
{
#if NETSTANDARD2_0 || NETFRAMEWORK
byte[] signature = key.SignHash(hasher.GetHashAndReset(), hashAlgorithm, RSASignaturePadding.Pss);
byte[] signature = key.SignHash(hasher.GetHashAndReset(), hashAlgorithm, padding);
signature.CopyTo(destination);
return signature.Length;
#else
......@@ -136,7 +153,7 @@ internal static int SignHashWithRSA(RSA key, IncrementalHash hasher, HashAlgorit
Span<byte> hash = stackalloc byte[hasher.HashLengthInBytes];
hasher.GetHashAndReset(hash);
if (!key.TrySignHash(hash, destination, hashAlgorithm, RSASignaturePadding.Pss, out int bytesWritten))
if (!key.TrySignHash(hash, destination, hashAlgorithm, padding, out int bytesWritten))
{
Debug.Fail("TrySignData failed with a pre-calculated destination");
throw new CryptographicException();
......@@ -152,5 +169,188 @@ internal static void AppendData(this IncrementalHash hasher, ReadOnlySpan<byte>
hasher.AppendData(data.ToArray());
}
#endif
internal static int GetCoseSignEncodedLengthMinusSignature(bool isTagged, int sizeOfCborTag, int encodedProtectedHeadersLength, CoseHeaderMap unprotectedHeaders, byte[]? content)
{
int retVal = 0;
if (isTagged)
{
retVal += sizeOfCborTag;
}
retVal += SizeOfArrayOfLessThan24;
retVal += GetByteStringEncodedSize(encodedProtectedHeadersLength);
retVal += CoseHeaderMap.ComputeEncodedSize(unprotectedHeaders);
if (content is null)
{
retVal += SizeOfNull;
}
else
{
retVal += GetByteStringEncodedSize(content.Length);
}
return retVal;
}
internal static int ComputeSignatureSize(CoseSigner signer)
{
int keySize = signer.Key.KeySize;
KeyType keyType = signer._keyType;
if (keyType == KeyType.ECDsa)
{
return 2 * ((keySize + 7) / 8);
}
else // RSA
{
Debug.Assert(keyType == KeyType.RSA);
return (keySize + 7) / 8;
}
}
internal static int? DecodeCoseAlgorithmHeader(ReadOnlyMemory<byte> encodedAlg)
{
var reader = new CborReader(encodedAlg);
CborReaderState state = reader.PeekState();
if (state == CborReaderState.UnsignedInteger)
{
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 (int)alg;
}
if (state == CborReaderState.TextString)
{
int alg = KnownCoseAlgorithms.FromString(reader.ReadTextString());
if (reader.BytesRemaining != 0)
{
throw new CryptographicException(SR.Sign1VerifyAlgHeaderWasIncorrect);
}
return alg;
}
return null;
}
internal static HashAlgorithmName GetHashAlgorithmFromCoseAlgorithmAndKeyType(int algorithm, KeyType keyType, out RSASignaturePadding? padding)
{
if (keyType == KeyType.ECDsa)
{
padding = null;
return algorithm switch
{
KnownCoseAlgorithms.ES256 => HashAlgorithmName.SHA256,
KnownCoseAlgorithms.ES384 => HashAlgorithmName.SHA384,
KnownCoseAlgorithms.ES512 => HashAlgorithmName.SHA512,
_ => throw new CryptographicException(SR.Format(SR.Sign1AlgDoesNotMatchWithTheOnesSupportedByTypeOfKey, algorithm, typeof(ECDsa)))
};
}
else
{
Debug.Assert(keyType == KeyType.RSA);
HashAlgorithmName hashAlgorithm = algorithm switch
{
KnownCoseAlgorithms.PS256 or KnownCoseAlgorithms.RS256 => HashAlgorithmName.SHA256,
KnownCoseAlgorithms.PS384 or KnownCoseAlgorithms.RS384 => HashAlgorithmName.SHA384,
KnownCoseAlgorithms.PS512 or KnownCoseAlgorithms.RS512 => HashAlgorithmName.SHA512,
_ => throw new CryptographicException(SR.Format(SR.Sign1AlgDoesNotMatchWithTheOnesSupportedByTypeOfKey, algorithm, typeof(RSA)))
};
if (algorithm <= KnownCoseAlgorithms.RS256)
{
Debug.Assert(algorithm >= KnownCoseAlgorithms.RS512);
padding = RSASignaturePadding.Pkcs1;
}
else
{
Debug.Assert(algorithm >= KnownCoseAlgorithms.PS512 && algorithm <= KnownCoseAlgorithms.PS256);
padding = RSASignaturePadding.Pss;
}
return hashAlgorithm;
}
}
internal static KeyType GetKeyType(AsymmetricAlgorithm key)
{
return key switch
{
ECDsa => KeyType.ECDsa,
RSA => KeyType.RSA,
_ => throw new ArgumentException(SR.Format(SR.Sign1UnsupportedKey, key.GetType()), nameof(key))
};
}
internal static ReadOnlyMemory<byte> GetCoseAlgorithmFromProtectedHeaders(CoseHeaderMap protectedHeaders)
{
// https://datatracker.ietf.org/doc/html/rfc8152#section-3.1 alg:
// This parameter MUST be authenticated where the ability to do so exists.
// This authentication can be done either by placing the header in the protected header bucket or as part of the externally supplied data.
if (!protectedHeaders.TryGetValue(CoseHeaderLabel.Algorithm, out CoseHeaderValue value))
{
throw new CryptographicException(SR.Sign1VerifyAlgIsRequired);
}
return value.EncodedValue;
}
internal static int WriteHeaderMap(Span<byte> buffer, CborWriter writer, CoseHeaderMap? headerMap, bool isProtected, int? algHeaderValueToSlip)
{
int bytesWritten = CoseHeaderMap.Encode(headerMap, buffer, isProtected, algHeaderValueToSlip);
ReadOnlySpan<byte> encodedValue = buffer.Slice(0, bytesWritten);
if (isProtected)
{
writer.WriteByteString(encodedValue);
}
else
{
writer.WriteEncodedValue(encodedValue);
}
return bytesWritten;
}
internal static void WriteContent(CborWriter writer, ReadOnlySpan<byte> content, bool isDetached)
{
if (isDetached)
{
writer.WriteNull();
}
else
{
writer.WriteByteString(content);
}
}
internal static void WriteSignature(Span<byte> buffer, IncrementalHash hasher, CborWriter writer, CoseSigner signer)
{
int bytesWritten = SignHash(signer, hasher, buffer);
writer.WriteByteString(buffer.Slice(0, bytesWritten));
}
}
}
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
namespace System.Security.Cryptography.Cose
{
public sealed class CoseSignature
{
private readonly byte[] _encodedBodyProtectedHeaders;
internal readonly byte[] _encodedSignProtectedHeaders;
internal readonly byte[] _signature;
private CoseMultiSignMessage? _message;
public CoseHeaderMap ProtectedHeaders { get; }
public CoseHeaderMap UnprotectedHeaders { get; }
internal CoseSignature(CoseMultiSignMessage message, CoseHeaderMap protectedHeaders, CoseHeaderMap unprotectedHeaders, byte[] encodedBodyProtectedHeaders, byte[] encodedSignProtectedHeaders, byte[] signature)
: this(protectedHeaders, unprotectedHeaders, encodedBodyProtectedHeaders, encodedSignProtectedHeaders, signature)
{
Message = message;
}
internal CoseSignature(CoseHeaderMap protectedHeaders, CoseHeaderMap unprotectedHeaders, byte[] encodedBodyProtectedHeaders, byte[] encodedSignProtectedHeaders, byte[] signature)
{
ProtectedHeaders = protectedHeaders;
UnprotectedHeaders = unprotectedHeaders;
_encodedBodyProtectedHeaders = encodedBodyProtectedHeaders;
_encodedSignProtectedHeaders = encodedSignProtectedHeaders;
_signature = signature;
}
internal CoseMultiSignMessage Message
{
get
{
Debug.Assert(_message != null);
return _message;
}
set
{
_message = value;
}
}
public bool VerifyEmbedded(AsymmetricAlgorithm key, ReadOnlySpan<byte> associatedData)
{
if (key is null)
{
throw new ArgumentNullException(nameof(key));
}
if (Message.IsDetached)
{
throw new InvalidOperationException(SR.ContentWasDetached);
}
return VerifyCore(key, Message.Content.Value.Span, null, associatedData, CoseHelpers.GetKeyType(key));
}
public bool VerifyEmbedded(AsymmetricAlgorithm key, byte[]? associatedData = null)
{
if (key is null)
{
throw new ArgumentNullException(nameof(key));
}
if (Message.IsDetached)
{
throw new InvalidOperationException(SR.ContentWasDetached);
}
return VerifyCore(key, Message.Content.Value.Span, null, associatedData, CoseHelpers.GetKeyType(key));
}
public bool VerifyDetached(AsymmetricAlgorithm key, byte[] detachedContent, byte[]? associatedData = null)
{
if (key is null)
{
throw new ArgumentNullException(nameof(key));
}
if (detachedContent is null)
{
throw new ArgumentNullException(nameof(detachedContent));
}
if (!Message.IsDetached)
{
throw new InvalidOperationException(SR.ContentWasEmbedded);
}
return VerifyCore(key, detachedContent, null, associatedData, CoseHelpers.GetKeyType(key));
}
public bool VerifyDetached(AsymmetricAlgorithm key, ReadOnlySpan<byte> detachedContent, ReadOnlySpan<byte> associatedData = default)
{
if (key is null)
{
throw new ArgumentNullException(nameof(key));
}
if (!Message.IsDetached)
{
throw new InvalidOperationException(SR.ContentWasEmbedded);
}
return VerifyCore(key, detachedContent, null, associatedData, CoseHelpers.GetKeyType(key));
}
public bool VerifyDetached(AsymmetricAlgorithm key, Stream detachedContent, ReadOnlySpan<byte> associatedData = default)
{
if (key is null)
{
throw new ArgumentNullException(nameof(key));
}
if (detachedContent is null)
{
throw new ArgumentNullException(nameof(detachedContent));
}
if (!detachedContent.CanRead)
{
throw new ArgumentException(SR.Sign1ArgumentStreamNotReadable, nameof(detachedContent));
}
if (!detachedContent.CanSeek)
{
throw new ArgumentException(SR.Sign1ArgumentStreamNotSeekable, nameof(detachedContent));
}
if (!Message.IsDetached)
{
throw new InvalidOperationException(SR.ContentWasEmbedded);
}
return VerifyCore(key, default, detachedContent, associatedData, CoseHelpers.GetKeyType(key));
}
public Task<bool> VerifyDetachedAsync(AsymmetricAlgorithm key, Stream detachedContent, ReadOnlyMemory<byte> associatedData = default, CancellationToken cancellationToken = default)
{
if (key is null)
{
throw new ArgumentNullException(nameof(key));
}
if (detachedContent is null)
{
throw new ArgumentNullException(nameof(detachedContent));
}
if (!detachedContent.CanRead)
{
throw new ArgumentException(SR.Sign1ArgumentStreamNotReadable, nameof(detachedContent));
}
if (!detachedContent.CanSeek)
{
throw new ArgumentException(SR.Sign1ArgumentStreamNotSeekable, nameof(detachedContent));
}
if (!Message.IsDetached)
{
throw new InvalidOperationException(SR.ContentWasEmbedded);
}
return VerifyAsyncCore(key, detachedContent, associatedData, CoseHelpers.GetKeyType(key), cancellationToken);
}
private async Task<bool> VerifyAsyncCore(AsymmetricAlgorithm key, Stream content, ReadOnlyMemory<byte> associatedData, KeyType keyType, CancellationToken cancellationToken)
{
ReadOnlyMemory<byte> encodedAlg = CoseHelpers.GetCoseAlgorithmFromProtectedHeaders(ProtectedHeaders);
int? nullableAlg = CoseHelpers.DecodeCoseAlgorithmHeader(encodedAlg);
if (nullableAlg == null)
{
throw new CryptographicException(SR.Sign1VerifyAlgHeaderWasIncorrect);
}
HashAlgorithmName hashAlgorithm = CoseHelpers.GetHashAlgorithmFromCoseAlgorithmAndKeyType(nullableAlg.Value, keyType, out RSASignaturePadding? padding);
using (IncrementalHash hasher = IncrementalHash.CreateHash(hashAlgorithm))
{
int bufferLength = CoseMessage.ComputeToBeSignedEncodedSize(
SigStructureContext.Signature,
_encodedBodyProtectedHeaders.Length,
_encodedSignProtectedHeaders.Length,
associatedData.Length,
contentLength: 0);
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferLength);
try
{
await CoseMessage.AppendToBeSignedAsync(buffer, hasher, SigStructureContext.Signature, _encodedBodyProtectedHeaders, _encodedSignProtectedHeaders, associatedData, content, cancellationToken).ConfigureAwait(false);
return VerifyHash(key, hasher, hashAlgorithm, keyType, padding);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
}
}
}
private bool VerifyCore(AsymmetricAlgorithm key, ReadOnlySpan<byte> contentBytes, Stream? contentStream, ReadOnlySpan<byte> associatedData, KeyType keyType)
{
ReadOnlyMemory<byte> encodedAlg = CoseHelpers.GetCoseAlgorithmFromProtectedHeaders(ProtectedHeaders);
int? nullableAlg = CoseHelpers.DecodeCoseAlgorithmHeader(encodedAlg);
if (nullableAlg == null)
{
throw new CryptographicException(SR.Sign1VerifyAlgHeaderWasIncorrect);
}
HashAlgorithmName hashAlgorithm = CoseHelpers.GetHashAlgorithmFromCoseAlgorithmAndKeyType(nullableAlg.Value, keyType, out RSASignaturePadding? padding);
using (IncrementalHash hasher = IncrementalHash.CreateHash(hashAlgorithm))
{
int bufferLength = CoseMessage.ComputeToBeSignedEncodedSize(
SigStructureContext.Signature,
_encodedBodyProtectedHeaders.Length,
_encodedSignProtectedHeaders.Length,
associatedData.Length,
contentLength: 0);
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferLength);
try
{
CoseMessage.AppendToBeSigned(buffer, hasher, SigStructureContext.Signature, _encodedBodyProtectedHeaders, _encodedSignProtectedHeaders, associatedData, contentBytes, contentStream);
return VerifyHash(key, hasher, hashAlgorithm, keyType, padding);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
}
}
}
private bool VerifyHash(AsymmetricAlgorithm key, IncrementalHash hasher, HashAlgorithmName hashAlgorithm, KeyType keyType, RSASignaturePadding? padding)
{
#if NETCOREAPP
Debug.Assert(hasher.HashLengthInBytes <= 512 / 8); // largest hash we can get (SHA512).
Span<byte> hash = stackalloc byte[hasher.HashLengthInBytes];
hasher.GetHashAndReset(hash);
#else
byte[] hash = hasher.GetHashAndReset();
#endif
if (keyType == KeyType.ECDsa)
{
var ecdsa = (ECDsa)key;
return ecdsa.VerifyHash(hash, _signature);
}
else
{
Debug.Assert(keyType == KeyType.RSA);
Debug.Assert(padding != null);
var rsa = (RSA)key;
return rsa.VerifyHash(hash, _signature, hashAlgorithm, padding);
}
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
namespace System.Security.Cryptography.Cose
{
public sealed class CoseSigner
{
internal readonly KeyType _keyType;
internal readonly int? _algHeaderValueToSlip;
internal CoseHeaderMap? _protectedHeaders;
internal CoseHeaderMap? _unprotectedHeaders;
public AsymmetricAlgorithm Key { get; }
public HashAlgorithmName HashAlgorithm { get; }
public RSASignaturePadding? RSASignaturePadding { get; }
public CoseSigner(AsymmetricAlgorithm key, HashAlgorithmName hashAlgorithm, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null)
{
if (key is null)
throw new ArgumentNullException(nameof(key));
if (key is RSA)
throw new CryptographicException(SR.CoseSignerRSAKeyNeedsPadding);
Key = key;
HashAlgorithm = hashAlgorithm;
_protectedHeaders = protectedHeaders;
_unprotectedHeaders = unprotectedHeaders;
_keyType = CoseHelpers.GetKeyType(key);
_algHeaderValueToSlip = ValidateOrSlipAlgorithmHeader();
}
public CoseSigner(RSA key, RSASignaturePadding signaturePadding, HashAlgorithmName hashAlgorithm, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null)
{
if (key is null)
throw new ArgumentNullException(nameof(key));
if (signaturePadding is null)
throw new ArgumentNullException(nameof(signaturePadding));
Key = key;
HashAlgorithm = hashAlgorithm;
RSASignaturePadding = signaturePadding;
_protectedHeaders = protectedHeaders;
_unprotectedHeaders = unprotectedHeaders;
_keyType = CoseHelpers.GetKeyType(key);
_algHeaderValueToSlip = ValidateOrSlipAlgorithmHeader();
}
public CoseHeaderMap ProtectedHeaders => _protectedHeaders ??= new CoseHeaderMap();
public CoseHeaderMap UnprotectedHeaders => _unprotectedHeaders ??= new CoseHeaderMap();
// If we Validate: The caller specified 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.
internal int? ValidateOrSlipAlgorithmHeader()
{
int algHeaderValue = GetCoseAlgorithmHeader();
if (_protectedHeaders != null && _protectedHeaders.TryGetValue(CoseHeaderLabel.Algorithm, out CoseHeaderValue value))
{
ValidateAlgorithmHeader(value.EncodedValue, algHeaderValue);
return null;
}
if (_unprotectedHeaders != null && _unprotectedHeaders.ContainsKey(CoseHeaderLabel.Algorithm))
{
throw new CryptographicException(SR.Sign1SignAlgMustBeProtected);
}
return algHeaderValue;
}
private void ValidateAlgorithmHeader(ReadOnlyMemory<byte> encodedAlg, int expectedAlg)
{
int? alg = CoseHelpers.DecodeCoseAlgorithmHeader(encodedAlg);
Debug.Assert(alg.HasValue, "Algorithm (alg) is a known header and should have been validated in Set[Encoded]Value()");
if (expectedAlg != alg.Value)
{
string exMsg;
if (_keyType == KeyType.RSA)
{
exMsg = SR.Format(SR.Sign1SignCoseAlgorithDoesNotMatchSpecifiedKeyHashAlgorithmAndPadding, alg.Value, _keyType, HashAlgorithm.Name, RSASignaturePadding);
}
else
{
exMsg = SR.Format(SR.Sign1SignCoseAlgorithDoesNotMatchSpecifiedKeyAndHashAlgorithm, alg.Value, _keyType, HashAlgorithm.Name);
}
throw new CryptographicException(exMsg);
}
}
private int GetCoseAlgorithmHeader()
{
string? hashAlgorithmName = HashAlgorithm.Name;
if (_keyType == KeyType.ECDsa)
{
return hashAlgorithmName switch
{
nameof(HashAlgorithmName.SHA256) => KnownCoseAlgorithms.ES256,
nameof(HashAlgorithmName.SHA384) => KnownCoseAlgorithms.ES384,
nameof(HashAlgorithmName.SHA512) => KnownCoseAlgorithms.ES512,
_ => throw new CryptographicException(SR.Format(SR.Sign1SignUnsupportedHashAlgorithm, hashAlgorithmName))
};
}
Debug.Assert(_keyType == KeyType.RSA);
Debug.Assert(RSASignaturePadding != null);
if (RSASignaturePadding == RSASignaturePadding.Pss)
{
return hashAlgorithmName switch
{
nameof(HashAlgorithmName.SHA256) => KnownCoseAlgorithms.PS256,
nameof(HashAlgorithmName.SHA384) => KnownCoseAlgorithms.PS384,
nameof(HashAlgorithmName.SHA512) => KnownCoseAlgorithms.PS512,
_ => throw new CryptographicException(SR.Format(SR.Sign1SignUnsupportedHashAlgorithm, hashAlgorithmName))
};
}
Debug.Assert(RSASignaturePadding == RSASignaturePadding.Pkcs1);
return hashAlgorithmName switch
{
nameof(HashAlgorithmName.SHA256) => KnownCoseAlgorithms.RS256,
nameof(HashAlgorithmName.SHA384) => KnownCoseAlgorithms.RS384,
nameof(HashAlgorithmName.SHA512) => KnownCoseAlgorithms.RS512,
_ => throw new CryptographicException(SR.Format(SR.Sign1SignUnsupportedHashAlgorithm, hashAlgorithmName))
};
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Security.Cryptography.Cose
{
internal enum KeyType
{
ECDsa,
RSA
}
}
......@@ -16,10 +16,16 @@ internal static class KnownCoseAlgorithms
internal const int PS256 = -37;
internal const int PS384 = -38;
internal const int PS512 = -39;
// RSASSA-PKCS1-v1_5 using SHA
internal const int RS256 = -257;
internal const int RS384 = -258;
internal const int RS512 = -259;
internal static void ThrowIfNotSupported(long alg)
{
if (alg != ES256 && alg > ES384 && alg < PS512)
if (alg != ES256 &&
(alg > ES384 || alg < PS512) &&
(alg > RS256 || alg < RS512))
{
throw new CryptographicException(SR.Format(SR.Sign1UnknownCoseAlgorithm, alg));
}
......@@ -41,6 +47,9 @@ internal static int FromString(string algString)
nameof(PS256) => PS256,
nameof(PS384) => PS384,
nameof(PS512) => PS512,
nameof(RS256) => RS256,
nameof(RS384) => RS384,
nameof(RS512) => RS512,
_ => throw new CryptographicException(SR.Format(SR.Sign1UnknownCoseAlgorithm, algString))
};
}
......
......@@ -10,8 +10,5 @@ internal static class KnownHeaders
internal const int Crit = 2;
internal const int ContentType = 3;
internal const int Kid = 4;
internal const int IV = 5;
internal const int PartialIV = 6;
internal const int CounterSignature = 7;
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Security.Cryptography.Cose
{
internal enum SigStructureContext
{
Signature,
Signature1
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Formats.Cbor;
using System.Linq;
using Test.Cryptography;
using Xunit;
using static System.Security.Cryptography.Cose.Tests.CoseTestHelpers;
namespace System.Security.Cryptography.Cose.Tests
{
public class CoseHeaderValueTests
{
[Theory]
[InlineData(int.MaxValue + 1L)]
[InlineData(int.MinValue - 1L)]
[InlineData(long.MaxValue)]
[InlineData(long.MinValue)]
public void GetValueAsInt32Overflows(long value)
{
var writer = new CborWriter();
writer.WriteInt64(value);
CoseHeaderValue headerValue = CoseHeaderValue.FromEncodedValue(writer.Encode());
Exception ex = Assert.Throws<InvalidOperationException>(() => headerValue.GetValueAsInt32());
Assert.IsType<OverflowException>(ex.InnerException);
}
[Theory]
[InlineData(0)]
[InlineData(int.MaxValue)]
[InlineData(int.MinValue)]
[InlineData((int)ECDsaAlgorithm.ES256)]
public void GetValueAsInt32Succeeds(int value)
{
var writer = new CborWriter();
writer.WriteInt32(value);
CoseHeaderValue headerValue = CoseHeaderValue.FromEncodedValue(writer.Encode());
Assert.Equal(value, headerValue.GetValueAsInt32());
}
[Theory]
[InlineData("")]
[InlineData("foo")]
[InlineData(ContentTypeDummyValue)]
public void GetValueAsStringSucceeds(string value)
{
var writer = new CborWriter();
writer.WriteTextString(value);
CoseHeaderValue headerValue = CoseHeaderValue.FromEncodedValue(writer.Encode());
Assert.Equal(value, headerValue.GetValueAsString());
}
[Theory]
[InlineData("")]
[InlineData("foo")]
[InlineData(ContentTypeDummyValue)]
public void GetValueAsStringBeingIndefiniteLengthSucceeds(string value)
{
Verify(0);
Verify(1);
Verify(10);
void Verify(int repetitions)
{
var writer = new CborWriter();
writer.WriteStartIndefiniteLengthTextString();
for (int i = 0; i < repetitions; i++)
{
writer.WriteTextString(value);
}
writer.WriteEndIndefiniteLengthTextString();
CoseHeaderValue headerValue = CoseHeaderValue.FromEncodedValue(writer.Encode());
string expectedValue = string.Join("", Enumerable.Repeat(value, repetitions));
Assert.Equal(expectedValue, headerValue.GetValueAsString());
}
}
[Theory]
[InlineData(ContentTestCase.Empty)]
[InlineData(ContentTestCase.Small)]
[InlineData(ContentTestCase.Large)]
public void GetValueAsBytesSucceeds(ContentTestCase @case)
{
byte[] content = GetDummyContent(@case);
var writer = new CborWriter();
writer.WriteByteString(content);
CoseHeaderValue headerValue = CoseHeaderValue.FromEncodedValue(writer.Encode());
AssertExtensions.SequenceEqual(content, headerValue.GetValueAsBytes());
Span<byte> buffer = new byte[content.Length];
int length = headerValue.GetValueAsBytes(buffer);
Assert.Equal(content.Length, length);
AssertExtensions.SequenceEqual(content, buffer);
}
[Theory]
[InlineData(ContentTestCase.Empty)]
[InlineData(ContentTestCase.Small)]
[InlineData(ContentTestCase.Large)]
public void GetValueAsBytesBeingIndefiniteLengthSucceeds(ContentTestCase @case)
{
Verify(0);
Verify(1);
Verify(10);
void Verify(int repetitions)
{
byte[] content = GetDummyContent(@case);
var writer = new CborWriter();
writer.WriteStartIndefiniteLengthByteString();
for (int i = 0; i < repetitions; i++)
{
writer.WriteByteString(content);
}
writer.WriteEndIndefiniteLengthByteString();
int expectedLength = content.Length * repetitions;
CoseHeaderValue headerValue = CoseHeaderValue.FromEncodedValue(writer.Encode());
ReadOnlySpan<byte> result = headerValue.GetValueAsBytes();
Assert.Equal(expectedLength, result.Length);
for (int i = 0; i < expectedLength; i += content.Length)
{
AssertExtensions.SequenceEqual(content, result.Slice(i, content.Length));
}
Span<byte> buffer = new byte[expectedLength];
int length = headerValue.GetValueAsBytes(buffer);
Assert.Equal(expectedLength, length);
for (int i = 0; i < expectedLength; i+= content.Length)
{
AssertExtensions.SequenceEqual(content, buffer.Slice(i, content.Length));
}
}
}
[Theory]
[MemberData(nameof(GetValueAsThrowsTestData))]
public void GetValueAsThrows(byte[] encodedValue, GetValueAs method)
{
CoseHeaderValue headerValue = CoseHeaderValue.FromEncodedValue(encodedValue);
if (method == GetValueAs.Int32)
{
Assert.Throws<InvalidOperationException>(() => headerValue.GetValueAsInt32());
}
else if (method == GetValueAs.String)
{
Assert.Throws<InvalidOperationException>(() => headerValue.GetValueAsString());
}
else if (method == GetValueAs.Bytes)
{
Assert.Throws<InvalidOperationException>(() => headerValue.GetValueAsBytes());
}
else
{
Assert.Equal(GetValueAs.BytesSpan, method);
Memory<byte> buffer = new byte[1024]; // big enough to not throw ArgumentException.
Assert.Throws<InvalidOperationException>(() => headerValue.GetValueAsBytes(buffer.Span));
}
}
public static IEnumerable<object[]> GetValueAsThrowsTestData()
{
// null
var writer = new CborWriter();
writer.WriteNull();
byte[] encodedNull = writer.Encode();
yield return new object[] { encodedNull, GetValueAs.Int32 };
yield return new object[] { encodedNull, GetValueAs.String };
yield return new object[] { encodedNull, GetValueAs.Bytes };
yield return new object[] { encodedNull, GetValueAs.BytesSpan };
}
public enum GetValueAs
{
Int32,
String,
Bytes,
BytesSpan
}
[Theory]
[InlineData(0)]
[InlineData(int.MaxValue)]
[InlineData(int.MinValue)]
[InlineData((int)ECDsaAlgorithm.ES256)]
public void FromInt32Succeeds(int value)
{
var writer = new CborWriter();
writer.WriteInt32(value);
CoseHeaderValue headerValue = CoseHeaderValue.FromInt32(value);
AssertExtensions.SequenceEqual(writer.Encode(), headerValue.EncodedValue.Span);
Assert.Equal(value, headerValue.GetValueAsInt32());
}
[Theory]
[InlineData("")]
[InlineData("foo")]
[InlineData(ContentTypeDummyValue)]
public void FromStringSucceeds(string value)
{
var writer = new CborWriter();
writer.WriteTextString(value);
CoseHeaderValue headerValue = CoseHeaderValue.FromString(value);
AssertExtensions.SequenceEqual(writer.Encode(), headerValue.EncodedValue.Span);
Assert.Equal(value, headerValue.GetValueAsString());
}
[Fact]
public void FromStringThrowsArgumentNull()
{
Assert.Throws<ArgumentNullException>("value", () => CoseHeaderValue.FromString(null!));
}
[Theory]
[InlineData(ContentTestCase.Empty)]
[InlineData(ContentTestCase.Small)]
[InlineData(ContentTestCase.Large)]
public void FromBytesSucceeds(ContentTestCase @case)
{
byte[] content = GetDummyContent(@case);
var writer = new CborWriter();
writer.WriteByteString(content);
byte[] encodedBytes = writer.Encode();
CoseHeaderValue headerValue = CoseHeaderValue.FromBytes(content);
AssertExtensions.SequenceEqual(encodedBytes, headerValue.EncodedValue.Span);
AssertExtensions.SequenceEqual(content, headerValue.GetValueAsBytes());
headerValue = CoseHeaderValue.FromBytes(content.AsSpan());
AssertExtensions.SequenceEqual(encodedBytes, headerValue.EncodedValue.Span);
Span<byte> buffer = new byte[content.Length];
int length = headerValue.GetValueAsBytes(buffer);
AssertExtensions.SequenceEqual(content, buffer);
Assert.Equal(content.Length, length);
}
[Theory]
[InlineData(ContentTestCase.Small)]
[InlineData(ContentTestCase.Large)]
public void FromBytesThrowsBufferTooSmall(ContentTestCase @case)
{
byte[] content = GetDummyContent(@case);
CoseHeaderValue headerValue = CoseHeaderValue.FromBytes(content.AsSpan());
Memory<byte> buffer = new byte[content.Length - 1];
Assert.Throws<ArgumentException>(() => headerValue.GetValueAsBytes(buffer.Span));
}
[Fact]
public void FromBytesThrowsArgumentNull()
{
Assert.Throws<ArgumentNullException>("value",() => CoseHeaderValue.FromBytes(null!));
}
[Theory]
[MemberData(nameof(AllCborTypesTestData))]
public void FromEncodedValue(byte[] encodedValue)
{
var headerValue = CoseHeaderValue.FromEncodedValue(encodedValue);
AssertExtensions.SequenceEqual(encodedValue, headerValue.EncodedValue.Span);
// make sure is readable.
var reader = new CborReader(headerValue.EncodedValue);
reader.SkipValue();
headerValue = CoseHeaderValue.FromEncodedValue(encodedValue.AsSpan());
AssertExtensions.SequenceEqual(encodedValue, headerValue.EncodedValue.Span);
// make sure is readable.
reader = new CborReader(headerValue.EncodedValue);
reader.SkipValue();
}
public static IEnumerable<object[]> AllCborTypesTestData()
{
foreach (byte[] encodedValue in AllCborTypes())
{
yield return new object[] { encodedValue };
}
}
[Fact]
public void FromEncodedValueThrowsArgumentNull()
{
Assert.Throws<ArgumentNullException>("encodedValue", () => CoseHeaderValue.FromEncodedValue(null!));
}
[Fact]
public void CoseHeaderValue_GetHashCode()
{
CoseHeaderValue value1 = default;
CoseHeaderValue value2 = new CoseHeaderValue();
int value1HashCode = value1.GetHashCode();
Assert.Equal(value1HashCode, value2.GetHashCode());
value1 = CoseHeaderValue.FromInt32(0);
value2 = CoseHeaderValue.FromInt32(0);
Assert.Equal(value1.GetHashCode(), value2.GetHashCode());
value1 = default;
Assert.NotEqual(value1.GetHashCode(), value2.GetHashCode());
value1 = new CoseHeaderValue();
Assert.NotEqual(value1.GetHashCode(), value2.GetHashCode());
}
[Fact]
public void CoseHeaderValue_Equals()
{
Assert.True(default(CoseHeaderValue).Equals(default), "default(CoseHeaderValue).Equals(default)");
Assert.True(default(CoseHeaderValue).Equals(new CoseHeaderValue()), "default(CoseHeaderValue).Equals(new CoseHeaderValue()");
CoseHeaderValue value1 = CoseHeaderValue.FromInt32(0);
CoseHeaderValue value2 = CoseHeaderValue.FromInt32(0);
Assert.True(value1.Equals(value2), "CoseHeaderValue.FromInt32(0) - value1.Equals(value2)");
value1 = CoseHeaderValue.FromString("foo");
value2 = CoseHeaderValue.FromString("foo");
Assert.True(value1.Equals(value2), "CoseHeaderValue.FromString(\"foo\") - value1.Equals(value2)");
byte[] bytes = "foo"u8.ToArray();
value1 = CoseHeaderValue.FromBytes(bytes);
value2 = CoseHeaderValue.FromBytes(bytes);
Assert.True(value1.Equals(value2), "CoseHeaderValue.FromBytes(bytes) - value1.Equals(value2)");
value1 = CoseHeaderValue.FromBytes(bytes.AsSpan());
value2 = CoseHeaderValue.FromBytes(bytes.AsSpan());
Assert.True(value1.Equals(value2), "CoseHeaderValue.FromBytes(bytes.AsSpan()) - value1.Equals(value2)");
byte[] encodedValue = ByteUtils.HexToByteArray("80"); // empty array
value1 = CoseHeaderValue.FromEncodedValue(encodedValue);
value2 = CoseHeaderValue.FromEncodedValue(encodedValue);
Assert.True(value1.Equals(value2), "CoseHeaderValue.FromEncodedValue(encodedValue) - value1.Equals(value2)");
value1 = CoseHeaderValue.FromString("foo");
value2 = CoseHeaderValue.FromString("bar");
Assert.False(value1.Equals(value2), "CoseHeaderValue.FromString(\"foo\").Equals(CoseHeaderValue.FromString(\"bar\"))");
value1 = CoseHeaderValue.FromInt32(0);
value2 = default;
Assert.False(value1.Equals(value2), "CoseHeaderValue.FromInt32(0).Equals(default)");
}
[Fact]
public void CoseHeaderValue_op_Equality()
{
Assert.True(default(CoseHeaderValue) == default, "default(CoseHeaderValue) == default");
Assert.True(default(CoseHeaderValue) == new CoseHeaderValue(), "default(CoseHeaderValue) == new CoseHeaderValue(");
CoseHeaderValue value1 = CoseHeaderValue.FromInt32(0);
CoseHeaderValue value2 = CoseHeaderValue.FromInt32(0);
Assert.True(value1 == value2, "CoseHeaderValue.FromInt32(0) - value1 == value2");
value1 = CoseHeaderValue.FromString("foo");
value2 = CoseHeaderValue.FromString("foo");
Assert.True(value1 == value2, "CoseHeaderValue.FromString(\"foo\") - value1 == value2");
byte[] bytes = "foo"u8.ToArray();
value1 = CoseHeaderValue.FromBytes(bytes);
value2 = CoseHeaderValue.FromBytes(bytes);
Assert.True(value1 == value2, "CoseHeaderValue.FromBytes(bytes) - value1 == value2");
value1 = CoseHeaderValue.FromBytes(bytes.AsSpan());
value2 = CoseHeaderValue.FromBytes(bytes.AsSpan());
Assert.True(value1 == value2, "CoseHeaderValue.FromBytes(bytes.AsSpan()) - value1 == value2");
byte[] encodedValue = ByteUtils.HexToByteArray("80"); // empty array
value1 = CoseHeaderValue.FromEncodedValue(encodedValue);
value2 = CoseHeaderValue.FromEncodedValue(encodedValue);
Assert.True(value1 == value2, "CoseHeaderValue.FromEncodedValue(encodedValue) - value1 == value2");
value1 = CoseHeaderValue.FromString("foo");
value2 = CoseHeaderValue.FromString("bar");
Assert.False(value1 == value2, "CoseHeaderValue.FromString(\"foo\") == CoseHeaderValue.FromString(\"bar\")");
value1 = CoseHeaderValue.FromInt32(0);
value2 = default;
Assert.False(value1 == value2, "CoseHeaderValue.FromInt32(0) == default");
}
[Fact]
public void CoseHeaderValue_op_Inequality()
{
Assert.False(default(CoseHeaderValue) != default, "default(CoseHeaderValue) != default");
Assert.False(default(CoseHeaderValue) != new CoseHeaderValue(), "default(CoseHeaderValue) != new CoseHeaderValue(");
CoseHeaderValue value1 = CoseHeaderValue.FromInt32(0);
CoseHeaderValue value2 = CoseHeaderValue.FromInt32(0);
Assert.False(value1 != value2, "CoseHeaderValue.FromInt32(0) - value1 != value2");
value1 = CoseHeaderValue.FromString("foo");
value2 = CoseHeaderValue.FromString("foo");
Assert.False(value1 != value2, "CoseHeaderValue.FromString(\"foo\") - value1 != value2");
byte[] bytes = "foo"u8.ToArray();
value1 = CoseHeaderValue.FromBytes(bytes);
value2 = CoseHeaderValue.FromBytes(bytes);
Assert.False(value1 != value2, "CoseHeaderValue.FromBytes(bytes) - value1 != value2");
value1 = CoseHeaderValue.FromBytes(bytes.AsSpan());
value2 = CoseHeaderValue.FromBytes(bytes.AsSpan());
Assert.False(value1 != value2, "CoseHeaderValue.FromBytes(bytes.AsSpan()) - value1 != value2");
byte[] encodedValue = ByteUtils.HexToByteArray("80"); // empty array
value1 = CoseHeaderValue.FromEncodedValue(encodedValue);
value2 = CoseHeaderValue.FromEncodedValue(encodedValue);
Assert.False(value1 != value2, "CoseHeaderValue.FromEncodedValue(encodedValue) - value1 != value2");
value1 = CoseHeaderValue.FromString("foo");
value2 = CoseHeaderValue.FromString("bar");
Assert.True(value1 != value2, "CoseHeaderValue.FromString(\"foo\") != (CoseHeaderValue.FromString(\"bar\")");
value1 = CoseHeaderValue.FromInt32(0);
value2 = default;
Assert.True(value1 != value2, "CoseHeaderValue.FromInt32(0) != default");
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.ObjectModel;
using System.Formats.Cbor;
using Test.Cryptography;
using Xunit;
using static System.Security.Cryptography.Cose.Tests.CoseTestHelpers;
namespace System.Security.Cryptography.Cose.Tests
{
public partial class CoseMessageTests
{
[Fact]
public void DecodeMultiSign_VerifyUntagged()
{
// https://github.com/cose-wg/Examples/blob/master/ecdsa-examples/ecdsa-01.json minus first 2 bytes.
CoseMultiSignMessage msg = CoseMessage.DecodeMultiSign(ByteUtils.HexToByteArray("8443A10300A054546869732069732074686520636F6E74656E742E818343A10126A1044231315840D71C05DB52C9CE7F1BF5AAC01334BBEACAC1D86A2303E6EEAA89266F45C01ED602CA649EAF790D8BC99D2458457CA6A872061940E7AFBE48E289DFAC146AE258"));
ReadOnlyCollection<CoseSignature> signatures = msg.Signatures;
Assert.Equal(1, signatures.Count);
Assert.True(signatures[0].VerifyEmbedded(DefaultKey));
}
[Fact]
public void DecodeMultiSign_VerifyDetachedContent()
{
// Content is replaced with CBOR null - https://github.com/cose-wg/Examples/blob/master/ecdsa-examples/ecdsa-01.json.
CoseMultiSignMessage msg = CoseMessage.DecodeMultiSign(ByteUtils.HexToByteArray("D8628443A10300A0F6818343A10126A1044231315840D71C05DB52C9CE7F1BF5AAC01334BBEACAC1D86A2303E6EEAA89266F45C01ED602CA649EAF790D8BC99D2458457CA6A872061940E7AFBE48E289DFAC146AE258"));
Assert.Null(msg.Content);
ReadOnlyCollection<CoseSignature> signatures = msg.Signatures;
Assert.Equal(1, signatures.Count);
Assert.True(signatures[0].VerifyDetached(DefaultKey, s_sampleContent));
}
[Theory]
// Body protected has duplicate header.
[InlineData(true, "D8628445A203000301A054546869732069732074686520636F6E74656E742E818343A10126A1044231315840D71C05DB52C9CE7F1BF5AAC01334BBEACAC1D86A2303E6EEAA89266F45C01ED602CA649EAF790D8BC99D2458457CA6A872061940E7AFBE48E289DFAC146AE258")]
// Body unprotected has duplicate header.
[InlineData(true, "D8628443A10300A2012601382254546869732069732074686520636F6E74656E742E818343A10126A1044231315840D71C05DB52C9CE7F1BF5AAC01334BBEACAC1D86A2303E6EEAA89266F45C01ED602CA649EAF790D8BC99D2458457CA6A872061940E7AFBE48E289DFAC146AE258")]
// Duplicate header is in the union of body protected and body unprotected.
[InlineData(false, "D8628443A10300A1030054546869732069732074686520636F6E74656E742E818343A10126A1044231315840D71C05DB52C9CE7F1BF5AAC01334BBEACAC1D86A2303E6EEAA89266F45C01ED602CA649EAF790D8BC99D2458457CA6A872061940E7AFBE48E289DFAC146AE258")]
// Sign protected has duplicate header.
[InlineData(true, "D8628443A10300A054546869732069732074686520636F6E74656E742E818345A201260138A1044231315840D71C05DB52C9CE7F1BF5AAC01334BBEACAC1D86A2303E6EEAA89266F45C01ED602CA649EAF790D8BC99D2458457CA6A872061940E7AFBE48E289DFAC146AE258")]
// Sign unprotected has duplicate header.
[InlineData(true, "D8628443A10300A054546869732069732074686520636F6E74656E742E818340A30126013822044231315840D71C05DB52C9CE7F1BF5AAC01334BBEACAC1D86A2303E6EEAA89266F45C01ED602CA649EAF790D8BC99D2458457CA6A872061940E7AFBE48E289DFAC146AE258")]
// Duplicate header is in the union of sign protected and sign unprotected.
[InlineData(false, "D8628443A10300A054546869732069732074686520636F6E74656E742E818343A10126A20126044231315840D71C05DB52C9CE7F1BF5AAC01334BBEACAC1D86A2303E6EEAA89266F45C01ED602CA649EAF790D8BC99D2458457CA6A872061940E7AFBE48E289DFAC146AE258")]
public void DecodeMultiSign_ThrowsWithDuplicateHeaders(bool shouldContainInnerException, string hexCborMessage)
{
CryptographicException ex = Assert.Throws<CryptographicException>(() => CoseMessage.DecodeMultiSign(ByteUtils.HexToByteArray(hexCborMessage)));
if (shouldContainInnerException) // if the duplicate headers were in one bucket the exception comes from CborReader because we use CborConformanceMode.Strict.
{
Assert.IsType<CborContentException>(ex.InnerException);
}
else
{
Assert.Null(ex.InnerException);
}
}
[Theory]
// Tag is 998 (Unknown) - https://github.com/cose-wg/Examples/blob/master/sign-tests/sign-fail-01.json.
[InlineData("D903E68440A054546869732069732074686520636F6E74656E742E818343A10126A1044231315840E2AEAFD40D69D19DFE6E52077C5D7FF4E408282CBEFB5D06CBF414AF2E19D982AC45AC98B8544C908B4507DE1E90B717C3D34816FE926A2B98F53AFD2FA0F30A")]
// Tag is 18 (Sign1) - https://github.com/cose-wg/Examples/blob/master/sign1-tests/sign-pass-01.json.
[InlineData("D28441A0A201260442313154546869732069732074686520636F6E74656E742E584087DB0D2E5571843B78AC33ECB2830DF7B6E0A4D5B7376DE336B23C591C90C425317E56127FBE04370097CE347087B233BF722B64072BEB4486BDA4031D27244F")]
public void DecodeMultiSign_IncorrectTag(string hexCborMessage)
{
Assert.Throws<CryptographicException>(() => CoseMessage.DecodeMultiSign(ByteUtils.HexToByteArray(hexCborMessage)));
}
[Fact]
public void DecodeMultiSign_IncorrectStructure()
{
var writer = new CborWriter();
writer.WriteStartArray(4);
writer.WriteNull();
writer.WriteNull();
writer.WriteNull();
writer.WriteNull();
writer.WriteEndArray();
Assert.Throws<CryptographicException>(() => CoseMessage.DecodeMultiSign(writer.Encode()));
}
}
}
......@@ -8,21 +8,23 @@
namespace System.Security.Cryptography.Cose.Tests
{
public class CoseMessageTests
public partial class CoseMessageTests
{
[Fact]
public void DecodeVerifyUntagged()
public void DecodeSign1_VerifyUntagged()
{
// https://github.com/cose-wg/Examples/blob/master/ecdsa-examples/ecdsa-sig-01.json minus first byte.
CoseSign1Message msg = CoseMessage.DecodeSign1(ByteUtils.HexToByteArray("8445A201260300A10442313154546869732069732074686520636F6E74656E742E58406520BBAF2081D7E0ED0F95F76EB0733D667005F7467CEC4B87B9381A6BA1EDE8E00DF29F32A37230F39A842A54821FDD223092819D7728EFB9D3A0080B75380B"));
Assert.True(msg.Verify(DefaultKey));
Assert.True(msg.VerifyEmbedded(DefaultKey));
}
[Fact]
public void DecodeVerifyDetachedContent()
public void DecodeSign1_VerifyDetachedContent()
{
// Content is replaced with CBOR null - https://github.com/cose-wg/Examples/blob/master/ecdsa-examples/ecdsa-sig-01.json.
CoseSign1Message msg = CoseMessage.DecodeSign1(ByteUtils.HexToByteArray("D28445A201260300A104423131F658406520BBAF2081D7E0ED0F95F76EB0733D667005F7467CEC4B87B9381A6BA1EDE8E00DF29F32A37230F39A842A54821FDD223092819D7728EFB9D3A0080B75380B"));
Assert.Null(msg.Content);
Assert.True(msg.Verify(DefaultKey, s_sampleContent));
Assert.True(msg.VerifyDetached(DefaultKey, s_sampleContent));
}
[Theory]
......@@ -32,7 +34,7 @@ public void DecodeVerifyDetachedContent()
[InlineData(true, "D28441A0A301260138220442313154546869732069732074686520636F6E74656E742E584087DB0D2E5571843B78AC33ECB2830DF7B6E0A4D5B7376DE336B23C591C90C425317E56127FBE04370097CE347087B233BF722B64072BEB4486BDA4031D27244F")]
// Duplicate header is in the union of protected and unprotected buckets.
[InlineData(false, "D28443A10126A201260442313154546869732069732074686520636F6E74656E742E584087DB0D2E5571843B78AC33ECB2830DF7B6E0A4D5B7376DE336B23C591C90C425317E56127FBE04370097CE347087B233BF722B64072BEB4486BDA4031D27244F")]
public void DecodeThrowsWithDuplicateHeaders(bool shouldContainInnerException, string hexCborMessage)
public void DecodeSign1_ThrowsWithDuplicateHeaders(bool shouldContainInnerException, string hexCborMessage)
{
CryptographicException ex = Assert.Throws<CryptographicException>(() => CoseMessage.DecodeSign1(ByteUtils.HexToByteArray(hexCborMessage)));
if (shouldContainInnerException) // if the duplicate headers were in one bucket the exception comes from CborReader because we use CborConformanceMode.Strict.
......@@ -48,7 +50,7 @@ public void DecodeThrowsWithDuplicateHeaders(bool shouldContainInnerException, s
[Theory]
// Tag is 998 (Unknown) - https://github.com/cose-wg/Examples/blob/master/sign1-tests/sign-fail-01.json.
[InlineData("D903E68443A10126A10442313154546869732069732074686520636F6E74656E742E58408EB33E4CA31D1C465AB05AAC34CC6B23D58FEF5C083106C4D25A91AEF0B0117E2AF9A291AA32E14AB834DC56ED2A223444547E01F11D3B0916E5A4C345CACB36")]
// Tag is 98 (Sign).
// Tag is 98 (Sign) - https://github.com/cose-wg/Examples/blob/master/sign-tests/sign-pass-01.json.
[InlineData("D8628441A0A054546869732069732074686520636F6E74656E742E818343A10126A1044231315840E2AEAFD40D69D19DFE6E52077C5D7FF4E408282CBEFB5D06CBF414AF2E19D982AC45AC98B8544C908B4507DE1E90B717C3D34816FE926A2B98F53AFD2FA0F30A")]
public void DecodeSign1_IncorrectTag(string hexCborMessage)
{
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.IO;
using System.Threading.Tasks;
using Xunit;
using static System.Security.Cryptography.Cose.Tests.CoseTestHelpers;
namespace System.Security.Cryptography.Cose.Tests
{
public abstract class CoseMessageTests_SignStream : CoseMessageTests_Sign_CustomHeaderMaps
{
internal override bool OnlySupportsDetachedContent => true;
}
public abstract class CoseMessageTests_SignStream_Async : CoseMessageTests_SignStream
{
internal abstract Task<byte[]> SignDetachedAsync(Stream detachedContent, CoseSigner signer, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null, byte[]? associatedData = null);
internal override byte[] Sign(byte[] content, CoseSigner signer, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null, byte[]? associatedData = null, bool isDetached = false)
{
Assert.False(isDetached);
if (content == null)
{
return SignDetachedAsync(null!, signer, protectedHeaders, unprotectedHeaders, associatedData).GetAwaiter().GetResult();
}
using Stream stream = GetTestStream(content);
return SignDetachedAsync(stream, signer, protectedHeaders, unprotectedHeaders, associatedData).GetAwaiter().GetResult();
}
[Fact]
public async Task SignAsyncWithUnseekableStream()
{
using Stream stream = GetTestStream(s_sampleContent, StreamKind.Unseekable);
await Assert.ThrowsAsync<ArgumentException>("detachedContent", () => SignDetachedAsync(stream, GetCoseSigner(DefaultKey, DefaultHash)));
}
[Fact]
public async Task SignAsyncWithUnreadableStream()
{
using Stream stream = GetTestStream(s_sampleContent, StreamKind.Unreadable);
await Assert.ThrowsAsync<ArgumentException>("detachedContent", () => SignDetachedAsync(stream, GetCoseSigner(DefaultKey, DefaultHash)));
}
}
public abstract class CoseMessageTests_SignStream_Sync : CoseMessageTests_SignStream
{
internal abstract byte[] SignDetached(Stream detachedContent, CoseSigner signer, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null, byte[]? associatedData = null);
internal override byte[] Sign(byte[] content, CoseSigner signer, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null, byte[]? associatedData = null, bool isDetached = false)
{
Assert.False(isDetached);
if (content == null)
{
return SignDetached(null!, signer, protectedHeaders, unprotectedHeaders, associatedData);
}
using Stream stream = GetTestStream(content);
return SignDetached(stream, signer, protectedHeaders, unprotectedHeaders, associatedData);
}
[Fact]
public void SignWithUnseekableStream()
{
using Stream stream = GetTestStream(s_sampleContent, StreamKind.Unseekable);
Assert.Throws<ArgumentException>("detachedContent", () => SignDetached(stream, GetCoseSigner(DefaultKey, DefaultHash)));
}
[Fact]
public void SignWithUnreadableStream()
{
using Stream stream = GetTestStream(s_sampleContent, StreamKind.Unreadable);
Assert.Throws<ArgumentException>("detachedContent", () => SignDetached(stream, GetCoseSigner(DefaultKey, DefaultHash)));
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Test.Cryptography;
using Xunit;
using static System.Security.Cryptography.Cose.Tests.CoseTestHelpers;
namespace System.Security.Cryptography.Cose.Tests
{
// Tests that apply to all [Try]Sign overloads using one single signer.
public abstract class CoseMessageTests_Sign<T> where T : AsymmetricAlgorithm
{
internal virtual bool OnlySupportsDetachedContent => false;
internal CoseAlgorithm DefaultAlgorithm => CoseAlgorithms[CoseAlgorithms.Count - 1];
internal T DefaultKey => GetKeyHashPaddingTriplet<T>(CoseAlgorithms[CoseAlgorithms.Count - 1]).Key;
internal HashAlgorithmName DefaultHash => GetKeyHashPaddingTriplet<T>(CoseAlgorithms[CoseAlgorithms.Count - 1]).Hash;
internal abstract List<CoseAlgorithm> CoseAlgorithms { get; }
internal abstract CoseMessageKind MessageKind { get; }
internal abstract void AddSignature(CoseMultiSignMessage msg, byte[] content, CoseSigner signer, byte[]? associatedData = null);
internal abstract CoseMessage Decode(ReadOnlySpan<byte> cborPayload);
internal abstract byte[] Sign(byte[] content,
CoseSigner signer,
CoseHeaderMap? protectedHeaders = null,
CoseHeaderMap? unprotectedHeaders = null,
byte[]? associatedData = null,
bool isDetached = false);
internal abstract bool Verify(CoseMessage msg, T key, byte[] content, byte[]? associatedData = null);
internal IEnumerable<(T Key, HashAlgorithmName Hash, CoseAlgorithm Algorithm, RSASignaturePadding? Padding)> GetKeyHashAlgorithmPaddingQuadruplet(bool useNonPrivateKey = false)
{
foreach (var algorithm in CoseAlgorithms)
{
var keyHashTriplet = GetKeyHashPaddingTriplet<T>(algorithm, useNonPrivateKey);
yield return (keyHashTriplet.Key, keyHashTriplet.Hash, algorithm, keyHashTriplet.Padding);
}
}
internal void AssertCoseSignMessage(
ReadOnlySpan<byte> encodedMsg,
ReadOnlySpan<byte> expectedContent,
AsymmetricAlgorithm key,
CoseAlgorithm algorithm,
List<(CoseHeaderLabel, ReadOnlyMemory<byte>)>? expectedProtectedHeaders = null,
List<(CoseHeaderLabel, ReadOnlyMemory<byte>)>? expectedUnprotectedHeaders = null,
bool? expectedDetachedContent = null,
List<(CoseHeaderLabel, ReadOnlyMemory<byte>)>? expectedMultiSignBodyProtectedHeaders = null,
List<(CoseHeaderLabel, ReadOnlyMemory<byte>)>? expectedMultiSignBodyUnprotectedHeaders = null,
int expectedSignatures = 1)
{
if (OnlySupportsDetachedContent && expectedDetachedContent != null)
{
throw new InvalidOperationException($"Don't specify {nameof(expectedDetachedContent)}, {GetType()} only supports detached content.");
}
if (MessageKind == CoseMessageKind.Sign1)
{
Assert.Null(expectedMultiSignBodyProtectedHeaders);
Assert.Null(expectedMultiSignBodyUnprotectedHeaders);
AssertSign1MessageCore(
encodedMsg,
expectedContent,
key,
algorithm,
expectedProtectedHeaders,
expectedUnprotectedHeaders,
expectedDetachedContent ?? OnlySupportsDetachedContent);
}
else if (MessageKind == CoseMessageKind.MultiSign)
{
AssertMultiSignMessageCore(
encodedMsg,
expectedContent,
key,
algorithm,
expectedSignatures,
expectedMultiSignBodyProtectedHeaders,
expectedMultiSignBodyUnprotectedHeaders,
expectedProtectedHeaders,
expectedUnprotectedHeaders,
expectedDetachedContent ?? OnlySupportsDetachedContent);
}
else
{
throw new InvalidOperationException();
}
}
[Fact]
public void SignVerify()
{
foreach ((T key, HashAlgorithmName hashAlgorithm, CoseAlgorithm algorithm, RSASignaturePadding? padding)
in GetKeyHashAlgorithmPaddingQuadruplet())
{
var signer = GetCoseSigner(key, hashAlgorithm, padding: padding);
ReadOnlySpan<byte> encodedMsg = Sign(s_sampleContent, signer);
AssertCoseSignMessage(encodedMsg, s_sampleContent, key, algorithm);
CoseMessage msg = Decode(encodedMsg);
Assert.True(Verify(msg, key, s_sampleContent));
}
}
[Fact]
public void SignWithNullContent()
{
ArgumentNullException ex = Assert.Throws<ArgumentNullException>(() => Sign(null!, GetCoseSigner(DefaultKey, DefaultHash)));
Assert.True(ex.ParamName == "embeddedContent" || ex.ParamName == "detachedContent");
}
[Theory]
[InlineData(ContentTestCase.Empty)]
[InlineData(ContentTestCase.Small)]
[InlineData(ContentTestCase.Large)]
public void SignWithValidContent(ContentTestCase @case)
{
byte[] content = GetDummyContent(@case);
ReadOnlySpan<byte> encodedMsg = Sign(content, GetCoseSigner(DefaultKey, DefaultHash));
AssertCoseSignMessage(encodedMsg, content, DefaultKey, DefaultAlgorithm);
}
[Fact]
public void SignWithNonPrivateKey()
{
foreach ((T nonPrivateKey, HashAlgorithmName hashAlgorithm, _, RSASignaturePadding? padding)
in GetKeyHashAlgorithmPaddingQuadruplet(useNonPrivateKey: true))
{
Assert.ThrowsAny<CryptographicException>(() => Sign(s_sampleContent, GetCoseSigner(nonPrivateKey, hashAlgorithm, padding: padding)));
}
}
[Theory]
[InlineData("SHA1")]
[InlineData("FOO")]
public void SignWithUnsupportedHashAlgorithm(string hashAlgorithm)
{
Assert.Throws<CryptographicException>(() => Sign(s_sampleContent, GetCoseSigner(DefaultKey, new HashAlgorithmName(hashAlgorithm))));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void SignWithIsDetached(bool isDetached)
{
if (OnlySupportsDetachedContent)
{
return;
}
ReadOnlySpan<byte> messageEncoded = Sign(s_sampleContent, GetCoseSigner(DefaultKey, DefaultHash), isDetached: isDetached);
AssertCoseSignMessage(messageEncoded, s_sampleContent, DefaultKey, DefaultAlgorithm, expectedDetachedContent: isDetached);
}
[Fact]
public void SignVerifyWithAssociatedData()
{
CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash);
byte[] associatedData = ByteUtils.HexToByteArray("11aa22bb33cc44dd55006699");
byte[]? encodedMsg = Sign(s_sampleContent, signer, associatedData: associatedData);
CoseMessage msg = Decode(encodedMsg);
Assert.False(Verify(msg, DefaultKey, s_sampleContent));
Assert.True(Verify(msg, DefaultKey, s_sampleContent, associatedData));
}
[Fact]
public void MultiSign_AddSignature()
{
if (MessageKind != CoseMessageKind.MultiSign)
{
return;
}
CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash);
CoseMessage msg = Decode(Sign(s_sampleContent, signer));
CoseMultiSignMessage multiSignMsg = Assert.IsType<CoseMultiSignMessage>(msg);
ReadOnlyCollection<CoseSignature> signatures = multiSignMsg.Signatures;
signer = GetCoseSigner(DefaultKey, DefaultHash);
AddSignature(multiSignMsg, s_sampleContent, signer);
Assert.Equal(2, signatures.Count);
// Encode/Decode
ReadOnlySpan<byte> encodedMsg = msg.Encode();
AssertCoseSignMessage(encodedMsg, s_sampleContent, DefaultKey, DefaultAlgorithm, expectedSignatures: 2);
multiSignMsg = Assert.IsType<CoseMultiSignMessage>(Decode(encodedMsg));
// Verify
MultiSignVerify(multiSignMsg, DefaultKey, s_sampleContent, expectedSignatures: 2);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void MultiSign_RemoveSignature(bool useIndex)
{
if (MessageKind != CoseMessageKind.MultiSign)
{
return;
}
CoseMessage msg = Decode(Sign(s_sampleContent, GetCoseSigner(DefaultKey, DefaultHash)));
CoseMultiSignMessage multiSignMsg = Assert.IsType<CoseMultiSignMessage>(msg);
ReadOnlyCollection<CoseSignature> signatures = multiSignMsg.Signatures;
Assert.Equal(1, signatures.Count);
RemoveSignature(multiSignMsg, signatures[0], useIndex);
Assert.Equal(0, signatures.Count);
// You can't create a message without signatures.
Assert.Throws<CryptographicException>(multiSignMsg.Encode);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void MultiSign_RemoveThenAddSignature(bool useIndex)
{
if (MessageKind != CoseMessageKind.MultiSign)
{
return;
}
CoseMessage msg = Decode(Sign(s_sampleContent, GetCoseSigner(DefaultKey, DefaultHash)));
CoseMultiSignMessage multiSignMsg = Assert.IsType<CoseMultiSignMessage>(msg);
ReadOnlyCollection<CoseSignature> signatures = multiSignMsg.Signatures;
RemoveSignature(multiSignMsg, signatures[0], useIndex);
Assert.Equal(0, signatures.Count);
CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash);
AddSignature(multiSignMsg, s_sampleContent, signer);
Assert.Equal(1, signatures.Count);
// Encode/Decode
ReadOnlySpan<byte> encodedMsg = msg.Encode();
AssertCoseSignMessage(encodedMsg, s_sampleContent, DefaultKey, DefaultAlgorithm, expectedSignatures: 1);
multiSignMsg = Assert.IsType<CoseMultiSignMessage>(Decode(encodedMsg));
// Verify
MultiSignVerify(multiSignMsg, DefaultKey, s_sampleContent, expectedSignatures: 1);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void MultiSign_AddThenRemoveSignature(bool useIndex)
{
if (MessageKind != CoseMessageKind.MultiSign)
{
return;
}
CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash);
CoseMessage msg = Decode(Sign(s_sampleContent, signer));
CoseMultiSignMessage multiSignMsg = Assert.IsType<CoseMultiSignMessage>(msg);
ReadOnlyCollection<CoseSignature> signatures = multiSignMsg.Signatures;
signer = GetCoseSigner(DefaultKey, DefaultHash);
AddSignature(multiSignMsg, s_sampleContent, signer);
Assert.Equal(2, signatures.Count);
RemoveSignature(multiSignMsg, signatures[0], useIndex);
Assert.Equal(1, signatures.Count);
// Encode/Decode
ReadOnlySpan<byte> encodedMsg = msg.Encode();
AssertCoseSignMessage(encodedMsg, s_sampleContent, DefaultKey, DefaultAlgorithm, expectedSignatures: 1);
multiSignMsg = Assert.IsType<CoseMultiSignMessage>(Decode(encodedMsg));
// Verify
MultiSignVerify(multiSignMsg, DefaultKey, s_sampleContent, expectedSignatures: 1);
}
private void RemoveSignature(CoseMultiSignMessage msg, CoseSignature signature, bool useIndex)
{
if (useIndex)
{
msg.RemoveSignature(msg.Signatures.IndexOf(signature));
}
else
{
msg.RemoveSignature(signature);
}
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Linq;
using static System.Security.Cryptography.Cose.Tests.CoseTestHelpers;
namespace System.Security.Cryptography.Cose.Tests
{
public class CoseMultiSignMessageTests_Sign : CoseMessageTests_Sign<AsymmetricAlgorithm>
{
internal override List<CoseAlgorithm> CoseAlgorithms => Enum.GetValues(typeof(CoseAlgorithm)).Cast<CoseAlgorithm>().ToList();
internal override CoseMessageKind MessageKind => CoseMessageKind.MultiSign;
internal override void AddSignature(CoseMultiSignMessage msg, byte[] content, CoseSigner signer, byte[]? associatedData = null)
=> MultiSignAddSignature(msg, content, signer, associatedData);
internal override CoseMessage Decode(ReadOnlySpan<byte> cborPayload)
=> CoseMessage.DecodeMultiSign(cborPayload);
internal override byte[] Sign(byte[] content, CoseSigner signer, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null, byte[]? associatedData = null, bool isDetached = false)
{
return isDetached ?
CoseMultiSignMessage.SignDetached(content, signer, protectedHeaders, unprotectedHeaders, associatedData) :
CoseMultiSignMessage.SignEmbedded(content, signer, protectedHeaders, unprotectedHeaders, associatedData);
}
internal override bool Verify(CoseMessage msg, AsymmetricAlgorithm key, byte[] content, byte[]? associatedData = null)
=> MultiSignVerify(msg, key, content, expectedSignatures: 1, associatedData);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册