Skip to content

Commit

Permalink
Progress on implementing signture validation in server.
Browse files Browse the repository at this point in the history
  • Loading branch information
KristofferStrube committed Jul 18, 2024
1 parent 4e664b3 commit 066897b
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="System.Formats.Cbor" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -43,6 +44,37 @@ public static Ok<byte[]> RegisterChallenge(string userName)
public static Ok<bool> 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<byte[]>? credentialList))
{
credentialList.Add(Convert.FromBase64String(registration.RawId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -69,7 +79,7 @@ private async Task CreateCredential()
],
Timeout = 360000,
Hints = "client-device",
Attestation = "none",
Attestation = AttestationConveyancePreference.Direct,
AttestationFormats = ["tpm"]
}
};
Expand All @@ -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;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@ public static Task<AuthenticatorAttestationResponse> CreateAsync(IJSRuntime jSRu

public AuthenticatorAttestationResponse(IJSRuntime jSRuntime, IJSObjectReference jSReference) : base(jSRuntime, jSReference) { }

public async Task<byte[]> GetAttestationObjectAsync()
{
IJSObjectReference helper = await webAuthenticationHelperTask.Value;
IJSObjectReference arrayBuffer = await helper.InvokeAsync<IJSObjectReference>("getAttribute", JSReference, "attestationObject");

IJSObjectReference webIDLHelper = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./_content/KristofferStrube.Blazor.WebIDL/KristofferStrube.Blazor.WebIDL.js");
IJSObjectReference uint8ArrayFromBuffer = await webIDLHelper.InvokeAsync<IJSObjectReference>("constructUint8Array", arrayBuffer);
Uint8Array uint8Array = await Uint8Array.CreateAsync(JSRuntime, uint8ArrayFromBuffer);
return await uint8Array.GetByteArrayAsync();
}

/// <summary>
/// These values are the transports that the authenticator is believed to support, or an empty sequence if the information is unavailable.
/// </summary>
public async Task<AuthenticatorTransport[]> GetTransportsAsync()
{
return await JSReference.InvokeAsync<AuthenticatorTransport[]>("getTransports");
}

public async Task<IJSObjectReference> GetAuthenticatorDataAsync()
{
return await JSReference.InvokeAsync<IJSObjectReference>("getAuthenticatorData");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace KristofferStrube.Blazor.WebAuthentication.Converters;

public class AttestationConveyancePreferenceConverter : JsonConverter<AttestationConveyancePreference>
{
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)}.")
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace KristofferStrube.Blazor.WebAuthentication.Converters;

public class AuthenticatorTransportConverter : JsonConverter<AuthenticatorTransport>
{
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)}.")
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

/// <remarks>
/// The publicKey field will be missing if pubKeyCredParams was used to negotiate a public-key algorithm that the user agent doesn’t understand.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.0-rc.1.23421.29" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.0" />
<PackageReference Include="System.Formats.Cbor" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using KristofferStrube.Blazor.WebAuthentication.Converters;
using System.Text.Json.Serialization;

namespace KristofferStrube.Blazor.WebAuthentication;

[JsonConverter(typeof(AttestationConveyancePreferenceConverter))]
public enum AttestationConveyancePreference
{
/// <summary>
/// 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.
/// </summary>
None,
/// <summary>
/// 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.
/// </summary>
Indirect,
/// <summary>
/// The Relying Party wants to receive the attestation statement as generated by the authenticator.
/// </summary>
Direct,
/// <summary>
/// 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.
/// </summary>
Enterprise
}
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace KristofferStrube.Blazor.WebAuthentication;

public class PackedAttestationFormat
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ public async Task<PublicKeyCredentialJSON> 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"
Expand Down

0 comments on commit 066897b

Please sign in to comment.