Skip to content

Commit 3c6808e

Browse files
authored
Use BCL Base64Url implementation (#575)
* Use BCL Base64Url implementation * [Testing] React to Base64Url changes (encoder is now strict, and no longer supports Base64) * Apply formatting * Update deps * Eliminate allocation in Base64UrlConverter and improve error message when encountering invalid data * Add Base64UrlConverter.EnableRelaxedDecoding feature * Format Base64UrlConverter * Fix EnableRelaxedDecoding implementation and add test coverage
1 parent 8f50921 commit 3c6808e

28 files changed

+271
-339
lines changed

Demo/TestController.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using System.Text;
1+
using System.Buffers.Text;
2+
using System.Text;
23
using System.Text.Json;
4+
35
using Fido2NetLib;
46
using Fido2NetLib.Development;
57
using Fido2NetLib.Objects;
@@ -42,7 +44,7 @@ public OkObjectResult MakeCredentialOptionsTest([FromBody] TEST_MakeCredentialPa
4244

4345
try
4446
{
45-
username = Base64Url.Decode(opts.Username);
47+
username = Base64Url.DecodeFromChars(opts.Username);
4648
}
4749
catch (FormatException)
4850
{

Src/Fido2.Models/Base64Url.cs

Lines changed: 0 additions & 158 deletions
This file was deleted.
Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
using System.Text.Json;
1+
#nullable enable
2+
3+
using System.Buffers;
4+
using System.Buffers.Text;
5+
using System.Text.Json;
26
using System.Text.Json.Serialization;
37

48
namespace Fido2NetLib;
@@ -8,20 +12,87 @@ namespace Fido2NetLib;
812
/// </summary>
913
public sealed class Base64UrlConverter : JsonConverter<byte[]>
1014
{
15+
public static bool EnableRelaxedDecoding { get; set; }
16+
1117
public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
1218
{
13-
if (!reader.HasValueSequence)
19+
byte[]? rentedBuffer = null;
20+
21+
scoped ReadOnlySpan<byte> source;
22+
23+
if (!reader.HasValueSequence && !reader.ValueIsEscaped)
1424
{
15-
return Base64Url.DecodeUtf8(reader.ValueSpan);
25+
source = reader.ValueSpan;
1626
}
1727
else
1828
{
19-
return Base64Url.Decode(reader.GetString());
29+
int valueLength = reader.HasValueSequence ? checked((int)reader.ValueSequence.Length) : reader.ValueSpan.Length;
30+
31+
Span<byte> buffer = valueLength <= 32 ? stackalloc byte[32] : (rentedBuffer = ArrayPool<byte>.Shared.Rent(valueLength));
32+
int bytesRead = reader.CopyString(buffer);
33+
source = buffer[..bytesRead];
34+
}
35+
36+
try
37+
{
38+
return Base64Url.DecodeFromUtf8(source);
39+
}
40+
catch (Exception ex)
41+
{
42+
if (Base64.IsValid(source))
43+
{
44+
static byte[] DecodeBase64FromUtf8(scoped ReadOnlySpan<byte> source)
45+
{
46+
var rentedBuffer = ArrayPool<byte>.Shared.Rent(Base64.GetMaxDecodedFromUtf8Length(source.Length));
47+
48+
try
49+
{
50+
_ = Base64.DecodeFromUtf8(source, rentedBuffer, out _, out int written);
51+
52+
return rentedBuffer[0..written];
53+
}
54+
finally
55+
{
56+
ArrayPool<byte>.Shared.Return(rentedBuffer);
57+
}
58+
}
59+
60+
if (EnableRelaxedDecoding)
61+
{
62+
return DecodeBase64FromUtf8(source);
63+
}
64+
else
65+
{
66+
throw new JsonException("Expected data to be in Base64Url format, but received Base64 encoding instead.");
67+
}
68+
}
69+
else
70+
{
71+
throw new JsonException(ex.Message, ex);
72+
}
73+
}
74+
finally
75+
{
76+
if (rentedBuffer != null)
77+
{
78+
ArrayPool<byte>.Shared.Return(rentedBuffer);
79+
}
2080
}
2181
}
2282

2383
public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options)
2484
{
25-
writer.WriteStringValue(Base64Url.Encode(value));
85+
var rentedBuffer = ArrayPool<byte>.Shared.Rent(Base64Url.GetEncodedLength(value.Length));
86+
87+
try
88+
{
89+
Base64Url.EncodeToUtf8(value, rentedBuffer, out _, out int written);
90+
91+
writer.WriteStringValue(rentedBuffer.AsSpan(0..written));
92+
}
93+
finally
94+
{
95+
ArrayPool<byte>.Shared.Return(rentedBuffer);
96+
}
2697
}
2798
}

Src/Fido2.Models/Fido2.Models.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFrameworks>$(SupportedTargetFrameworks)</TargetFrameworks>
@@ -9,4 +9,8 @@
99
<IsPackable>true</IsPackable>
1010
</PropertyGroup>
1111

12+
<ItemGroup>
13+
<PackageReference Include="Microsoft.Bcl.Memory" Version="9.0.0" />
14+
</ItemGroup>
15+
1216
</Project>

Src/Fido2.Models/Metadata/BiometricStatusReport.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System.ComponentModel.DataAnnotations;
2-
using System.Text.Json.Serialization;
1+
using System.Text.Json.Serialization;
32

43
namespace Fido2NetLib;
54

Src/Fido2/AttestationFormat/AndroidSafetyNet.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Buffers.Text;
23
using System.Collections.Generic;
34
using System.Linq;
45
using System.Security.Cryptography;
@@ -46,7 +47,7 @@ public override async ValueTask<VerifyAttestationResult> VerifyAsync(VerifyAttes
4647

4748
try
4849
{
49-
jwtHeaderBytes = Base64Url.Decode(jwtComponents[0]);
50+
jwtHeaderBytes = Base64Url.DecodeFromChars(jwtComponents[0]);
5051
}
5152
catch (FormatException)
5253
{

Src/Fido2/AuthenticatorAssertionResponse.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Linq;
43
using System.Security.Cryptography;
54
using System.Text;

Src/Fido2/Fido2.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212

1313
<ItemGroup>
1414
<ProjectReference Include="..\Fido2.Models\Fido2.Models.csproj" />
15-
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
15+
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
1616
<PackageReference Include="NSec.Cryptography" Version="22.4.0" />
17-
<PackageReference Include="System.Formats.Cbor" Version="8.0.0" />
17+
<PackageReference Include="System.Formats.Cbor" Version="9.0.0" />
1818
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.0" />
1919
</ItemGroup>
2020

Src/Fido2/MakeAssertionParams.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.ComponentModel;
1+
using System.ComponentModel;
42

53
namespace Fido2NetLib;
64

Src/Fido2/Metadata/ConformanceMetadataRepository.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Buffers.Text;
23
using System.Collections.Generic;
34
using System.Linq;
45
using System.Net.Http;
@@ -132,7 +133,7 @@ public async Task<MetadataBLOBPayload> DeserializeAndValidateBlobAsync(string ra
132133
throw new Fido2MetadataException("The JWT does not have the 3 expected components");
133134

134135
var blobHeader = jwtParts[0];
135-
using var jsonDoc = JsonDocument.Parse(Base64Url.Decode(blobHeader));
136+
using var jsonDoc = JsonDocument.Parse(Base64Url.DecodeFromChars(blobHeader));
136137
var tokenHeader = jsonDoc.RootElement;
137138

138139
var blobAlg = tokenHeader.TryGetProperty("alg", out var algEl)
@@ -235,7 +236,7 @@ public async Task<MetadataBLOBPayload> DeserializeAndValidateBlobAsync(string ra
235236

236237
var blobPayload = ((JsonWebToken)validateTokenResult.SecurityToken).EncodedPayload;
237238

238-
MetadataBLOBPayload blob = JsonSerializer.Deserialize(Base64Url.Decode(blobPayload), FidoModelSerializerContext.Default.MetadataBLOBPayload)!;
239+
MetadataBLOBPayload blob = JsonSerializer.Deserialize(Base64Url.DecodeFromChars(blobPayload), FidoModelSerializerContext.Default.MetadataBLOBPayload)!;
239240
blob.JwtAlg = blobAlg;
240241
return blob;
241242
}

0 commit comments

Comments
 (0)