From 066897b6e299a0fc85121554ba4bc9090652f3a3 Mon Sep 17 00:00:00 2001 From: KristofferStrube Date: Fri, 19 Jul 2024 00:45:59 +0200 Subject: [PATCH] Progress on implementing signture validation in server. --- ...Strube.Blazor.WebAuthentication.API.csproj | 1 + .../WebAuthenticationAPI.cs | 32 ++++++++++++++++++ .../Pages/Index.razor.cs | 24 ++++++++++++-- .../AuthenticatorAttestationResponse.cs | 19 +++++++++++ ...ttestationConveyancePreferenceConverter.cs | 31 +++++++++++++++++ .../AuthenticatorTransportConverter.cs | 31 +++++++++++++++++ .../AuthenticatorAttestationResponseJSON.cs | 2 +- ...fferStrube.Blazor.WebAuthentication.csproj | 3 +- .../AttestationConveyancePreference.cs | 33 +++++++++++++++++++ .../Options/AuthenticatorTransport.cs | 6 +++- .../PublicKeyCredentialCreationOptions.cs | 4 +-- .../PackedAttestationFormat.cs | 5 +++ .../PublicKeyCredential.cs | 4 +-- 13 files changed, 185 insertions(+), 10 deletions(-) create mode 100644 src/KristofferStrube.Blazor.WebAuthentication/Converters/AttestationConveyancePreferenceConverter.cs create mode 100644 src/KristofferStrube.Blazor.WebAuthentication/Converters/AuthenticatorTransportConverter.cs create mode 100644 src/KristofferStrube.Blazor.WebAuthentication/Options/AttestationConveyancePreference.cs create mode 100644 src/KristofferStrube.Blazor.WebAuthentication/PackedAttestationFormat.cs diff --git a/samples/KristofferStrube.Blazor.WebAuthentication.API/KristofferStrube.Blazor.WebAuthentication.API.csproj b/samples/KristofferStrube.Blazor.WebAuthentication.API/KristofferStrube.Blazor.WebAuthentication.API.csproj index 21c8b31..c024bda 100644 --- a/samples/KristofferStrube.Blazor.WebAuthentication.API/KristofferStrube.Blazor.WebAuthentication.API.csproj +++ b/samples/KristofferStrube.Blazor.WebAuthentication.API/KristofferStrube.Blazor.WebAuthentication.API.csproj @@ -10,6 +10,7 @@ + diff --git a/samples/KristofferStrube.Blazor.WebAuthentication.API/WebAuthenticationAPI.cs b/samples/KristofferStrube.Blazor.WebAuthentication.API/WebAuthenticationAPI.cs index e9efd1c..9405035 100644 --- a/samples/KristofferStrube.Blazor.WebAuthentication.API/WebAuthenticationAPI.cs +++ b/samples/KristofferStrube.Blazor.WebAuthentication.API/WebAuthenticationAPI.cs @@ -1,6 +1,7 @@ using KristofferStrube.Blazor.WebAuthentication.JSONRepresentations; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; +using System.Formats.Cbor; using System.Security.Cryptography; namespace KristofferStrube.Blazor.WebAuthentication.API; @@ -43,6 +44,37 @@ public static Ok RegisterChallenge(string userName) public static Ok Register(string userName, [FromBody] RegistrationResponseJSON registration) { // TODO: Do actual integrity validation on registration. + if (!Challenges.TryGetValue(userName, out byte[]? originalChallenge)) + { + return TypedResults.Ok(false); + } + + CborReader cborReader = new(Convert.FromBase64String(registration.Response.AttestationObject)); + CborReaderState state = cborReader.PeekState(); + Console.WriteLine(state); + int? mapSize = cborReader.ReadStartMap(); + Console.WriteLine(mapSize); + state = cborReader.PeekState(); + Console.WriteLine(state); + string fmt = cborReader.ReadTextString(); + Console.WriteLine(fmt); + state = cborReader.PeekState(); + Console.WriteLine(state); + string fmtType = cborReader.ReadTextString(); + Console.WriteLine(fmtType); + state = cborReader.PeekState(); + Console.WriteLine(state); + string attStmt = cborReader.ReadTextString(); + Console.WriteLine(attStmt); + state = cborReader.PeekState(); + Console.WriteLine(state); + Console.WriteLine(cborReader.ReadStartMap()); + state = cborReader.PeekState(); + Console.WriteLine(state); + _ = cborReader.ReadTextString(); + Console.WriteLine(cborReader.PeekState()); + Console.WriteLine((COSEAlgorithm)(-(long)cborReader.ReadCborNegativeIntegerRepresentation() - 1)); + if (Credentials.TryGetValue(userName, out List? credentialList)) { credentialList.Add(Convert.FromBase64String(registration.RawId)); diff --git a/samples/KristofferStrube.Blazor.WebAuthentication.WasmExample/Pages/Index.razor.cs b/samples/KristofferStrube.Blazor.WebAuthentication.WasmExample/Pages/Index.razor.cs index 74d5f4b..d8672c3 100644 --- a/samples/KristofferStrube.Blazor.WebAuthentication.WasmExample/Pages/Index.razor.cs +++ b/samples/KristofferStrube.Blazor.WebAuthentication.WasmExample/Pages/Index.razor.cs @@ -3,7 +3,9 @@ using KristofferStrube.Blazor.WebIDL.Exceptions; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; +using System; using System.Text; +using System.Threading.Channels; using static KristofferStrube.Blazor.WebAuthentication.WasmExample.WebAuthenticationClient; namespace KristofferStrube.Blazor.WebAuthentication.WasmExample.Pages; @@ -39,6 +41,14 @@ private async Task CreateCredential() byte[] userId = Encoding.ASCII.GetBytes(username); challenge = await WebAuthenticationClient.RegisterChallenge(username); + + if (challenge is null) + { + errorMessage = "Was not succesfull in registering a challenge before making credentials."; + credential = null; + return; + } + CredentialCreationOptions options = new() { PublicKey = new PublicKeyCredentialCreationOptions() @@ -69,7 +79,7 @@ private async Task CreateCredential() ], Timeout = 360000, Hints = "client-device", - Attestation = "none", + Attestation = AttestationConveyancePreference.Direct, AttestationFormats = ["tpm"] } }; @@ -83,8 +93,16 @@ private async Task CreateCredential() PublicKeyCredentialJSON registrationResponse = await credential.ToJSONAsync(); if (registrationResponse is RegistrationResponseJSON { } registration) { - await WebAuthenticationClient.Register(username, registration); - publicKey = registration.Response.PublicKey is not null ? Convert.FromBase64String(registration.Response.PublicKey) : null; + bool succesfullyRegistered = await WebAuthenticationClient.Register(username, registration); + if (succesfullyRegistered) + { + publicKey = registration.Response.PublicKey is not null ? Convert.FromBase64String(registration.Response.PublicKey) : null; + } + else + { + errorMessage = "Was not successfull in registering the credentials"; + credential = null; + } } } diff --git a/src/KristofferStrube.Blazor.WebAuthentication/AuthenticatorAttestationResponse.cs b/src/KristofferStrube.Blazor.WebAuthentication/AuthenticatorAttestationResponse.cs index 5e97508..fca8515 100644 --- a/src/KristofferStrube.Blazor.WebAuthentication/AuthenticatorAttestationResponse.cs +++ b/src/KristofferStrube.Blazor.WebAuthentication/AuthenticatorAttestationResponse.cs @@ -12,6 +12,25 @@ public static Task CreateAsync(IJSRuntime jSRu public AuthenticatorAttestationResponse(IJSRuntime jSRuntime, IJSObjectReference jSReference) : base(jSRuntime, jSReference) { } + public async Task GetAttestationObjectAsync() + { + IJSObjectReference helper = await webAuthenticationHelperTask.Value; + IJSObjectReference arrayBuffer = await helper.InvokeAsync("getAttribute", JSReference, "attestationObject"); + + IJSObjectReference webIDLHelper = await JSRuntime.InvokeAsync("import", "./_content/KristofferStrube.Blazor.WebIDL/KristofferStrube.Blazor.WebIDL.js"); + IJSObjectReference uint8ArrayFromBuffer = await webIDLHelper.InvokeAsync("constructUint8Array", arrayBuffer); + Uint8Array uint8Array = await Uint8Array.CreateAsync(JSRuntime, uint8ArrayFromBuffer); + return await uint8Array.GetByteArrayAsync(); + } + + /// + /// These values are the transports that the authenticator is believed to support, or an empty sequence if the information is unavailable. + /// + public async Task GetTransportsAsync() + { + return await JSReference.InvokeAsync("getTransports"); + } + public async Task GetAuthenticatorDataAsync() { return await JSReference.InvokeAsync("getAuthenticatorData"); diff --git a/src/KristofferStrube.Blazor.WebAuthentication/Converters/AttestationConveyancePreferenceConverter.cs b/src/KristofferStrube.Blazor.WebAuthentication/Converters/AttestationConveyancePreferenceConverter.cs new file mode 100644 index 0000000..2d57271 --- /dev/null +++ b/src/KristofferStrube.Blazor.WebAuthentication/Converters/AttestationConveyancePreferenceConverter.cs @@ -0,0 +1,31 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace KristofferStrube.Blazor.WebAuthentication.Converters; + +public class AttestationConveyancePreferenceConverter : JsonConverter +{ + public override AttestationConveyancePreference Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetString() switch + { + "none" => AttestationConveyancePreference.None, + "indirect" => AttestationConveyancePreference.Indirect, + "direct" => AttestationConveyancePreference.Direct, + "enterprise" => AttestationConveyancePreference.Enterprise, + var value => throw new ArgumentException($"Value '{value}' was not a valid {nameof(PublicKeyCredentialType)}.") + }; + } + + public override void Write(Utf8JsonWriter writer, AttestationConveyancePreference value, JsonSerializerOptions options) + { + writer.WriteStringValue(value switch + { + AttestationConveyancePreference.None => "none", + AttestationConveyancePreference.Indirect => "indirect", + AttestationConveyancePreference.Direct => "direct", + AttestationConveyancePreference.Enterprise => "enterprise", + _ => throw new ArgumentException($"Value '{value}' was not a valid {nameof(PublicKeyCredentialType)}.") + }); + } +} diff --git a/src/KristofferStrube.Blazor.WebAuthentication/Converters/AuthenticatorTransportConverter.cs b/src/KristofferStrube.Blazor.WebAuthentication/Converters/AuthenticatorTransportConverter.cs new file mode 100644 index 0000000..9f6b2d8 --- /dev/null +++ b/src/KristofferStrube.Blazor.WebAuthentication/Converters/AuthenticatorTransportConverter.cs @@ -0,0 +1,31 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace KristofferStrube.Blazor.WebAuthentication.Converters; + +public class AuthenticatorTransportConverter : JsonConverter +{ + public override AuthenticatorTransport Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetString() switch + { + "usb" => AuthenticatorTransport.Usb, + "nfc" => AuthenticatorTransport.Nfc, + "ble" => AuthenticatorTransport.Ble, + "internal" => AuthenticatorTransport.Internal, + var value => throw new ArgumentException($"Value '{value}' was not a valid {nameof(PublicKeyCredentialType)}.") + }; + } + + public override void Write(Utf8JsonWriter writer, AuthenticatorTransport value, JsonSerializerOptions options) + { + writer.WriteStringValue(value switch + { + AuthenticatorTransport.Usb => "usb", + AuthenticatorTransport.Nfc => "nfc", + AuthenticatorTransport.Ble => "ble", + AuthenticatorTransport.Internal => "internal", + _ => throw new ArgumentException($"Value '{value}' was not a valid {nameof(PublicKeyCredentialType)}.") + }); + } +} diff --git a/src/KristofferStrube.Blazor.WebAuthentication/JSONRepresentations/AuthenticatorAttestationResponseJSON.cs b/src/KristofferStrube.Blazor.WebAuthentication/JSONRepresentations/AuthenticatorAttestationResponseJSON.cs index 9f4e510..3dfbacb 100644 --- a/src/KristofferStrube.Blazor.WebAuthentication/JSONRepresentations/AuthenticatorAttestationResponseJSON.cs +++ b/src/KristofferStrube.Blazor.WebAuthentication/JSONRepresentations/AuthenticatorAttestationResponseJSON.cs @@ -11,7 +11,7 @@ public class AuthenticatorAttestationResponseJSON public required string AuthenticatorData { get; set; } [JsonPropertyName("transports")] - public required string[] Transports { get; set; } + public required AuthenticatorTransport[] Transports { get; set; } /// /// The publicKey field will be missing if pubKeyCredParams was used to negotiate a public-key algorithm that the user agent doesn’t understand. diff --git a/src/KristofferStrube.Blazor.WebAuthentication/KristofferStrube.Blazor.WebAuthentication.csproj b/src/KristofferStrube.Blazor.WebAuthentication/KristofferStrube.Blazor.WebAuthentication.csproj index 4c8bdac..82e455a 100644 --- a/src/KristofferStrube.Blazor.WebAuthentication/KristofferStrube.Blazor.WebAuthentication.csproj +++ b/src/KristofferStrube.Blazor.WebAuthentication/KristofferStrube.Blazor.WebAuthentication.csproj @@ -12,7 +12,8 @@ - + + diff --git a/src/KristofferStrube.Blazor.WebAuthentication/Options/AttestationConveyancePreference.cs b/src/KristofferStrube.Blazor.WebAuthentication/Options/AttestationConveyancePreference.cs new file mode 100644 index 0000000..4af513e --- /dev/null +++ b/src/KristofferStrube.Blazor.WebAuthentication/Options/AttestationConveyancePreference.cs @@ -0,0 +1,33 @@ +using KristofferStrube.Blazor.WebAuthentication.Converters; +using System.Text.Json.Serialization; + +namespace KristofferStrube.Blazor.WebAuthentication; + +[JsonConverter(typeof(AttestationConveyancePreferenceConverter))] +public enum AttestationConveyancePreference +{ + /// + /// The Relying Party is not interested in authenticator attestation. + /// For example, in order to potentially avoid having to obtain user consent to relay identifying information to the Relying Party, + /// or to save a roundtrip to an Attestation CA or Anonymization CA. + /// If the authenticator generates an attestation statement that is not a self attestation, the client will replace it with a None attestation statement. + /// + None, + /// + /// The Relying Party wants to receive a verifiable attestation statement, + /// but allows the client to decide how to obtain such an attestation statement. + /// The client can replace an authenticator-generated attestation statement with one generated by an Anonymization CA, in order to protect the user’s privacy, + /// or to assist Relying Parties with attestation verification in a heterogeneous ecosystem. + /// + Indirect, + /// + /// The Relying Party wants to receive the attestation statement as generated by the authenticator. + /// + Direct, + /// + /// The Relying Party wants to receive an attestation statement that may include uniquely identifying information. + /// This is intended for controlled deployments within an enterprise where the organization wishes to tie registrations to specific authenticators. + /// User agents will provide such an attestation unless the user agent or authenticator configuration permits it for the requested RP ID. + /// + Enterprise +} diff --git a/src/KristofferStrube.Blazor.WebAuthentication/Options/AuthenticatorTransport.cs b/src/KristofferStrube.Blazor.WebAuthentication/Options/AuthenticatorTransport.cs index 1e62fac..e7b78ca 100644 --- a/src/KristofferStrube.Blazor.WebAuthentication/Options/AuthenticatorTransport.cs +++ b/src/KristofferStrube.Blazor.WebAuthentication/Options/AuthenticatorTransport.cs @@ -1,5 +1,9 @@ -namespace KristofferStrube.Blazor.WebAuthentication; +using KristofferStrube.Blazor.WebAuthentication.Converters; +using System.Text.Json.Serialization; +namespace KristofferStrube.Blazor.WebAuthentication; + +[JsonConverter(typeof(AuthenticatorTransportConverter))] public enum AuthenticatorTransport { Usb, diff --git a/src/KristofferStrube.Blazor.WebAuthentication/Options/PublicKeyCredentialCreationOptions.cs b/src/KristofferStrube.Blazor.WebAuthentication/Options/PublicKeyCredentialCreationOptions.cs index 0c0d0e3..864b484 100644 --- a/src/KristofferStrube.Blazor.WebAuthentication/Options/PublicKeyCredentialCreationOptions.cs +++ b/src/KristofferStrube.Blazor.WebAuthentication/Options/PublicKeyCredentialCreationOptions.cs @@ -23,9 +23,9 @@ public class PublicKeyCredentialCreationOptions [JsonPropertyName("hints")] public string? Hints { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("attestation")] - public string? Attestation { get; set; } + public AttestationConveyancePreference Attestation { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("attestationFormats")] diff --git a/src/KristofferStrube.Blazor.WebAuthentication/PackedAttestationFormat.cs b/src/KristofferStrube.Blazor.WebAuthentication/PackedAttestationFormat.cs new file mode 100644 index 0000000..e016d1c --- /dev/null +++ b/src/KristofferStrube.Blazor.WebAuthentication/PackedAttestationFormat.cs @@ -0,0 +1,5 @@ +namespace KristofferStrube.Blazor.WebAuthentication; + +public class PackedAttestationFormat +{ +} diff --git a/src/KristofferStrube.Blazor.WebAuthentication/PublicKeyCredential.cs b/src/KristofferStrube.Blazor.WebAuthentication/PublicKeyCredential.cs index d2ce366..608531e 100644 --- a/src/KristofferStrube.Blazor.WebAuthentication/PublicKeyCredential.cs +++ b/src/KristofferStrube.Blazor.WebAuthentication/PublicKeyCredential.cs @@ -75,11 +75,11 @@ public async Task ToJSONAsync() Response = new() { ClientDataJSON = Convert.ToBase64String(await authenticatorAttestation.GetClientDataJSONAsArrayAsync()), - Transports = ["dummy data"], + Transports = await authenticatorAttestation.GetTransportsAsync(), AuthenticatorData = Convert.ToBase64String(await authenticatorAttestation.GetAuthenticatorDataAsArrayAsync()), PublicKey = Convert.ToBase64String(await authenticatorAttestation.GetPublicKeyAsArrayAsync()), PublicKeyAlgorithm = (long)await authenticatorAttestation.GetPublicKeyAlgorithmAsync(), - AttestationObject = "dummy data", + AttestationObject = Convert.ToBase64String(await authenticatorAttestation.GetAttestationObjectAsync()), }, ClientExtensionResults = new(), Type = "public-key"