Skip to content

Commit

Permalink
Merge branch 'master' into move-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
joegoldman2 committed Oct 18, 2024
2 parents 3e74b9f + 0ea57fd commit c0c017c
Show file tree
Hide file tree
Showing 31 changed files with 634 additions and 132 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,5 @@ ASALocalRun/
/Test/coverage.netcoreapp3.1.cobertura.xml
.DS_Store
/testEnvironments.json

Demo/Conformance/
1 change: 0 additions & 1 deletion Demo/TestController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Fido2NetLib;
using Fido2NetLib.Development;
using Fido2NetLib.Objects;
Expand Down
4 changes: 2 additions & 2 deletions Demo/wwwroot/js/custom.login.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async function handleSignInSubmit(event) {
console.log("Assertion Options Object", makeAssertionOptions);

// show options error to user
if (makeAssertionOptions.status !== "ok") {
if (makeAssertionOptions.status === "error") {
console.log("Error creating assertion options");
console.log(makeAssertionOptions.errorMessage);
showErrorAlert(makeAssertionOptions.errorMessage);
Expand Down Expand Up @@ -117,7 +117,7 @@ async function verifyAssertionWithServer(assertedCredential) {
console.log("Assertion Object", response);

// show error
if (response.status !== "ok") {
if (response.status === "error") {
console.log("Error doing assertion");
console.log(response.errorMessage);
showErrorAlert(response.errorMessage);
Expand Down
4 changes: 2 additions & 2 deletions Demo/wwwroot/js/custom.register.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async function handleRegisterSubmit(event) {

console.log("Credential Options Object", makeCredentialOptions);

if (makeCredentialOptions.status !== "ok") {
if (makeCredentialOptions.status === "error") {
console.log("Error creating credential options");
console.log(makeCredentialOptions.errorMessage);
showErrorAlert(makeCredentialOptions.errorMessage);
Expand Down Expand Up @@ -140,7 +140,7 @@ async function registerNewCredential(newCredential) {
console.log("Credential Object", response);

// show error
if (response.status !== "ok") {
if (response.status === "error") {
console.log("Error creating credential");
console.log(response.errorMessage);
showErrorAlert(response.errorMessage);
Expand Down
4 changes: 2 additions & 2 deletions Demo/wwwroot/js/mfa.login.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async function handleSignInSubmit(event) {
console.log("Assertion Options Object", makeAssertionOptions);

// show options error to user
if (makeAssertionOptions.status !== "ok") {
if (makeAssertionOptions.status === "error") {
console.log("Error creating assertion options");
console.log(makeAssertionOptions.errorMessage);
showErrorAlert(makeAssertionOptions.errorMessage);
Expand Down Expand Up @@ -122,7 +122,7 @@ async function verifyAssertionWithServer(assertedCredential) {
console.log("Assertion Object", response);

// show error
if (response.status !== "ok") {
if (response.status === "error") {
console.log("Error doing assertion");
console.log(response.errorMessage);
showErrorAlert(response.errorMessage);
Expand Down
4 changes: 2 additions & 2 deletions Demo/wwwroot/js/mfa.register.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ async function handleRegisterSubmit(event) {

console.log("Credential Options Object", makeCredentialOptions);

if (makeCredentialOptions.status !== "ok") {
if (makeCredentialOptions.status === "error") {
console.log("Error creating credential options");
console.log(makeCredentialOptions.errorMessage);
showErrorAlert(makeCredentialOptions.errorMessage);
Expand Down Expand Up @@ -145,7 +145,7 @@ async function registerNewCredential(newCredential) {
console.log("Credential Object", response);

// show error
if (response.status !== "ok") {
if (response.status === "error") {
console.log("Error creating credential");
console.log(response.errorMessage);
showErrorAlert(response.errorMessage);
Expand Down
4 changes: 2 additions & 2 deletions Demo/wwwroot/js/passwordless.login.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async function handleSignInSubmit(event) {
console.log("Assertion Options Object", makeAssertionOptions);

// show options error to user
if (makeAssertionOptions.status !== "ok") {
if (makeAssertionOptions.status === "error") {
console.log("Error creating assertion options");
console.log(makeAssertionOptions.errorMessage);
showErrorAlert(makeAssertionOptions.errorMessage);
Expand Down Expand Up @@ -115,7 +115,7 @@ async function verifyAssertionWithServer(assertedCredential) {
console.log("Assertion Object", response);

// show error
if (response.status !== "ok") {
if (response.status === "error") {
console.log("Error doing assertion");
console.log(response.errorMessage);
showErrorAlert(response.errorMessage);
Expand Down
4 changes: 2 additions & 2 deletions Demo/wwwroot/js/passwordless.register.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async function handleRegisterSubmit(event) {

console.log("Credential Options Object", makeCredentialOptions);

if (makeCredentialOptions.status !== "ok") {
if (makeCredentialOptions.status === "error") {
console.log("Error creating credential options");
console.log(makeCredentialOptions.errorMessage);
showErrorAlert(makeCredentialOptions.errorMessage);
Expand Down Expand Up @@ -142,7 +142,7 @@ async function registerNewCredential(newCredential) {
console.log("Credential Object", response);

// show error
if (response.status !== "ok") {
if (response.status === "error") {
console.log("Error creating credential");
console.log(response.errorMessage);
showErrorAlert(response.errorMessage);
Expand Down
4 changes: 2 additions & 2 deletions Demo/wwwroot/js/usernameless.login.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async function handleSignInSubmit(event) {
console.log("Assertion Options Object", makeAssertionOptions);

// show options error to user
if (makeAssertionOptions.status !== "ok") {
if (makeAssertionOptions.status === "error") {
console.log("Error creating assertion options");
console.log(makeAssertionOptions.errorMessage);
showErrorAlert(makeAssertionOptions.errorMessage);
Expand Down Expand Up @@ -117,7 +117,7 @@ async function verifyAssertionWithServer(assertedCredential) {
console.log("Assertion Object", response);

// show error
if (response.status !== "ok") {
if (response.status === "error") {
console.log("Error doing assertion");
console.log(response.errorMessage);
showErrorAlert(response.errorMessage);
Expand Down
4 changes: 2 additions & 2 deletions Demo/wwwroot/js/usernameless.register.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ async function handleRegisterSubmit(event) {

console.log("Credential Options Object", makeCredentialOptions);

if (makeCredentialOptions.status !== "ok") {
if (makeCredentialOptions.status === "error") {
console.log("Error creating credential options");
console.log(makeCredentialOptions.errorMessage);
showErrorAlert(makeCredentialOptions.errorMessage);
Expand Down Expand Up @@ -143,7 +143,7 @@ async function registerNewCredential(newCredential) {
console.log("Credential Object", response);

// show error
if (response.status !== "ok") {
if (response.status === "error") {
console.log("Error creating credential");
console.log(response.errorMessage);
showErrorAlert(response.errorMessage);
Expand Down
2 changes: 1 addition & 1 deletion Src/Fido2.Models/AuthenticatorAttestationRawResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public sealed class AuthenticatorAttestationRawResponse
public byte[] RawId { get; set; }

[JsonPropertyName("type")]
public PublicKeyCredentialType Type { get; set; } = PublicKeyCredentialType.PublicKey;
public PublicKeyCredentialType? Type { get; set; }

[JsonPropertyName("response")]
public AttestationResponse Response { get; set; }
Expand Down
2 changes: 2 additions & 0 deletions Src/Fido2.Models/CredentialCreateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public static CredentialCreateOptions Create(
PubKeyCredParam.ES512,
PubKeyCredParam.RS512,
PubKeyCredParam.PS512,
PubKeyCredParam.RS1
],
AuthenticatorSelection = authenticatorSelection,
Attestation = attestationConveyancePreference,
Expand Down Expand Up @@ -185,6 +186,7 @@ public sealed class PubKeyCredParam(
public static readonly PubKeyCredParam PS384 = new(COSE.Algorithm.PS384);
public static readonly PubKeyCredParam PS512 = new(COSE.Algorithm.PS512);
public static readonly PubKeyCredParam Ed25519 = new(COSE.Algorithm.EdDSA);
public static readonly PubKeyCredParam RS1 = new(COSE.Algorithm.RS1);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ public sealed class AuthenticationExtensionsClientInputs
/// </summary>
[JsonPropertyName("example.extension.bool")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public object Example { get; set; }
public bool? Example { get; set; }

/// <summary>
/// This extension allows WebAuthn Relying Parties that have previously registered a credential using the legacy FIDO JavaScript APIs to request an assertion.
/// https://www.w3.org/TR/webauthn/#sctn-appid-extension
/// </summary>
[JsonPropertyName("appid")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
public string AppID { get; set; }

/// <summary>
Expand All @@ -33,10 +33,11 @@ public sealed class AuthenticationExtensionsClientInputs
/// <summary>
/// This extension enables use of a user verification method.
/// https://www.w3.org/TR/webauthn/#sctn-uvm-extension
/// TODO: Remove this completely as it's removed in L3
/// </summary>
[JsonPropertyName("uvm")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? UserVerificationMethod { get; set; }
public bool? UserVerificationMethod { private get; set; }

#nullable enable
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class AuthenticationExtensionsClientOutputs
/// </summary>
[JsonPropertyName("example.extension.bool")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public object Example { get; set; }
public bool? Example { get; set; }

#nullable enable

Expand Down
12 changes: 10 additions & 2 deletions Src/Fido2/AuthenticatorAssertionResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static AuthenticatorAssertionResponse Parse(AuthenticatorAssertionRawResp
/// <param name="storedSignatureCounter">The stored counter value for this CredentialId</param>
/// <param name="isUserHandleOwnerOfCredId">A function that returns <see langword="true"/> if user handle is owned by the credential ID.</param>
/// <param name="metadataService"></param>
/// <param name="requestTokenBindingId">DO NOT USE - Deprecated, but kept in code due to conformance testing tool</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
public async Task<VerifyAssertionResult> VerifyAsync(
AssertionOptions options,
Expand All @@ -61,9 +62,10 @@ public async Task<VerifyAssertionResult> VerifyAsync(
uint storedSignatureCounter,
IsUserHandleOwnerOfCredentialIdAsync isUserHandleOwnerOfCredId,
IMetadataService? metadataService,
byte[]? requestTokenBindingId,
CancellationToken cancellationToken = default)
{
BaseVerify(config.FullyQualifiedOrigins, options.Challenge);
BaseVerify(config.FullyQualifiedOrigins, options.Challenge, requestTokenBindingId);

if (Raw.Type != PublicKeyCredentialType.PublicKey)
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAssertionResponse, Fido2ErrorMessages.AssertionResponseNotPublicKey);
Expand Down Expand Up @@ -115,15 +117,20 @@ public async Task<VerifyAssertionResult> VerifyAsync(
// https://www.w3.org/TR/webauthn/#sctn-appid-extension
// FIDO AppID Extension:
// If true, the AppID was used and thus, when verifying an assertion, the Relying Party MUST expect the rpIdHash to be the hash of the AppID, not the RP ID.

var rpid = Raw.ClientExtensionResults?.AppID ?? false ? options.Extensions?.AppID : options.RpId;

byte[] hashedRpId = SHA256.HashData(Encoding.UTF8.GetBytes(rpid ?? string.Empty));
byte[] hash = SHA256.HashData(Raw.Response.ClientDataJson);

if (!authData.RpIdHash.SequenceEqual(hashedRpId))
throw new Fido2VerificationException(Fido2ErrorCode.InvalidRpidHash, Fido2ErrorMessages.InvalidRpidHash);

var conformanceTesting = metadataService != null && metadataService.ConformanceTesting();

// 14. Verify that the UP bit of the flags in authData is set.
if (!authData.UserPresent)
// Todo: Conformance testing verifies the UVP flags differently than W3C spec, simplify this by removing the mention of conformanceTesting when conformance tools are updated)
if (!authData.UserPresent && !conformanceTesting)
throw new Fido2VerificationException(Fido2ErrorCode.UserPresentFlagNotSet, Fido2ErrorMessages.UserPresentFlagNotSet);

// 15. If the Relying Party requires user verification for this assertion, verify that the UV bit of the flags in authData is set.
Expand Down Expand Up @@ -174,6 +181,7 @@ public async Task<VerifyAssertionResult> VerifyAsync(
if (authData.SignCount > 0 && authData.SignCount <= storedSignatureCounter)
throw new Fido2VerificationException(Fido2ErrorCode.InvalidSignCount, Fido2ErrorMessages.SignCountIsLessThanSignatureCounter);


return new VerifyAssertionResult
{
CredentialId = Raw.Id,
Expand Down
12 changes: 8 additions & 4 deletions Src/Fido2/AuthenticatorAttestationResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public async Task<RegisteredPublicKeyCredential> VerifyAsync(
Fido2Configuration config,
IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser,
IMetadataService? metadataService,
byte[]? requestTokenBindingId,
CancellationToken cancellationToken = default)
{
// https://www.w3.org/TR/webauthn/#registering-a-new-credential
Expand All @@ -74,7 +75,10 @@ public async Task<RegisteredPublicKeyCredential> VerifyAsync(

// 8. Verify that the value of C.challenge matches the challenge that was sent to the authenticator in the create() call.
// 9. Verify that the value of C.origin matches the Relying Party's origin.
BaseVerify(config.FullyQualifiedOrigins, originalOptions.Challenge);
// 9.5. Verify that the value of C.tokenBinding.status matches the state of Token Binding for the TLS connection over which the attestation was obtained.
// If Token Binding was used on that TLS connection, also verify that C.tokenBinding.id matches the base64url encoding of the Token Binding ID for the connection.
// Validated in BaseVerify.
BaseVerify(config.FullyQualifiedOrigins, originalOptions.Challenge, requestTokenBindingId);

if (Raw.Id is null || Raw.Id.Length == 0)
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAttestationResponse, Fido2ErrorMessages.AttestationResponseIdMissing);
Expand Down Expand Up @@ -149,7 +153,7 @@ public async Task<RegisteredPublicKeyCredential> VerifyAsync(
if (metadataService?.ConformanceTesting() is true && metadataEntry is null && attType != AttestationType.None && AttestationObject.Fmt is not "fido-u2f")
throw new Fido2VerificationException(Fido2ErrorCode.AaGuidNotFound, "AAGUID not found in MDS test metadata");

TrustAnchor.Verify(metadataEntry, trustPath);
TrustAnchor.Verify(metadataEntry, trustPath, metadataService?.ConformanceTesting() is true ? FidoValidationMode.FidoConformance2024 : FidoValidationMode.Default);

// 22. Assess the attestation trustworthiness using the outputs of the verification procedure in step 14, as follows:
// If self attestation was used, check if self attestation is acceptable under Relying Party policy.
Expand Down Expand Up @@ -186,7 +190,7 @@ public async Task<RegisteredPublicKeyCredential> VerifyAsync(

return new RegisteredPublicKeyCredential
{
Type = Raw.Type,
Type = Raw.Type.Value,
Id = authData.AttestedCredentialData.CredentialId,
PublicKey = authData.AttestedCredentialData.CredentialPublicKey.GetBytes(),
SignCount = authData.SignCount,
Expand Down Expand Up @@ -253,7 +257,7 @@ private async Task<byte[]> DevicePublicKeyRegistrationAsync(
if (metadataService?.ConformanceTesting() is true && metadataEntry is null && attType != AttestationType.None && devicePublicKeyAuthenticatorOutput.Fmt is not "fido-u2f")
throw new Fido2VerificationException(Fido2ErrorCode.AaGuidNotFound, "AAGUID not found in MDS test metadata");

TrustAnchor.Verify(metadataEntry, trustPath);
TrustAnchor.Verify(metadataEntry, trustPath, metadataService?.ConformanceTesting() is true ? FidoValidationMode.FidoConformance2024 : FidoValidationMode.Default);

// Check status reports for authenticator with undesirable status
var latestStatusReport = metadataEntry?.GetLatestStatusReport();
Expand Down
11 changes: 10 additions & 1 deletion Src/Fido2/AuthenticatorResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ protected AuthenticatorResponse(ReadOnlySpan<byte> utf8EncodedJson)
Type = response.Type;
Challenge = response.Challenge;
Origin = response.Origin;
TokenBinding = response.TokenBinding;
}

public const int MAX_ORIGINS_TO_PRINT = 5;
Expand All @@ -62,7 +63,11 @@ protected AuthenticatorResponse(ReadOnlySpan<byte> utf8EncodedJson)
[JsonPropertyName("origin")]
public string Origin { get; }

protected void BaseVerify(IReadOnlySet<string> fullyQualifiedExpectedOrigins, ReadOnlySpan<byte> originalChallenge)
// [Obsolete("This property is not used and will be removed in a future version once the conformance tool stops testing for it.")]
[JsonPropertyName("tokenBinding")]
public TokenBindingDto? TokenBinding { get; set; }

protected void BaseVerify(IReadOnlySet<string> fullyQualifiedExpectedOrigins, ReadOnlySpan<byte> originalChallenge, byte[]? requestTokenBindingId)
{
if (Type is not "webauthn.create" && Type is not "webauthn.get")
throw new Fido2VerificationException(Fido2ErrorCode.InvalidAuthenticatorResponse, $"Type must be 'webauthn.create' or 'webauthn.get'. Was '{Type}'");
Expand All @@ -79,6 +84,10 @@ protected void BaseVerify(IReadOnlySet<string> fullyQualifiedExpectedOrigins, Re
// 12. Verify that the value of C.origin matches the Relying Party's origin.
if (!fullyQualifiedExpectedOrigins.Contains(fullyQualifiedOrigin))
throw new Fido2VerificationException($"Fully qualified origin {fullyQualifiedOrigin} of {Origin} not equal to fully qualified original origin {string.Join(", ", fullyQualifiedExpectedOrigins.Take(MAX_ORIGINS_TO_PRINT))} ({fullyQualifiedExpectedOrigins.Count})");

// 13?. Verify that the value of C.tokenBinding.status matches the state of Token Binding for the TLS connection over which the assertion was obtained.
// If Token Binding was used on that TLS connection, also verify that C.tokenBinding.id matches the base64url encoding of the Token Binding ID for the connection.
TokenBinding?.Verify(requestTokenBindingId);
}

/*
Expand Down
Loading

0 comments on commit c0c017c

Please sign in to comment.