Skip to content

Commit 285a611

Browse files
authored
Enable C# 11 and embed base64 encoded certificates as UTF-8 data (#414)
* Embed base64 encoded certificates as UTF-8 data * Use .NET 7.0 SDK * Install net6.0 and net7.0 SDKs * Use list patterns
1 parent eddb69e commit 285a611

File tree

5 files changed

+69
-57
lines changed

5 files changed

+69
-57
lines changed

Src/Fido2/Extensions/X509CertificateHelper.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
11
using System;
2+
using System.Buffers;
3+
using System.Buffers.Text;
24
using System.Security.Cryptography.X509Certificates;
35

46
namespace Fido2NetLib;
57

68
internal static class X509CertificateHelper
79
{
8-
public static X509Certificate2 CreateFromBase64String(string base64String)
10+
public static X509Certificate2 CreateFromBase64String(ReadOnlySpan<byte> base64String)
911
{
10-
byte[] rawData;
12+
var rentedBuffer = ArrayPool<byte>.Shared.Rent(Base64.GetMaxDecodedFromUtf8Length(base64String.Length));
13+
14+
if (Base64.DecodeFromUtf8(base64String, rentedBuffer, out _, out int bytesWritten) != OperationStatus.Done)
15+
{
16+
ArrayPool<byte>.Shared.Return(rentedBuffer, true);
17+
18+
throw new Exception("Invalid base64 data found parsing X509 certificate");
19+
}
1120

1221
try
1322
{
14-
rawData = Convert.FromBase64String(base64String);
23+
return CreateFromRawData(rentedBuffer.AsSpan(0, bytesWritten));
1524
}
16-
catch
25+
finally
1726
{
18-
throw new Exception("Invalid base64 data found parsing X509 certificate");
27+
ArrayPool<byte>.Shared.Return(rentedBuffer, true);
1928
}
20-
21-
return CreateFromRawData(rawData);
2229
}
2330

24-
public static X509Certificate2 CreateFromRawData(byte[] rawData)
31+
public static X509Certificate2 CreateFromRawData(ReadOnlySpan<byte> rawData)
2532
{
2633
try
2734
{

Src/Fido2/Fido2.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<RootNamespace>Fido2NetLib</RootNamespace>
66
<IsTrimmable>true</IsTrimmable>
77
<NoWarn>IDE0057</NoWarn>
8+
<LangVersion>11</LangVersion>
89
</PropertyGroup>
910

1011
<ItemGroup>

Src/Fido2/Metadata/ConformanceMetadataRepository.cs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,20 @@ namespace Fido2NetLib;
1818

1919
public sealed class ConformanceMetadataRepository : IMetadataRepository
2020
{
21-
private const string ROOT_CERT = "MIICaDCCAe6gAwIBAgIPBCqih0DiJLW7+UHXx/o1MAoGCCqGSM49BAMDMGcxCzAJ" +
22-
"BgNVBAYTAlVTMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMScwJQYDVQQLDB5GQUtF" +
23-
"IE1ldGFkYXRhIDMgQkxPQiBST09UIEZBS0UxFzAVBgNVBAMMDkZBS0UgUm9vdCBG" +
24-
"QUtFMB4XDTE3MDIwMTAwMDAwMFoXDTQ1MDEzMTIzNTk1OVowZzELMAkGA1UEBhMC" +
25-
"VVMxFjAUBgNVBAoMDUZJRE8gQWxsaWFuY2UxJzAlBgNVBAsMHkZBS0UgTWV0YWRh" +
26-
"dGEgMyBCTE9CIFJPT1QgRkFLRTEXMBUGA1UEAwwORkFLRSBSb290IEZBS0UwdjAQ" +
27-
"BgcqhkjOPQIBBgUrgQQAIgNiAASKYiz3YltC6+lmxhPKwA1WFZlIqnX8yL5RybSL" +
28-
"TKFAPEQeTD9O6mOz+tg8wcSdnVxHzwnXiQKJwhrav70rKc2ierQi/4QUrdsPes8T" +
29-
"EirZOkCVJurpDFbXZOgs++pa4XmjYDBeMAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E" +
30-
"BTADAQH/MB0GA1UdDgQWBBQGcfeCs0Y8D+lh6U5B2xSrR74eHTAfBgNVHSMEGDAW" +
31-
"gBQGcfeCs0Y8D+lh6U5B2xSrR74eHTAKBggqhkjOPQQDAwNoADBlAjEA/xFsgri0" +
32-
"xubSa3y3v5ormpPqCwfqn9s0MLBAtzCIgxQ/zkzPKctkiwoPtDzI51KnAjAmeMyg" +
33-
"X2S5Ht8+e+EQnezLJBJXtnkRWY+Zt491wgt/AwSs5PHHMv5QgjELOuMxQBc=";
21+
private static ReadOnlySpan<byte> ROOT_CERT =>
22+
"MIICaDCCAe6gAwIBAgIPBCqih0DiJLW7+UHXx/o1MAoGCCqGSM49BAMDMGcxCzAJ"u8 +
23+
"BgNVBAYTAlVTMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMScwJQYDVQQLDB5GQUtF"u8 +
24+
"IE1ldGFkYXRhIDMgQkxPQiBST09UIEZBS0UxFzAVBgNVBAMMDkZBS0UgUm9vdCBG"u8 +
25+
"QUtFMB4XDTE3MDIwMTAwMDAwMFoXDTQ1MDEzMTIzNTk1OVowZzELMAkGA1UEBhMC"u8 +
26+
"VVMxFjAUBgNVBAoMDUZJRE8gQWxsaWFuY2UxJzAlBgNVBAsMHkZBS0UgTWV0YWRh"u8 +
27+
"dGEgMyBCTE9CIFJPT1QgRkFLRTEXMBUGA1UEAwwORkFLRSBSb290IEZBS0UwdjAQ"u8 +
28+
"BgcqhkjOPQIBBgUrgQQAIgNiAASKYiz3YltC6+lmxhPKwA1WFZlIqnX8yL5RybSL"u8 +
29+
"TKFAPEQeTD9O6mOz+tg8wcSdnVxHzwnXiQKJwhrav70rKc2ierQi/4QUrdsPes8T"u8 +
30+
"EirZOkCVJurpDFbXZOgs++pa4XmjYDBeMAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E"u8 +
31+
"BTADAQH/MB0GA1UdDgQWBBQGcfeCs0Y8D+lh6U5B2xSrR74eHTAfBgNVHSMEGDAW"u8 +
32+
"gBQGcfeCs0Y8D+lh6U5B2xSrR74eHTAKBggqhkjOPQQDAwNoADBlAjEA/xFsgri0"u8 +
33+
"xubSa3y3v5ormpPqCwfqn9s0MLBAtzCIgxQ/zkzPKctkiwoPtDzI51KnAjAmeMyg"u8 +
34+
"X2S5Ht8+e+EQnezLJBJXtnkRWY+Zt491wgt/AwSs5PHHMv5QgjELOuMxQBc="u8;
3435

3536
private readonly HttpClient _httpClient;
3637

@@ -212,10 +213,8 @@ public async Task<MetadataBLOBPayload> DeserializeAndValidateBlob(string rawBLOB
212213
if (rootCert.Thumbprint.Equals(certChain.ChainElements[^1].Certificate.Thumbprint, StringComparison.Ordinal) &&
213214
// and that the number of elements in the chain accounts for what was in x5c plus the root we added
214215
certChain.ChainElements.Count == (x5cRawKeys.Length + 1) &&
215-
// and that the root cert has exactly one status listed against it
216-
certChain.ChainElements[^1].ChainElementStatus.Length == 1 &&
217-
// and that that status is a status of exactly UntrustedRoot
218-
certChain.ChainElements[^1].ChainElementStatus[0].Status == X509ChainStatusFlags.UntrustedRoot)
216+
// and that the root cert has exactly one status with the value of UntrustedRoot
217+
certChain.ChainElements[^1].ChainElementStatus is [ { Status: X509ChainStatusFlags.UntrustedRoot } ])
219218
{
220219
// if we are good so far, that is a good sign
221220
certChainIsValid = true;

Src/Fido2/Metadata/Fido2MetadataServiceRepository.cs

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,26 @@ namespace Fido2NetLib;
1616

1717
public sealed class Fido2MetadataServiceRepository : IMetadataRepository
1818
{
19-
private const string ROOT_CERT =
20-
"MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G" +
21-
"A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp" +
22-
"Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4" +
23-
"MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG" +
24-
"A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI" +
25-
"hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8" +
26-
"RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT" +
27-
"gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm" +
28-
"KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd" +
29-
"QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ" +
30-
"XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw" +
31-
"DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o" +
32-
"LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU" +
33-
"RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp" +
34-
"jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK" +
35-
"6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX" +
36-
"mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs" +
37-
"Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH" +
38-
"WD9f";
19+
private ReadOnlySpan<byte> ROOT_CERT =>
20+
"MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G"u8 +
21+
"A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp"u8 +
22+
"Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4"u8 +
23+
"MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG"u8 +
24+
"A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI"u8 +
25+
"hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8"u8 +
26+
"RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT"u8 +
27+
"gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm"u8 +
28+
"KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd"u8 +
29+
"QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ"u8 +
30+
"XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw"u8 +
31+
"DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o"u8 +
32+
"LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU"u8 +
33+
"RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp"u8 +
34+
"jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK"u8 +
35+
"6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX"u8 +
36+
"mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs"u8 +
37+
"Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH"u8 +
38+
"WD9f"u8;
3939

4040
private readonly string _blobUrl = "https://mds.fidoalliance.org/";
4141
private readonly IHttpClientFactory _httpClientFactory;
@@ -184,10 +184,8 @@ private async Task<MetadataBLOBPayload> DeserializeAndValidateBlobAsync(string r
184184
if (rootCert.Thumbprint == certChain.ChainElements[^1].Certificate.Thumbprint &&
185185
// and that the number of elements in the chain accounts for what was in x5c plus the root we added
186186
certChain.ChainElements.Count == (x5cRawKeys.Length + 1) &&
187-
// and that the root cert has exactly one status listed against it
188-
certChain.ChainElements[^1].ChainElementStatus.Length == 1 &&
189-
// and that that status is a status of exactly UntrustedRoot
190-
certChain.ChainElements[^1].ChainElementStatus[0].Status == X509ChainStatusFlags.UntrustedRoot)
187+
// and that the root cert has exactly one status with the value of UntrustedRoot
188+
certChain.ChainElements[^1].ChainElementStatus is [ { Status: X509ChainStatusFlags.UntrustedRoot } ])
191189
{
192190
// if we are good so far, that is a good sign
193191
certChainIsValid = true;

azure-pipelines.yml

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ jobs:
1414
vmImage: $(TargetWindowsVMImage)
1515
steps:
1616
- task: UseDotNet@2
17-
displayName: 'Use .NET 6.0 SDK'
17+
displayName: 'Use .NET 7.0 SDK'
1818
inputs:
1919
packageType: 'sdk'
20-
version: '6.0.x'
20+
version: '7.0.x'
2121
installationPath: $(Agent.ToolsDirectory)/dotnet-demo
2222
- task: DotNetCoreCLI@2
2323
displayName: 'dotnet restore'
@@ -50,11 +50,10 @@ jobs:
5050
vmImage: $(TargetWindowsVMImage)
5151
steps:
5252
- task: UseDotNet@2
53-
displayName: 'Use .NET 6.0 SDK'
53+
displayName: 'Use .NET 7.0 SDK'
5454
inputs:
5555
packageType: 'sdk'
56-
version: '6.0.x'
57-
installationPath: $(Agent.ToolsDirectory)/dotnet-demo
56+
version: '7.0.x'
5857
- task: DotNetCoreCLI@2
5958
displayName: 'dotnet restore'
6059
inputs:
@@ -74,11 +73,15 @@ jobs:
7473
vmImage: $(TargetWindowsVMImage)
7574
steps:
7675
- task: UseDotNet@2
77-
displayName: 'Use .NET 6.0 SDK'
76+
displayName: 'Install .NET 6.0 SDK'
7877
inputs:
7978
packageType: 'sdk'
8079
version: '6.0.x'
81-
installationPath: $(Agent.ToolsDirectory)/dotnet-unit-tests
80+
- task: UseDotNet@2
81+
displayName: 'Install .NET 7.0 SDK'
82+
inputs:
83+
packageType: 'sdk'
84+
version: '7.0.x'
8285
- task: DotNetCoreCLI@2
8386
displayName: 'dotnet restore'
8487
inputs:
@@ -132,7 +135,11 @@ jobs:
132135
inputs:
133136
packageType: 'sdk'
134137
version: '6.0.x'
135-
installationPath: $(Agent.ToolsDirectory)/dotnet-unit-tests
138+
- task: UseDotNet@2
139+
displayName: 'Use .NET 7.0 SDK'
140+
inputs:
141+
packageType: 'sdk'
142+
version: '7.0.x'
136143
- task: DotNetCoreCLI@2
137144
displayName: 'dotnet restore'
138145
inputs:

0 commit comments

Comments
 (0)