Skip to content

Commit

Permalink
[Draft] Add credProtect extension to Fido2.Models (#448)
Browse files Browse the repository at this point in the history
  • Loading branch information
dbeinder authored Jan 15, 2024
1 parent c2f384d commit 67eec54
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 8 deletions.
27 changes: 19 additions & 8 deletions Src/Fido2.Models/Converters/FidoEnumConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,26 @@ public sealed class FidoEnumConverter<[DynamicallyAccessedMembers(DynamicallyAcc
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string text = reader.GetString();

if (EnumNameMapper<T>.TryGetValue(reader.GetString(), out T value))
{
return value;
}
else
switch (reader.TokenType)
{
throw new JsonException($"Invalid enum value = {text}");
case JsonTokenType.String:
string text = reader.GetString();
if (EnumNameMapper<T>.TryGetValue(text, out T value))
return value;
else
throw new JsonException($"Invalid enum value = \"{text}\"");

case JsonTokenType.Number:
if (!reader.TryGetInt32(out var number))
throw new JsonException($"Invalid enum value = {reader.GetString()}");
var casted = (T)(object)number; // ints can always be casted to enum, even when the value is not defined
if (Enum.IsDefined(casted))
return casted;
else
throw new JsonException($"Invalid enum value = {number}");

default:
throw new JsonException($"Invalid enum value ({reader.TokenType})");
}
}

Expand Down
22 changes: 22 additions & 0 deletions Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,27 @@ public sealed class AuthenticationExtensionsClientInputs
[JsonPropertyName("prf")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public AuthenticationExtensionsPRFInputs? PRF { get; set; }

/// <summary>
/// This registration extension allows relying parties to specify a credential protection policy when creating a credential.
/// Additionally, authenticators MAY choose to establish a default credential protection policy greater than <c>UserVerificationOptional</c> (the lowest level)
/// and unilaterally enforce such policy. Authenticators not supporting some form of user verification MUST NOT support this extension.
/// Authenticators supporting some form of user verification MUST process this extension and persist the credProtect value with the credential,
/// even if the authenticator is not protected by some form of user verification at the time.
/// https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-credProtect-extension
/// </summary>
[JsonPropertyName("credentialProtectionPolicy")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public CredentialProtectionPolicy? CredentialProtectionPolicy { get; set; }

/// <summary>
/// This controls whether it is better to fail to create a credential rather than ignore the protection policy.
/// When true, and <c>CredentialProtectionPolicy</c>'s value is
/// either <c>UserVerificationOptionalWithCredentialIdList</c> or <c>UserVerificationRequired</c>, the platform
/// SHOULD NOT create the credential in a way that does not implement the requested protection policy.
/// </summary>
[JsonPropertyName("enforceCredentialProtectionPolicy")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? EnforceCredentialProtectionPolicy { get; set; }
}

Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,13 @@ public class AuthenticationExtensionsClientOutputs
[JsonPropertyName("prf")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public AuthenticationExtensionsPRFOutputs? PRF { get; set; }


/// <summary>
/// The <c>CredentialProtectionPolicy</c> stored alongside the created credential
/// https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-credProtect-extension
/// </summary>
[JsonPropertyName("credProtect")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public CredentialProtectionPolicy? CredProtect { get; set; }
}
31 changes: 31 additions & 0 deletions Src/Fido2.Models/Objects/CredentialProtectionPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Runtime.Serialization;
using System.Text.Json.Serialization;

namespace Fido2NetLib.Objects;

/// <summary>
/// CredentialProtectionPolicy
/// https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-credProtect-extension
/// </summary>
[JsonConverter(typeof(FidoEnumConverter<CredentialProtectionPolicy>))]
public enum CredentialProtectionPolicy
{
/// <summary>
/// This reflects "FIDO_2_0" semantics. In this configuration, performing some form of user verification is OPTIONAL with or without credentialID list.
/// This is the default state of the credential if the extension is not specified
/// </summary>
[EnumMember(Value = "userVerificationOptional")]
UserVerificationOptional = 0x01,

/// <summary>
/// In this configuration, credential is discovered only when its credentialID is provided by the platform or when some form of user verification is performed.
/// </summary>
[EnumMember(Value = "userVerificationOptionalWithCredentialIDList")]
UserVerificationOptionalWithCredentialIdList = 0x02,

/// <summary>
/// TThis reflects that discovery and usage of the credential MUST be preceded by some form of user verification.
/// </summary>
[EnumMember(Value = "userVerificationRequired")]
UserVerificationRequired = 0x03
}
8 changes: 8 additions & 0 deletions Test/Converters/FidoEnumConverterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ public void CorrectlyFallsBackToMemberName()
Assert.Equal(ABC.A, JsonSerializer.Deserialize<ABC>("\"a\""));
}

[Fact]
public void CorrectlyDeserializesNumericEnumValue()
{
Assert.Equal(CredentialProtectionPolicy.UserVerificationRequired, JsonSerializer.Deserialize<CredentialProtectionPolicy>($"{CredentialProtectionPolicy.UserVerificationRequired:d}"));
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<CredentialProtectionPolicy>($"99"));
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<CredentialProtectionPolicy>($"99.7"));
}

[Fact]
public void DeserializationIsCaseInsensitive()
{
Expand Down

0 comments on commit 67eec54

Please sign in to comment.