Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit d4534e7

Browse files
authored
Support for RFC 3161 cryptographic timestamps with RFC 5816 additions
This change adds API to consume, and produce, cryptographic timestamp tokens compliant with RFC 3161, or with the RFC 5816's extensions to support certificate thumbprint algorithms other than SHA-1. In addition to the low-level production and consumption, accelerator API exists for applying RFC 3161 Appendix A rules for (counter-)signing a SignedCMS SignerInfo signature.
1 parent 1fd7d79 commit d4534e7

30 files changed

+5217
-11
lines changed

src/Common/src/System/Security/Cryptography/Asn1V2.Serializer.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,50 @@ public static T Deserialize<T>(ReadOnlyMemory<byte> source, AsnEncodingRules rul
14111411
return t;
14121412
}
14131413

1414+
/// <summary>
1415+
/// Read the first ASN.1 data element from <paramref name="source"/> encoded under the specified
1416+
/// encoding rules into the typed structure.
1417+
/// </summary>
1418+
/// <typeparam name="T">
1419+
/// The type to deserialize as.
1420+
/// In order to be deserialized the type must have sequential layout, be sealed, and be composed of
1421+
/// members that are also able to be deserialized by this method.
1422+
/// </typeparam>
1423+
/// <param name="source">A view of the encoded bytes to be deserialized.</param>
1424+
/// <param name="ruleSet">The ASN.1 encoding ruleset to use for reading <paramref name="source"/>.</param>
1425+
/// <param name="bytesRead">Receives the number of bytes read from <paramref name="source"/>.</param>
1426+
/// <returns>A deserialized instance of <typeparamref name="T"/>.</returns>
1427+
/// <remarks>
1428+
/// Except for where required to for avoiding ambiguity, this method does not check that there are
1429+
/// no cycles in the type graph for <typeparamref name="T"/>. If <typeparamref name="T"/> is a
1430+
/// reference type (class) which includes a cycle in the type graph,
1431+
/// then it is possible for the data in <paramref name="source"/> to cause
1432+
/// an arbitrary extension to the maximum stack depth of this routine, leading to a
1433+
/// <see cref="StackOverflowException"/>.
1434+
///
1435+
/// If <typeparamref name="T"/> is a value type (struct) the compiler will enforce that there are no
1436+
/// cycles in the type graph.
1437+
///
1438+
/// When reference types are used the onus is on the caller of this method to prevent cycles, or to
1439+
/// mitigate the possibility of the stack overflow.
1440+
/// </remarks>
1441+
/// <exception cref="AsnSerializationConstraintException">
1442+
/// A portion of <typeparamref name="T"/> is invalid for deserialization.
1443+
/// </exception>
1444+
/// <exception cref="CryptographicException">
1445+
/// Any of the data in <paramref name="source"/> is invalid for mapping to the return value.
1446+
/// </exception>
1447+
public static T Deserialize<T>(ReadOnlyMemory<byte> source, AsnEncodingRules ruleSet, out int bytesRead)
1448+
{
1449+
Deserializer deserializer = GetDeserializer(typeof(T), null);
1450+
AsnReader reader = new AsnReader(source, ruleSet);
1451+
ReadOnlyMemory<byte> firstElement = reader.PeekEncodedValue();
1452+
1453+
T t = (T)deserializer(reader);
1454+
bytesRead = firstElement.Length;
1455+
return t;
1456+
}
1457+
14141458
/// <summary>
14151459
/// Serialize <paramref name="value"/> into an ASN.1 writer under the specified encoding rules.
14161460
/// </summary>

src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/SimpleDeserialize.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ public static void ReadOptionals(string inputHex, bool hasUtf8, bool hasIa5)
442442
}
443443

444444
[Fact]
445-
public static void TooMuchData()
445+
public static void TooMuchDataInValue()
446446
{
447447
// This is { IA5String("IA5"), UTF8String("UTF8") }, which is the opposite
448448
// of the field order of OptionalValues. SO it will see the UTF8String as null,
@@ -452,6 +452,24 @@ public static void TooMuchData()
452452
Assert.Throws<CryptographicException>(
453453
() => AsnSerializer.Deserialize<OptionalValues>(inputData, AsnEncodingRules.BER));
454454
}
455+
456+
[Fact]
457+
public static void TooMuchDataForValue()
458+
{
459+
// Two empty sequences, which is more data than one OptionalValues value.
460+
byte[] inputData = "30003000".HexToByteArray();
461+
462+
OptionalValues parsed = AsnSerializer.Deserialize<OptionalValues>(
463+
inputData,
464+
AsnEncodingRules.BER,
465+
out int bytesRead);
466+
467+
Assert.NotNull(parsed);
468+
Assert.Equal(2, bytesRead);
469+
470+
Assert.Throws<CryptographicException>(
471+
() => AsnSerializer.Deserialize<OptionalValues>(inputData, AsnEncodingRules.BER));
472+
}
455473
}
456474

457475
// RFC 3280 / ITU-T X.509

src/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<ProjectReference Include="..\..\System.Runtime\ref\System.Runtime.csproj" />
2525
<ProjectReference Include="..\..\System.Security.Cryptography.Csp\ref\System.Security.Cryptography.Csp.csproj" />
2626
<ProjectReference Include="..\..\System.Security.Cryptography.Encoding\ref\System.Security.Cryptography.Encoding.csproj" />
27+
<ProjectReference Include="..\..\System.Security.Cryptography.Primitives\ref\System.Security.Cryptography.Primitives.csproj" />
2728
<ProjectReference Include="..\..\System.Security.Cryptography.X509Certificates\ref\System.Security.Cryptography.X509Certificates.csproj" />
2829
</ItemGroup>
2930
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />

src/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.netcoreapp.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,60 @@
55
// Changes to this file must follow the http://aka.ms/api-review process.
66
// ------------------------------------------------------------------------------
77

8+
using System.Security.Cryptography.X509Certificates;
9+
810
namespace System.Security.Cryptography.Pkcs
911
{
12+
public sealed partial class Rfc3161TimestampRequest
13+
{
14+
private Rfc3161TimestampRequest() { }
15+
public int Version => throw null;
16+
public ReadOnlyMemory<byte> GetMessageHash() => throw null;
17+
public Oid HashAlgorithmId => throw null;
18+
public Oid RequestedPolicyId => throw null;
19+
public bool RequestSignerCertificate => throw null;
20+
public ReadOnlyMemory<byte>? GetNonce() => throw null;
21+
public bool HasExtensions => throw null;
22+
public X509ExtensionCollection GetExtensions() => throw null;
23+
public byte[] Encode() => throw null;
24+
public bool TryEncode(Span<byte> destination, out int bytesWritten) => throw null;
25+
public Rfc3161TimestampToken ProcessResponse(ReadOnlyMemory<byte> responseBytes, out int bytesConsumed) => throw null;
26+
public static Rfc3161TimestampRequest CreateFromData(ReadOnlySpan<byte> data, HashAlgorithmName hashAlgorithm, Oid requestedPolicyId = null, ReadOnlyMemory<byte>? nonce = null, bool requestSignerCertificates = false, X509ExtensionCollection extensions = null) => throw null;
27+
public static Rfc3161TimestampRequest CreateFromHash(ReadOnlyMemory<byte> hash, HashAlgorithmName hashAlgorithm, Oid requestedPolicyId = null, ReadOnlyMemory<byte>? nonce = null, bool requestSignerCertificates = false, X509ExtensionCollection extensions = null) => throw null;
28+
public static Rfc3161TimestampRequest CreateFromHash(ReadOnlyMemory<byte> hash, Oid hashAlgorithmId, Oid requestedPolicyId = null, ReadOnlyMemory<byte>? nonce = null, bool requestSignerCertificates = false, X509ExtensionCollection extensions = null) => throw null;
29+
public static Rfc3161TimestampRequest CreateFromSignerInfo(SignerInfo signerInfo, HashAlgorithmName hashAlgorithm, Oid requestedPolicyId = null, ReadOnlyMemory<byte>? nonce = null, bool requestSignerCertificates = false, X509ExtensionCollection extensions = null) => throw null;
30+
public static bool TryDecode(ReadOnlyMemory<byte> encodedBytes, out Rfc3161TimestampRequest request, out int bytesConsumed) => throw null;
31+
}
32+
public sealed partial class Rfc3161TimestampToken
33+
{
34+
private Rfc3161TimestampToken() { }
35+
public Rfc3161TimestampTokenInfo TokenInfo => throw null;
36+
public SignedCms AsSignedCms() => throw null;
37+
public bool VerifySignatureForHash(ReadOnlySpan<byte> hash, HashAlgorithmName hashAlgorithm, out X509Certificate2 signerCertificate, X509Certificate2Collection extraCandidates = null) => throw null;
38+
public bool VerifySignatureForHash(ReadOnlySpan<byte> hash, Oid hashAlgorithmId, out X509Certificate2 signerCertificate, X509Certificate2Collection extraCandidates = null) => throw null;
39+
public bool VerifySignatureForData(ReadOnlySpan<byte> data, out X509Certificate2 signerCertificate, X509Certificate2Collection extraCandidates = null) => throw null;
40+
public bool VerifySignatureForSignerInfo(SignerInfo signerInfo, out X509Certificate2 signerCertificate, X509Certificate2Collection extraCandidates = null) => throw null;
41+
public static bool TryDecode(ReadOnlyMemory<byte> encodedBytes, out Rfc3161TimestampToken token, out int bytesConsumed) => throw null;
42+
}
43+
public sealed partial class Rfc3161TimestampTokenInfo
44+
{
45+
public Rfc3161TimestampTokenInfo(Oid policyId, Oid hashAlgorithmId, ReadOnlyMemory<byte> messageHash, ReadOnlyMemory<byte> serialNumber, DateTimeOffset timestamp, long? accuracyInMicroseconds=null, bool isOrdering=false, ReadOnlyMemory<byte>? nonce=null, ReadOnlyMemory<byte>? timestampAuthorityName=null, X509ExtensionCollection extensions =null) { throw null; }
46+
public int Version => throw null;
47+
public Oid PolicyId=> throw null;
48+
public Oid HashAlgorithmId => throw null;
49+
public ReadOnlyMemory<byte> GetMessageHash() { throw null; }
50+
public ReadOnlyMemory<byte> GetSerialNumber() { throw null; }
51+
public DateTimeOffset Timestamp => throw null;
52+
public long? AccuracyInMicroseconds => throw null;
53+
public bool IsOrdering => throw null;
54+
public ReadOnlyMemory<byte>? GetNonce() { throw null; }
55+
public ReadOnlyMemory<byte>? GetTimestampAuthorityName() { throw null; }
56+
public bool HasExtensions => throw null;
57+
public X509ExtensionCollection GetExtensions() { throw null; }
58+
public byte[] Encode() => throw null;
59+
public bool TryEncode(Span<byte> destination, out int bytesWritten) => throw null;
60+
public static bool TryDecode(ReadOnlyMemory<byte> encodedBytes, out Rfc3161TimestampTokenInfo timestampTokenInfo, out int bytesConsumed) { throw null; }
61+
}
1062
public sealed partial class SignerInfo
1163
{
1264
public Oid SignatureAlgorithm => throw null;

src/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Helpers.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ internal static HashAlgorithmName GetDigestAlgorithm(string oidValue)
5656
}
5757
}
5858

59+
internal static string GetOidFromHashAlgorithm(HashAlgorithmName algName)
60+
{
61+
if (algName == HashAlgorithmName.MD5)
62+
return Oids.Md5;
63+
if (algName == HashAlgorithmName.SHA1)
64+
return Oids.Sha1;
65+
if (algName == HashAlgorithmName.SHA256)
66+
return Oids.Sha256;
67+
if (algName == HashAlgorithmName.SHA384)
68+
return Oids.Sha384;
69+
if (algName == HashAlgorithmName.SHA512)
70+
return Oids.Sha512;
71+
72+
throw new CryptographicException(SR.Cryptography_Cms_UnknownAlgorithm, algName.Name);
73+
}
74+
5975
/// <summary>
6076
/// This is not just a convenience wrapper for Array.Resize(). In DEBUG builds, it forces the array to move in memory even if no resize is needed. This should be used by
6177
/// helper methods that do anything of the form "call a native api once to get the estimated size, call it again to get the data and return the data in a byte[] array."

src/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Oids.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System;
6-
using System.Text;
7-
using System.Diagnostics;
8-
95
namespace Internal.Cryptography
106
{
117
internal static class Oids
@@ -24,6 +20,8 @@ internal static class Oids
2420
public const string DocumentDescription = "1.3.6.1.4.1.311.88.2.2";
2521
public const string MessageDigest = "1.2.840.113549.1.9.4";
2622
public const string CounterSigner = "1.2.840.113549.1.9.6";
23+
public const string SigningCertificate = "1.2.840.113549.1.9.16.2.12";
24+
public const string SigningCertificateV2 = "1.2.840.113549.1.9.16.2.47";
2725
public const string DocumentName = "1.3.6.1.4.1.311.88.2.1";
2826

2927
// Key wrap algorithms
@@ -64,5 +62,9 @@ internal static class Oids
6462
// Cert Extensions
6563
public const string SubjectKeyIdentifier = "2.5.29.14";
6664
public const string KeyUsage = "2.5.29.15";
65+
66+
// RFC3161 Timestamping
67+
public const string TstInfo = "1.2.840.113549.1.9.16.1.4";
68+
public const string TimeStampingPurpose = "1.3.6.1.5.5.7.3.8";
6769
}
6870
}

src/System.Security.Cryptography.Pkcs/src/Resources/Strings.resx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,21 @@
244244
<data name="Cryptography_Pkcs_PssParametersSaltMismatch" xml:space="preserve">
245245
<value>PSS salt size {0} is not supported by this platform with hash algorithm {1}.</value>
246246
</data>
247+
<data name="Cryptography_TimestampReq_BadNonce" xml:space="preserve">
248+
<value>The response from the timestamping server did not match the request nonce.</value>
249+
</data>
250+
<data name="Cryptography_TimestampReq_BadResponse" xml:space="preserve">
251+
<value>The response from the timestamping server was not understood.</value>
252+
</data>
253+
<data name="Cryptography_TimestampReq_Failure" xml:space="preserve">
254+
<value>The timestamping server did not grant the request. The request status is '{0}' with failure info '{1}'.</value>
255+
</data>
256+
<data name="Cryptography_TimestampReq_NoCertFound" xml:space="preserve">
257+
<value>The timestamping request required the TSA certificate in the response, but it was not found.</value>
258+
</data>
259+
<data name="Cryptography_TimestampReq_UnexpectedCertFound" xml:space="preserve">
260+
<value>The timestamping request required the TSA certificate not be included in the response, but certificates were present.</value>
261+
</data>
247262
<data name="InvalidOperation_DuplicateItemNotAllowed" xml:space="preserve">
248263
<value>Duplicate items are not allowed in the collection.</value>
249264
</data>

src/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,11 +225,19 @@
225225
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\CertificateChoiceAsn.cs" />
226226
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\ContentInfoAsn.cs" />
227227
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\EncapsulatedContentInfoAsn.cs" />
228+
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\GeneralName.cs" />
228229
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\IssuerAndSerialNumberAsn.cs" />
230+
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\MessageImprint.cs" />
229231
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\PssParamsAsn.cs" />
232+
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\Rfc3161Accuracy.cs" />
233+
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\Rfc3161TimeStampReq.cs" />
234+
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\Rfc3161TimeStampResp.cs" />
235+
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\Rfc3161TstInfo.cs" />
230236
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\SignedDataAsn.cs" />
231237
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\SignerIdentifierAsn.cs" />
232238
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\SignerInfoAsn.cs" />
239+
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\SigningCertificateAsn.cs" />
240+
<Compile Include="System\Security\Cryptography\Pkcs\Asn1\X509ExtensionAsn.cs" />
233241
<Compile Include="System\Security\Cryptography\Pkcs\CmsSignature.cs" />
234242
<Compile Include="System\Security\Cryptography\Pkcs\CmsSignature.ECDsa.cs" />
235243
<Compile Include="System\Security\Cryptography\Pkcs\CmsSignature.RSA.cs" />
@@ -242,6 +250,10 @@
242250
</ItemGroup>
243251
<ItemGroup Condition="'$(TargetGroup)' == 'netcoreapp'">
244252
<Compile Include="System\Security\Cryptography\Pkcs\CmsSignature.DSA.cs" />
253+
<Compile Include="System\Security\Cryptography\Pkcs\Rfc3161RequestResponseStatus.cs" />
254+
<Compile Include="System\Security\Cryptography\Pkcs\Rfc3161TimestampRequest.cs" />
255+
<Compile Include="System\Security\Cryptography\Pkcs\Rfc3161TimestampToken.cs" />
256+
<Compile Include="System\Security\Cryptography\Pkcs\Rfc3161TimestampTokenInfo.cs" />
245257
</ItemGroup>
246258
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
247259
</Project>

src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/AlgorithmIdentifierAsn.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ namespace System.Security.Cryptography.Pkcs.Asn1
1515
// parameters ANY DEFINED BY algorithm OPTIONAL }
1616
internal struct AlgorithmIdentifierAsn
1717
{
18+
internal static readonly ReadOnlyMemory<byte> ExplicitDerNull = new byte[] { 0x05, 0x00 };
19+
1820
[ObjectIdentifier(PopulateFriendlyName = true)]
1921
public Oid Algorithm;
2022

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Runtime.InteropServices;
6+
using System.Security.Cryptography.Asn1;
7+
8+
namespace System.Security.Cryptography.Pkcs.Asn1
9+
{
10+
[Choice]
11+
[StructLayout(LayoutKind.Sequential)]
12+
internal struct GeneralName
13+
{
14+
[ExpectedTag(0, ExplicitTag = true)]
15+
internal OtherName? OtherName;
16+
17+
[ExpectedTag(1, ExplicitTag = true)]
18+
[IA5String]
19+
internal string Rfc822Name;
20+
21+
[ExpectedTag(2, ExplicitTag = true)]
22+
[IA5String]
23+
internal string DnsName;
24+
25+
[ExpectedTag(3, ExplicitTag = true)]
26+
[AnyValue]
27+
internal ReadOnlyMemory<byte>? X400Address;
28+
29+
[ExpectedTag(4, ExplicitTag = true)]
30+
[AnyValue]
31+
internal ReadOnlyMemory<byte>? DirectoryName;
32+
33+
[ExpectedTag(5, ExplicitTag = true)]
34+
internal EdiPartyName? EdiPartyName;
35+
36+
[ExpectedTag(6, ExplicitTag = true)]
37+
[IA5String]
38+
internal string Uri;
39+
40+
[ExpectedTag(7, ExplicitTag = true)]
41+
[OctetString]
42+
internal ReadOnlyMemory<byte>? IPAddress;
43+
44+
[ExpectedTag(8, ExplicitTag = true)]
45+
[ObjectIdentifier]
46+
internal string RegisteredId;
47+
}
48+
49+
[StructLayout(LayoutKind.Sequential)]
50+
internal struct OtherName
51+
{
52+
internal string TypeId;
53+
54+
[ExpectedTag(0, ExplicitTag = true)]
55+
[AnyValue]
56+
internal ReadOnlyMemory<byte> Value;
57+
}
58+
59+
[StructLayout(LayoutKind.Sequential)]
60+
internal struct EdiPartyName
61+
{
62+
[OptionalValue]
63+
internal DirectoryString? NameAssigner;
64+
65+
internal DirectoryString PartyName;
66+
}
67+
68+
[Choice]
69+
[StructLayout(LayoutKind.Sequential)]
70+
internal struct DirectoryString
71+
{
72+
[ExpectedTag(TagClass.Universal, (int)UniversalTagNumber.TeletexString)]
73+
internal ReadOnlyMemory<byte>? TeletexString;
74+
75+
[PrintableString]
76+
internal string PrintableString;
77+
78+
[ExpectedTag(TagClass.Universal, (int)UniversalTagNumber.UniversalString)]
79+
internal ReadOnlyMemory<byte>? UniversalString;
80+
81+
[UTF8String]
82+
internal string Utf8String;
83+
84+
[BMPString]
85+
internal string BMPString;
86+
}
87+
}

0 commit comments

Comments
 (0)