diff --git a/Demo/ConformanceTesting.cs b/Demo/ConformanceTesting.cs index 69c1dbe6..0c3a9331 100644 --- a/Demo/ConformanceTesting.cs +++ b/Demo/ConformanceTesting.cs @@ -23,7 +23,7 @@ public static IMetadataService MetadataServiceInstance(string cacheDir, string o new FileSystemMetadataRepository(cacheDir) }; _instance = new SimpleMetadataService(repos); - _instance.Initialize().Wait(); + _instance.InitializeAsync().Wait(); } } } diff --git a/Src/Fido2.AspNet/DistributedCacheMetadataService.cs b/Src/Fido2.AspNet/DistributedCacheMetadataService.cs index 8d18d432..013db9da 100644 --- a/Src/Fido2.AspNet/DistributedCacheMetadataService.cs +++ b/Src/Fido2.AspNet/DistributedCacheMetadataService.cs @@ -2,10 +2,10 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; namespace Fido2NetLib { @@ -86,7 +86,7 @@ protected virtual async Task LoadTocEntryStatement( var cachedEntry = await _cache.GetStringAsync(cacheKey); if (cachedEntry != null) { - var statement = JsonConvert.DeserializeObject(cachedEntry); + var statement = JsonSerializer.Deserialize(cachedEntry); if (!string.IsNullOrWhiteSpace(statement.AaGuid)) { var aaGuid = Guid.Parse(statement.AaGuid); @@ -102,7 +102,7 @@ protected virtual async Task LoadTocEntryStatement( { if (!string.IsNullOrWhiteSpace(entry.AaGuid)) { - var statementJson = JsonConvert.SerializeObject(entry.MetadataStatement, Formatting.Indented); + var statementJson = JsonSerializer.Serialize(entry.MetadataStatement, new JsonSerializerOptions { WriteIndented = true }); _log?.LogDebug("{0}:{1}\n{2}", entry.AaGuid, entry.MetadataStatement.Description, statementJson); @@ -149,7 +149,7 @@ protected virtual async Task LoadTocEntryStatement( return null; } - protected virtual async Task InitializeRepository(IMetadataRepository repository) + protected virtual async Task InitializeRepositoryAsync(IMetadataRepository repository) { var blobCacheKey = GetTocCacheKey(repository); @@ -161,7 +161,7 @@ protected virtual async Task InitializeRepository(IMetadataRepository repository if (cachedToc != null) { - blob = JsonConvert.DeserializeObject(cachedToc); + blob = JsonSerializer.Deserialize(cachedToc); cacheUntil = GetCacheUntilTime(blob); } else @@ -186,7 +186,7 @@ protected virtual async Task InitializeRepository(IMetadataRepository repository { await _cache.SetStringAsync( blobCacheKey, - JsonConvert.SerializeObject(blob), + JsonSerializer.Serialize(blob), new DistributedCacheEntryOptions() { AbsoluteExpiration = cacheUntil @@ -204,19 +204,19 @@ await _cache.SetStringAsync( } catch (Exception ex) { - _log?.LogError(ex, "Error getting statement from {0} for AAGUID '{1}'.\nTOC entry:\n{2} ", repository.GetType().Name, entry.AaGuid, JsonConvert.SerializeObject(entry, Formatting.Indented)); + _log?.LogError(ex, "Error getting statement from {0} for AAGUID '{1}'.\nTOC entry:\n{2} ", repository.GetType().Name, entry.AaGuid, JsonSerializer.Serialize(entry, new JsonSerializerOptions { WriteIndented = true })); } } } } - public virtual async Task Initialize() + public virtual async Task InitializeAsync() { foreach (var repository in _repositories) { try { - await InitializeRepository(repository); + await InitializeRepositoryAsync(repository); } catch (Exception ex) { diff --git a/Src/Fido2.AspNet/Fido2NetLibBuilderExtensions.cs b/Src/Fido2.AspNet/Fido2NetLibBuilderExtensions.cs index 54a3733e..b6911dc8 100644 --- a/Src/Fido2.AspNet/Fido2NetLibBuilderExtensions.cs +++ b/Src/Fido2.AspNet/Fido2NetLibBuilderExtensions.cs @@ -87,7 +87,7 @@ private static void AddMetadataService(this IFido2NetLibBuilder builde builder.Services.AddSingleton(r => { var service = r.GetService(); - service.Initialize().Wait(); + service.InitializeAsync().Wait(); return service; }); } diff --git a/Src/Fido2.AspNet/NullMetadataService.cs b/Src/Fido2.AspNet/NullMetadataService.cs index a0b7aae3..7b0e6c18 100644 --- a/Src/Fido2.AspNet/NullMetadataService.cs +++ b/Src/Fido2.AspNet/NullMetadataService.cs @@ -15,7 +15,7 @@ MetadataBLOBPayloadEntry IMetadataService.GetEntry(Guid aaguid) return null; } - Task IMetadataService.Initialize() + Task IMetadataService.InitializeAsync() { return Task.CompletedTask; } diff --git a/Src/Fido2.Models/AssertionOptions.cs b/Src/Fido2.Models/AssertionOptions.cs index cedab090..87bcecd0 100644 --- a/Src/Fido2.Models/AssertionOptions.cs +++ b/Src/Fido2.Models/AssertionOptions.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + using Fido2NetLib.Objects; -using Newtonsoft.Json; namespace Fido2NetLib { @@ -12,38 +14,39 @@ public class AssertionOptions : Fido2ResponseBase /// /// This member represents a challenge that the selected authenticator signs, along with other data, when producing an authentication assertion.See the §13.1 Cryptographic Challenges security consideration. /// - [JsonProperty("challenge")] + [JsonPropertyName("challenge")] [JsonConverter(typeof(Base64UrlConverter))] public byte[] Challenge { get; set; } /// /// This member specifies a time, in milliseconds, that the caller is willing to wait for the call to complete. This is treated as a hint, and MAY be overridden by the client. /// - [JsonProperty("timeout")] + [JsonPropertyName("timeout")] public uint Timeout { get; set; } /// /// This OPTIONAL member specifies the relying party identifier claimed by the caller.If omitted, its value will be the CredentialsContainer object’s relevant settings object's origin's effective domain /// - [JsonProperty("rpId")] + [JsonPropertyName("rpId")] public string RpId { get; set; } /// /// This OPTIONAL member contains a list of PublicKeyCredentialDescriptor objects representing public key credentials acceptable to the caller, in descending order of the caller’s preference(the first item in the list is the most preferred credential, and so on down the list) /// - [JsonProperty("allowCredentials")] + [JsonPropertyName("allowCredentials")] public IEnumerable AllowCredentials { get; set; } /// /// This member describes the Relying Party's requirements regarding user verification for the get() operation. Eligible authenticators are filtered to only those capable of satisfying this requirement /// - [JsonProperty("userVerification")] + [JsonPropertyName("userVerification")] public UserVerificationRequirement? UserVerification { get; set; } /// /// This OPTIONAL member contains additional parameters requesting additional processing by the client and authenticator. For example, if transaction confirmation is sought from the user, then the prompt string might be included as an extension. /// - [JsonProperty("extensions", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("extensions")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public AuthenticationExtensionsClientInputs Extensions { get; set; } public static AssertionOptions Create(Fido2Configuration config, byte[] challenge, IEnumerable allowedCredentials, UserVerificationRequirement? userVerification, AuthenticationExtensionsClientInputs extensions) @@ -63,12 +66,12 @@ public static AssertionOptions Create(Fido2Configuration config, byte[] challeng public string ToJson() { - return JsonConvert.SerializeObject(this); + return JsonSerializer.Serialize(this); } public static AssertionOptions FromJson(string json) { - return JsonConvert.DeserializeObject(json); + return JsonSerializer.Deserialize(json); } } } diff --git a/Src/Fido2.Models/AuthenticatorAssertionRawResponse.cs b/Src/Fido2.Models/AuthenticatorAssertionRawResponse.cs index 0d0a689c..1108d5ee 100644 --- a/Src/Fido2.Models/AuthenticatorAssertionRawResponse.cs +++ b/Src/Fido2.Models/AuthenticatorAssertionRawResponse.cs @@ -1,6 +1,7 @@ #nullable disable -using Newtonsoft.Json; +using System.Text.Json.Serialization; + using Fido2NetLib.Objects; namespace Fido2NetLib @@ -11,32 +12,39 @@ namespace Fido2NetLib public class AuthenticatorAssertionRawResponse { [JsonConverter(typeof(Base64UrlConverter))] + [JsonPropertyName("id")] public byte[] Id { get; set; } // might be wrong to base64url encode this... [JsonConverter(typeof(Base64UrlConverter))] + [JsonPropertyName("rawId")] public byte[] RawId { get; set; } + [JsonPropertyName("response")] public AssertionResponse Response { get; set; } + [JsonPropertyName("type")] public PublicKeyCredentialType? Type { get; set; } + [JsonPropertyName("extensions")] public AuthenticationExtensionsClientOutputs Extensions { get; set; } public class AssertionResponse { [JsonConverter(typeof(Base64UrlConverter))] + [JsonPropertyName("authenticatorData")] public byte[] AuthenticatorData { get; set; } [JsonConverter(typeof(Base64UrlConverter))] + [JsonPropertyName("signature")] public byte[] Signature { get; set; } - [JsonProperty("clientDataJson")] [JsonConverter(typeof(Base64UrlConverter))] + [JsonPropertyName("clientDataJSON")] public byte[] ClientDataJson { get; set; } - [JsonProperty("userHandle")] - [JsonConverter(typeof(Base64UrlConverter), Required.AllowNull)] + [JsonPropertyName("userHandle")] + [JsonConverter(typeof(Base64UrlConverter))] public byte[] UserHandle { get; set; } } } diff --git a/Src/Fido2.Models/AuthenticatorAttestationRawResponse.cs b/Src/Fido2.Models/AuthenticatorAttestationRawResponse.cs index 8741706f..8a22690b 100644 --- a/Src/Fido2.Models/AuthenticatorAttestationRawResponse.cs +++ b/Src/Fido2.Models/AuthenticatorAttestationRawResponse.cs @@ -1,27 +1,36 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; + using Fido2NetLib.Objects; namespace Fido2NetLib { - public class AuthenticatorAttestationRawResponse + public sealed class AuthenticatorAttestationRawResponse { [JsonConverter(typeof(Base64UrlConverter))] + [JsonPropertyName("id")] public byte[] Id { get; set; } [JsonConverter(typeof(Base64UrlConverter))] + [JsonPropertyName("rawId")] public byte[] RawId { get; set; } + [JsonPropertyName("type")] public PublicKeyCredentialType? Type { get; set; } + [JsonPropertyName("response")] public ResponseData Response { get; set; } + [JsonPropertyName("extensions")] public AuthenticationExtensionsClientOutputs Extensions { get; set; } - public class ResponseData + public sealed class ResponseData { [JsonConverter(typeof(Base64UrlConverter))] + [JsonPropertyName("attestationObject")] public byte[] AttestationObject { get; set; } + [JsonConverter(typeof(Base64UrlConverter))] + [JsonPropertyName("clientDataJSON")] public byte[] ClientDataJson { get; set; } } } diff --git a/Src/Fido2.Models/Base64Converter.cs b/Src/Fido2.Models/Base64Converter.cs deleted file mode 100644 index c4dc7df3..00000000 --- a/Src/Fido2.Models/Base64Converter.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using Newtonsoft.Json; - -namespace Fido2NetLib -{ - /// - /// Custom Converter for encoding/encoding byte[] using Base64Url instead of default Base64. - /// - public class Base64UrlConverter : JsonConverter - { - private readonly Required _requirement = Required.DisallowNull; - - public Base64UrlConverter() - { - } - - public Base64UrlConverter(Required required = Required.DisallowNull) - { - _requirement = required; - } - - public override void WriteJson(JsonWriter writer, byte[] value, JsonSerializer serializer) - { - writer.WriteValue(Base64Url.Encode(value)); - } - - public override byte[] ReadJson(JsonReader reader, Type objectType, byte[] existingValue, bool hasExistingValue, JsonSerializer serializer) - { - byte[] ret = null; - - if (null == reader.Value && _requirement == Required.AllowNull) - return ret; - - if (null == reader.Value) - throw new Fido2VerificationException("json value must not be null"); - if (Type.GetType("System.String") != reader.ValueType) - throw new Fido2VerificationException("json valuetype must be string"); - try - { - ret = Base64Url.Decode((string)reader.Value); - } - catch (FormatException ex) - { - throw new Fido2VerificationException("json value must be valid base64 encoded string", ex); - } - return ret; - } - } -} diff --git a/Src/Fido2.Models/Base64Url.cs b/Src/Fido2.Models/Base64Url.cs index 8b338f8f..99967325 100644 --- a/Src/Fido2.Models/Base64Url.cs +++ b/Src/Fido2.Models/Base64Url.cs @@ -1,5 +1,7 @@ using System; using System.Buffers; +using System.Buffers.Text; +using System.Text.Unicode; namespace Fido2NetLib { @@ -49,11 +51,8 @@ public static string Encode(ReadOnlySpan arg) /// /// Decodes a Base64Url encoded string to its raw bytes. /// - public static byte[] Decode(string text) + public static byte[] Decode(ReadOnlySpan text) { - if (text is null) - throw new ArgumentNullException(nameof(text)); - int padCharCount = (text.Length % 4) switch { 2 => 2, @@ -65,7 +64,7 @@ public static byte[] Decode(string text) char[] buffer = ArrayPool.Shared.Rent(encodedLength); - text.CopyTo(0, buffer, 0, text.Length); + text.CopyTo(buffer); for (int i = 0; i < text.Length; i++) { @@ -94,5 +93,54 @@ public static byte[] Decode(string text) return result; } + + + /// + /// Decodes a Base64Url encoded string to its raw bytes. + /// + public static byte[] DecodeUtf8(ReadOnlySpan text) + { + int padCharCount = (text.Length % 4) switch + { + 2 => 2, + 3 => 1, + _ => 0 + }; + + int encodedLength = text.Length + padCharCount; + + byte[] buffer = ArrayPool.Shared.Rent(encodedLength); + + text.CopyTo(buffer); + + for (int i = 0; i < text.Length; i++) + { + ref byte c = ref buffer[i]; + + switch ((char)c) + { + case '-': c = (byte)'+'; break; + case '_': c = (byte)'/'; break; + } + } + + if (padCharCount == 1) + { + buffer[encodedLength - 1] = (byte)'='; + } + else if (padCharCount == 2) + { + buffer[encodedLength - 1] = (byte)'='; + buffer[encodedLength - 2] = (byte)'='; + } + + Base64.DecodeFromUtf8InPlace(buffer.AsSpan(0, encodedLength), out int decodedLength); + + var result = buffer.AsSpan(0, decodedLength).ToArray(); + + ArrayPool.Shared.Return(buffer, true); + + return result; + } } } diff --git a/Src/Fido2.Models/Converters/Base64Converter.cs b/Src/Fido2.Models/Converters/Base64Converter.cs new file mode 100644 index 00000000..fe7e6366 --- /dev/null +++ b/Src/Fido2.Models/Converters/Base64Converter.cs @@ -0,0 +1,29 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Fido2NetLib +{ + /// + /// Custom Converter for encoding/encoding byte[] using Base64Url instead of default Base64. + /// + public sealed class Base64UrlConverter : JsonConverter + { + public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (!reader.HasValueSequence) + { + return Base64Url.DecodeUtf8(reader.ValueSpan); + } + else + { + return Base64Url.Decode(reader.GetString()); + } + } + + public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options) + { + writer.WriteStringValue(Base64Url.Encode(value)); + } + } +} diff --git a/Src/Fido2.Models/Converters/FidoEnumConverter.cs b/Src/Fido2.Models/Converters/FidoEnumConverter.cs new file mode 100644 index 00000000..550d25ae --- /dev/null +++ b/Src/Fido2.Models/Converters/FidoEnumConverter.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Fido2NetLib +{ + public sealed class FidoEnumConverter : JsonConverter + where T: struct, Enum + { + private static readonly Dictionary valueToNames = GetIdToNameMap(); + private static readonly Dictionary namesToValues = Invert(valueToNames); + + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string text = reader.GetString(); + + if (namesToValues.TryGetValue(reader.GetString(), out T value)) + { + return value; + } + else + { + throw new JsonException($"Invalid enum value = {text}"); + } + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + writer.WriteStringValue(valueToNames[value]); + } + + private static Dictionary Invert(Dictionary map) + { + var result = new Dictionary(map.Count, StringComparer.OrdinalIgnoreCase); + + foreach (var item in map) + { + result[item.Value] = item.Key; + } + + return result; + } + + private static Dictionary GetIdToNameMap() + { + var dic = new Dictionary(); + + foreach (var field in typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static)) + { + var description = field.GetCustomAttribute(false); + + var value = (T)field.GetValue(null); + + dic[value] = description is not null ? description.Value : value.ToString(); + } + + return dic; + } + } +} diff --git a/Src/Fido2.Models/CredentialCreateOptions.cs b/Src/Fido2.Models/CredentialCreateOptions.cs index 1709a380..ad26e2eb 100644 --- a/Src/Fido2.Models/CredentialCreateOptions.cs +++ b/Src/Fido2.Models/CredentialCreateOptions.cs @@ -1,6 +1,8 @@ -using Fido2NetLib.Objects; -using Newtonsoft.Json; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +using Fido2NetLib.Objects; namespace Fido2NetLib { @@ -12,57 +14,58 @@ public sealed class CredentialCreateOptions : Fido2ResponseBase /// Its value’s name member is required. /// Its value’s id member specifies the relying party identifier with which the credential should be associated.If omitted, its value will be the CredentialsContainer object’s relevant settings object's origin's effective domain. /// - [JsonProperty("rp")] + [JsonPropertyName("rp")] public PublicKeyCredentialRpEntity Rp { get; set; } /// /// This member contains data about the user account for which the Relying Party is requesting attestation. /// Its value’s name, displayName and id members are required. /// - [JsonProperty("user")] + [JsonPropertyName("user")] public Fido2User User { get; set; } /// /// Must be generated by the Server (Relying Party) /// - [JsonProperty("challenge")] + [JsonPropertyName("challenge")] [JsonConverter(typeof(Base64UrlConverter))] public byte[] Challenge { get; set; } /// /// This member contains information about the desired properties of the credential to be created. The sequence is ordered from most preferred to least preferred. The platform makes a best-effort to create the most preferred credential that it can. /// - [JsonProperty("pubKeyCredParams")] + [JsonPropertyName("pubKeyCredParams")] public List PubKeyCredParams { get; set; } /// /// This member specifies a time, in milliseconds, that the caller is willing to wait for the call to complete. This is treated as a hint, and MAY be overridden by the platform. /// - [JsonProperty("timeout")] + [JsonPropertyName("timeout")] public long Timeout { get; set; } /// /// This member is intended for use by Relying Parties that wish to express their preference for attestation conveyance.The default is none. /// - [JsonProperty("attestation")] + [JsonPropertyName("attestation")] public AttestationConveyancePreference Attestation { get; set; } = AttestationConveyancePreference.None; /// /// This member is intended for use by Relying Parties that wish to select the appropriate authenticators to participate in the create() operation. /// - [JsonProperty("authenticatorSelection")] + [JsonPropertyName("authenticatorSelection")] public AuthenticatorSelection AuthenticatorSelection { get; set; } /// /// This member is intended for use by Relying Parties that wish to limit the creation of multiple credentials for the same account on a single authenticator.The client is requested to return an error if the new credential would be created on an authenticator that also contains one of the credentials enumerated in this parameter. /// - [JsonProperty("excludeCredentials")] + [JsonPropertyName("excludeCredentials")] public List ExcludeCredentials { get; set; } /// /// This OPTIONAL member contains additional parameters requesting additional processing by the client and authenticator. For example, if transaction confirmation is sought from the user, then the prompt string might be included as an extension. /// - [JsonProperty("extensions", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("extensions")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public AuthenticationExtensionsClientInputs Extensions { get; set; } public static CredentialCreateOptions Create(Fido2Configuration config, byte[] challenge, Fido2User user, AuthenticatorSelection authenticatorSelection, AttestationConveyancePreference attestationConveyancePreference, List excludeCredentials, AuthenticationExtensionsClientInputs extensions) @@ -98,12 +101,12 @@ public static CredentialCreateOptions Create(Fido2Configuration config, byte[] c public string ToJson() { - return JsonConvert.SerializeObject(this); + return JsonSerializer.Serialize(this); } public static CredentialCreateOptions FromJson(string json) { - return JsonConvert.DeserializeObject(json); + return JsonSerializer.Deserialize(json); } private static readonly PubKeyCredParam ES256 = new(COSE.Algorithm.ES256); // External authenticators support the ES256 algorithm @@ -133,13 +136,13 @@ public PubKeyCredParam(COSE.Algorithm alg, PublicKeyCredentialType type = Public /// /// The type member specifies the type of credential to be created. /// - [JsonProperty("type")] + [JsonPropertyName("type")] public PublicKeyCredentialType Type { get; } /// /// The alg member specifies the cryptographic signature algorithm with which the newly generated credential will be used, and thus also the type of asymmetric key pair to be generated, e.g., RSA or Elliptic Curve. /// - [JsonProperty("alg")] + [JsonPropertyName("alg")] public COSE.Algorithm Alg { get; } } @@ -158,16 +161,17 @@ public PublicKeyCredentialRpEntity(string id, string name, string icon) /// /// A unique identifier for the Relying Party entity, which sets the RP ID. /// - [JsonProperty("id")] + [JsonPropertyName("id")] public string Id { get; set; } /// /// A human-readable name for the entity. Its function depends on what the PublicKeyCredentialEntity represents: /// - [JsonProperty("name")] + [JsonPropertyName("name")] public string Name { get; set; } - [JsonProperty("icon", DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonPropertyName("icon")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string Icon { get; set; } } @@ -179,19 +183,20 @@ public class AuthenticatorSelection /// /// If this member is present, eligible authenticators are filtered to only authenticators attached with the specified §5.4.5 Authenticator Attachment enumeration (enum AuthenticatorAttachment). /// - [JsonProperty("authenticatorAttachment", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("authenticatorAttachment")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public AuthenticatorAttachment? AuthenticatorAttachment { get; set; } /// /// This member describes the Relying Parties' requirements regarding resident credentials. If the parameter is set to true, the authenticator MUST create a client-side-resident public key credential source when creating a public key credential. /// - [JsonProperty("requireResidentKey")] + [JsonPropertyName("requireResidentKey")] public bool RequireResidentKey { get; set; } /// /// This member describes the Relying Party's requirements regarding user verification for the create() operation. Eligible authenticators are filtered to only those capable of satisfying this requirement. /// - [JsonProperty("userVerification")] + [JsonPropertyName("userVerification")] public UserVerificationRequirement UserVerification { get; set; } public static AuthenticatorSelection Default => new AuthenticatorSelection @@ -204,24 +209,23 @@ public class AuthenticatorSelection public class Fido2User { - /// /// Required. A human-friendly identifier for a user account. It is intended only for display, i.e., aiding the user in determining the difference between user accounts with similar displayNames. For example, "alexm", "alex.p.mueller@example.com" or "+14255551234". https://w3c.github.io/webauthn/#dictdef-publickeycredentialentity /// - [JsonProperty("name")] + [JsonPropertyName("name")] public string Name { get; set; } /// /// The user handle of the user account entity. To ensure secure operation, authentication and authorization decisions MUST be made on the basis of this id member, not the displayName nor name members /// - [JsonProperty("id")] + [JsonPropertyName("id")] [JsonConverter(typeof(Base64UrlConverter))] public byte[] Id { get; set; } /// /// A human-friendly name for the user account, intended only for display. For example, "Alex P. Müller" or "田中 倫". The Relying Party SHOULD let the user choose this, and SHOULD NOT restrict the choice more than necessary. /// - [JsonProperty("displayName")] + [JsonPropertyName("displayName")] public string DisplayName { get; set; } } } diff --git a/Src/Fido2.Models/Fido2.Models.csproj b/Src/Fido2.Models/Fido2.Models.csproj index 390cf33b..35e04d68 100644 --- a/Src/Fido2.Models/Fido2.Models.csproj +++ b/Src/Fido2.Models/Fido2.Models.csproj @@ -12,7 +12,7 @@ - + diff --git a/Src/Fido2.Models/Fido2ResponseBase.cs b/Src/Fido2.Models/Fido2ResponseBase.cs index edf4feab..b69a1adc 100644 --- a/Src/Fido2.Models/Fido2ResponseBase.cs +++ b/Src/Fido2.Models/Fido2ResponseBase.cs @@ -1,13 +1,13 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Fido2NetLib { public abstract class Fido2ResponseBase { - [JsonProperty("status")] + [JsonPropertyName("status")] public string Status { get; set; } - [JsonProperty("errorMessage")] + [JsonPropertyName("errorMessage")] public string ErrorMessage { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/AlternativeDescriptions.cs b/Src/Fido2.Models/Metadata/AlternativeDescriptions.cs index 476e62ef..d4aabe26 100644 --- a/Src/Fido2.Models/Metadata/AlternativeDescriptions.cs +++ b/Src/Fido2.Models/Metadata/AlternativeDescriptions.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -23,7 +23,7 @@ public class AlternativeDescriptions /// Each description SHALL NOT exceed a maximum length of 200 characters. /// Description values can contain any UTF-8 characters. /// - [JsonProperty("alternativeDescriptions")] + [JsonPropertyName("alternativeDescriptions")] public Dictionary IETFLanguageCodesMembers { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/AuthenticatorStatus.cs b/Src/Fido2.Models/Metadata/AuthenticatorStatus.cs index 292945fc..dbdad9b1 100644 --- a/Src/Fido2.Models/Metadata/AuthenticatorStatus.cs +++ b/Src/Fido2.Models/Metadata/AuthenticatorStatus.cs @@ -1,4 +1,6 @@ -namespace Fido2NetLib +using System.Text.Json.Serialization; + +namespace Fido2NetLib { /// /// Describes the status of an authenticator model as identified by its AAID and potentially some additional information (such as a specific attestation key). @@ -6,6 +8,7 @@ /// /// /// + [JsonConverter(typeof(JsonStringEnumConverter))] public enum AuthenticatorStatus { /// diff --git a/Src/Fido2.Models/Metadata/BiometricAccuracyDescriptor.cs b/Src/Fido2.Models/Metadata/BiometricAccuracyDescriptor.cs index f8137cb1..ffec9b41 100644 --- a/Src/Fido2.Models/Metadata/BiometricAccuracyDescriptor.cs +++ b/Src/Fido2.Models/Metadata/BiometricAccuracyDescriptor.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -8,7 +8,7 @@ namespace Fido2NetLib /// /// /// - public class BiometricAccuracyDescriptor + public sealed class BiometricAccuracyDescriptor { /// /// Gets or sets the false rejection rate. @@ -16,16 +16,17 @@ public class BiometricAccuracyDescriptor /// /// /// [ISO19795-1] for a single template, i.e. the percentage of verification transactions with truthful claims of identity that are incorrectly denied. - /// /// - [JsonProperty("selfAttestedFRR ")] + [JsonPropertyName("selfAttestedFRR")] public double SelfAttestedFRR { get; set; } + /// /// Gets or sets the false acceptance rate. /// For example a FAR of 0.002% would be encoded as 0.00002. /// - [JsonProperty("selfAttestedFAR ")] + [JsonPropertyName("selfAttestedFAR")] public double SelfAttestedFAR { get; set; } + /// /// Gets or sets the maximum number of alternative templates from different fingers allowed. /// @@ -33,14 +34,16 @@ public class BiometricAccuracyDescriptor /// For other modalities, multiple parts of the body that can be used interchangeably. /// For example: 3 if the user is allowed to enroll up to 3 different fingers to a fingerprint based authenticator. /// - [JsonProperty("maxTemplates")] + [JsonPropertyName("maxTemplates")] public ushort MaxTemplates { get; set; } + /// /// Gets or sets the maximum number of false attempts before the authenticator will block this method (at least for some time). /// Zero (0) means it will never block. /// - [JsonProperty("maxRetries")] + [JsonPropertyName("maxRetries")] public ushort MaxRetries { get; set; } + /// /// Gets or sets the enforced minimum number of seconds wait time after blocking (e.g. due to forced reboot or similar). /// Zero (0) means that this user verification method will be blocked either permanently or until an alternative user verification method succeeded. @@ -48,7 +51,7 @@ public class BiometricAccuracyDescriptor /// /// All alternative user verification methods MUST be specified appropriately in the metadata in . /// - [JsonProperty("blockSlowdown")] + [JsonPropertyName("blockSlowdown")] public ushort BlockSlowdown { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/BiometricStatusReport.cs b/Src/Fido2.Models/Metadata/BiometricStatusReport.cs index df9d48e9..8b8ce330 100644 --- a/Src/Fido2.Models/Metadata/BiometricStatusReport.cs +++ b/Src/Fido2.Models/Metadata/BiometricStatusReport.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -13,7 +14,7 @@ public class BiometricStatusReport /// /// Gets or sets the level of the biometric certification of this biometric component of the authenticator. /// - [JsonProperty("certLevel", Required = Required.Always)] + [JsonPropertyName("certLevel"), Required] public ushort CertLevel { get; set; } /// /// Gets or sets a single USER_VERIFY constant indicating the modality of the biometric component. @@ -22,39 +23,44 @@ public class BiometricStatusReport /// This is not a bit flag combination. /// This value MUST be non-zero and this value MUST correspond to one or more entries in field userVerificationDetails in the related Metadata Statement. /// - [JsonProperty("modality", Required = Required.Always)] + [JsonPropertyName("modality"), Required] public ulong Modality { get; set; } + /// /// Gets or sets a ISO-8601 formatted date since when the certLevel achieved, if applicable. /// If no date is given, the status is assumed to be effective while present. /// - [JsonProperty("effectiveDate")] + [JsonPropertyName("effectiveDate")] public string EffectiveDate { get; set; } + /// /// Gets or sets the externally visible aspects of the Biometric Certification evaluation. /// - [JsonProperty("certificationDescriptor")] + [JsonPropertyName("certificationDescriptor")] public string CertificationDescriptor { get; set; } + /// /// Gets or sets the unique identifier for the issued Biometric Certification. /// - [JsonProperty("certificateNumber")] + [JsonPropertyName("certificateNumber")] public string CertificateNumber { get; set; } + /// /// Gets or sets the version of the Biometric Certification Policy the implementation is Certified to. /// /// /// For example: "1.0.0". /// - [JsonProperty("certificationPolicyVersion")] + [JsonPropertyName("certificationPolicyVersion")] public string CertificationPolicyVersion { get; set; } + /// /// Gets or sets the version of the Biometric Requirements the implementation is certified to. /// /// /// For example: "1.0.0". /// - [JsonProperty("certificationRequirementsVersion")] + [JsonPropertyName("certificationRequirementsVersion")] public string CertificationRequirementsVersion { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/CodeAccuracyDescriptor.cs b/Src/Fido2.Models/Metadata/CodeAccuracyDescriptor.cs index bf64f806..3dc4f287 100644 --- a/Src/Fido2.Models/Metadata/CodeAccuracyDescriptor.cs +++ b/Src/Fido2.Models/Metadata/CodeAccuracyDescriptor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -8,24 +9,27 @@ namespace Fido2NetLib /// /// /// - public class CodeAccuracyDescriptor + public sealed class CodeAccuracyDescriptor { /// /// Gets or sets the numeric system base (radix) of the code, e.g. 10 in the case of decimal digits. /// - [JsonProperty("base", Required = Required.Always)] + [JsonPropertyName("base"), Required] public ushort Base { get; set; } + /// /// Gets or sets the minimum number of digits of the given base required for that code, e.g. 4 in the case of 4 digits. /// - [JsonProperty("minLength", Required = Required.Always)] + [JsonPropertyName("minLength"), Required] public ushort MinLength { get; set; } + /// /// Gets or sets the maximum number of false attempts before the authenticator will block this method (at least for some time). /// Zero (0) means it will never block. /// - [JsonProperty("maxRetries")] + [JsonPropertyName("maxRetries")] public ushort MaxRetries { get; set; } + /// /// Gets or sets the enforced minimum number of seconds wait time after blocking (e.g. due to forced reboot or similar). /// Zero (0) means this user verification method will be blocked, either permanently or until an alternative user verification method method succeeded. @@ -33,7 +37,7 @@ public class CodeAccuracyDescriptor /// /// All alternative user verification methods MUST be specified appropriately in the Metadata in . /// - [JsonProperty("blockSlowdown")] + [JsonPropertyName("blockSlowdown")] public ushort BlockSlowdown { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/DisplayPNGCharacteristicsDescriptor.cs b/Src/Fido2.Models/Metadata/DisplayPNGCharacteristicsDescriptor.cs index 72fc0c64..c96a7545 100644 --- a/Src/Fido2.Models/Metadata/DisplayPNGCharacteristicsDescriptor.cs +++ b/Src/Fido2.Models/Metadata/DisplayPNGCharacteristicsDescriptor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -8,47 +9,54 @@ namespace Fido2NetLib /// /// /// - public class DisplayPNGCharacteristicsDescriptor + public sealed class DisplayPNGCharacteristicsDescriptor { /// /// Gets or sets the image width. /// - [JsonProperty("width", Required = Required.Always)] + [JsonPropertyName("width"), Required] public ulong Width { get; set; } + /// /// Gets or sets the image height. /// - [JsonProperty("height", Required = Required.Always)] + [JsonPropertyName("height"), Required] public ulong Height { get; set; } + /// /// Gets or sets the bit depth - bits per sample or per palette index. /// - [JsonProperty("bitDepth", Required = Required.Always)] + [JsonPropertyName("bitDepth"), Required] public byte BitDepth { get; set; } + /// /// Gets or sets the color type defines the PNG image type. /// - [JsonProperty("colorType", Required = Required.Always)] + [JsonPropertyName("colorType"), Required] public byte ColorType { get; set; } + /// /// Gets or sets the compression method used to compress the image data. /// - [JsonProperty("compression", Required = Required.Always)] + [JsonPropertyName("compression"), Required] public byte Compression { get; set; } + /// /// Gets or sets the filter method is the preprocessing method applied to the image data before compression. /// - [JsonProperty("filter", Required = Required.Always)] + [JsonPropertyName("filter"), Required] public byte Filter { get; set; } + /// /// Gets or sets the interlace method is the transmission order of the image data. /// - [JsonProperty("interlace", Required = Required.Always)] + [JsonPropertyName("interlace"), Required] public byte Interlace { get; set; } + /// /// Gets or sets the palette (1 to 256 palette entries). /// - [JsonProperty("plte")] + [JsonPropertyName("plte")] public RgbPaletteEntry[] Plte { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/EcdaaTrustAnchor.cs b/Src/Fido2.Models/Metadata/EcdaaTrustAnchor.cs index 45a13ae4..780e3f60 100644 --- a/Src/Fido2.Models/Metadata/EcdaaTrustAnchor.cs +++ b/Src/Fido2.Models/Metadata/EcdaaTrustAnchor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -9,38 +10,43 @@ namespace Fido2NetLib /// /// In the case of ECDAA attestation, the ECDAA-Issuer's trust anchor MUST be specified in this field. /// - public class EcdaaTrustAnchor + public sealed class EcdaaTrustAnchor { /// /// Gets or sets a base64url encoding of the result of ECPoint2ToB of the ECPoint2 X=P2​x​​. /// - [JsonProperty("x", Required = Required.Always)] + [JsonPropertyName("x"), Required] public string X { get; set; } + /// /// Gets or sets a base64url encoding of the result of ECPoint2ToB of the ECPoint2. /// - [JsonProperty("y", Required = Required.Always)] + [JsonPropertyName("y"), Required] public string Y { get; set; } + /// /// Gets or sets a base64url encoding of the result of BigNumberToB(c). /// - [JsonProperty("c", Required = Required.Always)] + [JsonPropertyName("c"), Required] public string C { get; set; } + /// /// Gets or sets the base64url encoding of the result of BigNumberToB(sx). /// - [JsonProperty("sx", Required = Required.Always)] + [JsonPropertyName("sx"), Required] public string SX { get; set; } + /// /// Gets or sets the base64url encoding of the result of BigNumberToB(sy). /// - [JsonProperty("sy", Required = Required.Always)] + [JsonPropertyName("sy"), Required] public string SY { get; set; } + /// /// Gets or sets a name of the Barreto-Naehrig elliptic curve for G1. /// "BN_P256", "BN_P638", "BN_ISOP256", and "BN_ISOP512" are supported. /// - [JsonProperty("G1Curve", Required = Required.Always)] + [JsonPropertyName("G1Curve"), Required] public string G1Curve { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/ExtensionDescriptor.cs b/Src/Fido2.Models/Metadata/ExtensionDescriptor.cs index a24f3392..725a257c 100644 --- a/Src/Fido2.Models/Metadata/ExtensionDescriptor.cs +++ b/Src/Fido2.Models/Metadata/ExtensionDescriptor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -13,8 +14,9 @@ public class ExtensionDescriptor /// /// Gets or sets the identifier that identifies the extension. /// - [JsonProperty("id", Required = Required.Always)] + [JsonPropertyName("id"), Required] public string Id { get; set; } + /// /// Gets or sets the tag. /// This field may be empty. @@ -22,8 +24,9 @@ public class ExtensionDescriptor /// /// The TAG of the extension if this was assigned. TAGs are assigned to extensions if they could appear in an assertion. /// - [JsonProperty("tag")] + [JsonPropertyName("tag")] public ushort Tag { get; set; } + /// /// Gets or sets arbitrary data further describing the extension and/or data needed to correctly process the extension. /// This field may be empty. @@ -31,8 +34,9 @@ public class ExtensionDescriptor /// /// This field MAY be missing or it MAY be empty. /// - [JsonProperty("data")] + [JsonPropertyName("data")] public string Data { get; set; } + /// /// Gets or sets a value indication whether an unknown extensions must be ignored (false) or must lead to an error (true) when the extension is to be processed by the FIDO Server, FIDO Client, ASM, or FIDO Authenticator. /// @@ -42,7 +46,7 @@ public class ExtensionDescriptor /// A value of true indicates that unknown extensions MUST result in an error. /// /// - [JsonProperty("fail_if_unknown", Required = Required.Always)] + [JsonPropertyName("fail_if_unknown"), Required] public bool Fail_If_Unknown { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/MetadataBLOBPayload.cs b/Src/Fido2.Models/Metadata/MetadataBLOBPayload.cs index 22ba86d8..cd137d58 100644 --- a/Src/Fido2.Models/Metadata/MetadataBLOBPayload.cs +++ b/Src/Fido2.Models/Metadata/MetadataBLOBPayload.cs @@ -1,6 +1,5 @@ -using System.Text.Json.Serialization; - -using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -18,7 +17,7 @@ public sealed class MetadataBLOBPayload /// /// This value MAY contain URL(s) pointing to further information, such as a full Terms and Conditions statement. /// - [JsonProperty("legalHeader")] + [JsonPropertyName("legalHeader")] public string LegalHeader { get; set; } /// @@ -27,24 +26,25 @@ public sealed class MetadataBLOBPayload /// /// Serial numbers MUST be consecutive and strictly monotonic, i.e. the successor BLOB will have a no value exactly incremented by one. /// - [JsonProperty("no", Required = Required.Always)] + [JsonPropertyName("no"), Required] public int Number { get; set; } + /// /// Gets or sets a formatted date (ISO-8601) when the next update will be provided at latest. /// - [JsonProperty("nextUpdate", Required = Required.Always)] + [JsonPropertyName("nextUpdate"), Required] public string NextUpdate { get; set; } /// /// Gets or sets a list of zero or more entries of . /// - [JsonProperty("entries", Required = Required.Always)] + [JsonPropertyName("entries"), Required] public MetadataBLOBPayloadEntry[] Entries { get; set; } /// /// The "alg" property from the original JWT header. Used to validate MetadataStatements. /// - [JsonProperty("jwtAlg", Required = Required.Default)] + [JsonPropertyName("jwtAlg")] public string JwtAlg { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/MetadataBLOBPayloadEntry.cs b/Src/Fido2.Models/Metadata/MetadataBLOBPayloadEntry.cs index 59efa5c3..e5a0130a 100644 --- a/Src/Fido2.Models/Metadata/MetadataBLOBPayloadEntry.cs +++ b/Src/Fido2.Models/Metadata/MetadataBLOBPayloadEntry.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -8,20 +9,22 @@ namespace Fido2NetLib /// /// /// - public class MetadataBLOBPayloadEntry + public sealed class MetadataBLOBPayloadEntry { /// /// Gets or sets the AAID. /// The AAID of the authenticator this metadata BLOB payload entry relates to. /// - [JsonProperty("aaid")] + [JsonPropertyName("aaid")] public string Aaid { get; set; } + /// /// Gets or sets the AAGUID. /// The Authenticator Attestation GUID. /// - [JsonProperty("aaguid")] + [JsonPropertyName("aaguid")] public string AaGuid { get; set; } + /// /// Gets or sets a list of the attestation certificate public key identifiers encoded as hex string. /// @@ -34,8 +37,9 @@ public class MetadataBLOBPayloadEntry /// /// FIDO U2F authenticators do not support AAID nor AAGUID, but they use attestation certificates dedicated to a single authenticator model. /// - [JsonProperty("attestationCertificateKeyIdentifiers")] + [JsonPropertyName("attestationCertificateKeyIdentifiers")] public string[] AttestationCertificateKeyIdentifiers { get; set; } + /// /// Gets or sets the hash value computed over the base64url encoding of the UTF-8 representation of the JSON encoded metadata statement available at url. /// @@ -43,8 +47,9 @@ public class MetadataBLOBPayloadEntry /// The hash algorithm related to the signature algorithm specified in the JWTHeader (see Metadata BLOB) must be used. /// This method of base64url encoding the UTF-8 representation is also used by JWT [JWT] to avoid encoding ambiguities. /// - [JsonProperty("hash")] + [JsonPropertyName("hash")] public string Hash { get; set; } + /// /// Gets or sets the Uniform resource locator (URL) of the encoded metadata statement for this authenticator model (identified by its AAID, AAGUID or attestationCertificateKeyIdentifier). /// @@ -52,41 +57,46 @@ public class MetadataBLOBPayloadEntry /// This URL must point to the base64url encoding of the UTF-8 representation of the JSON encoded metadata statement. /// If this field is missing, the metadata statement has not been published. /// - [JsonProperty("url")] + [JsonPropertyName("url")] public string Url { get; set; } + /// /// Gets or sets the status of the FIDO Biometric Certification of one or more biometric components of the Authenticator. /// - [JsonProperty("biometricStatusReports")] + [JsonPropertyName("biometricStatusReports")] public BiometricStatusReport[] BiometricStatusReports { get; set; } + /// /// Gets or sets an array of status reports applicable to this authenticator. /// - [JsonProperty("statusReports", Required = Required.Always)] + [JsonPropertyName("statusReports"), Required] public StatusReport[] StatusReports { get; set; } + /// /// Gets or sets ISO-8601 formatted date since when the status report array was set to the current value. /// - [JsonProperty("timeOfLastStatusChange")] + [JsonPropertyName("timeOfLastStatusChange")] public string TimeOfLastStatusChange { get; set; } + /// /// Gets or sets an URL of a list of rogue (i.e. untrusted) individual authenticators. /// - [JsonProperty("rogueListURL")] + [JsonPropertyName("rogueListURL")] public string RogueListURL { get; set; } + /// /// Gets or sets the hash value computed of . /// /// /// This hash value must be present and non-empty whenever rogueListURL is present. /// - [JsonProperty("rogueListHash")] + [JsonPropertyName("rogueListHash")] public string RogueListHash { get; set; } + /// /// Gets or sets the metadata statement. /// - [JsonProperty("metadataStatement")] - //[JsonConverter(typeof(Base64UrlConverter))] + [JsonPropertyName("metadataStatement")] public MetadataStatement MetadataStatement { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/MetadataStatement.cs b/Src/Fido2.Models/Metadata/MetadataStatement.cs index 15160eb7..1130e0f4 100644 --- a/Src/Fido2.Models/Metadata/MetadataStatement.cs +++ b/Src/Fido2.Models/Metadata/MetadataStatement.cs @@ -1,5 +1,5 @@ -using System; -using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -14,16 +14,18 @@ public class MetadataStatement /// /// Gets or sets the legalHeader, if present, contains a legal guide for accessing and using metadata, which itself MAY contain URL(s) pointing to further information, such as a full Terms and Conditions statement. /// - [JsonProperty("legalHeader")] + [JsonPropertyName("legalHeader")] public string LegalHeader { get; set; } + /// /// Gets or set the Authenticator Attestation ID. /// /// /// Note: FIDO UAF Authenticators support AAID, but they don't support AAGUID. /// - [JsonProperty("aaid")] + [JsonPropertyName("aaid")] public string Aaid { get; set; } + /// /// Gets or sets the Authenticator Attestation GUID. /// @@ -31,54 +33,63 @@ public class MetadataStatement /// This field MUST be set if the authenticator implements FIDO 2. /// Note: FIDO 2 Authenticators support AAGUID, but they don't support AAID. /// - [JsonProperty("aaguid")] + [JsonPropertyName("aaguid")] public string AaGuid { get; set; } + /// /// Gets or sets a list of the attestation certificate public key identifiers encoded as hex string. /// - [JsonProperty("attestationCertificateKeyIdentifiers")] + [JsonPropertyName("attestationCertificateKeyIdentifiers")] public string[] AttestationCertificateKeyIdentifiers { get; set; } + /// /// Gets or sets a human-readable, short description of the authenticator, in English. /// - [JsonProperty("description", Required = Required.Always)] + [JsonPropertyName("description"), Required] public string Description { get; set; } + /// /// Gets or set a list of human-readable short descriptions of the authenticator in different languages. /// - [JsonProperty("alternativeDescriptions")] + [JsonPropertyName("alternativeDescriptions")] public AlternativeDescriptions IETFLanguageCodesMembers { get; set; } + /// /// Gets or set earliest (i.e. lowest) trustworthy authenticatorVersion meeting the requirements specified in this metadata statement. /// - [JsonProperty("authenticatorVersion", Required = Required.Always)] + [JsonPropertyName("authenticatorVersion"), Required] public ulong AuthenticatorVersion { get; set; } + /// /// Gets or set the FIDO protocol family. /// The values "uaf", "u2f", and "fido2" are supported. /// - [JsonProperty("protocolFamily", Required = Required.Always)] + [JsonPropertyName("protocolFamily"), Required] public string ProtocolFamily { get; set; } + /// /// The Metadata Schema version /// Metadata schema version defines what schema of the metadata statement is currently present.The schema version of this version of the specification is 3. /// - [JsonProperty("schema", Required = Required.Always)] + [JsonPropertyName("schema"), Required] public ushort Schema { get; set; } + /// /// Gets or sets the FIDO unified protocol version(s) (related to the specific protocol family) supported by this authenticator. /// - [JsonProperty("upv", Required = Required.Always)] + [JsonPropertyName("upv"), Required] public UafVersion[] Upv { get; set; } + /// /// Gets or sets the list of authentication algorithms supported by the authenticator. /// - [JsonProperty("authenticationAlgorithms", Required = Required.Always)] + [JsonPropertyName("authenticationAlgorithms"), Required] public string[] AuthenticationAlgorithms { get; set; } + /// /// Gets or sets the list of public key formats supported by the authenticator during registration operations. /// - [JsonProperty("publicKeyAlgAndEncodings", Required = Required.Always)] + [JsonPropertyName("publicKeyAlgAndEncodings"), Required] public string[] PublicKeyAlgAndEncodings { get; set; } /// /// Gets or sets the supported attestation type(s). @@ -86,18 +97,21 @@ public class MetadataStatement /// /// For example: TAG_ATTESTATION_BASIC_FULL(0x3E07), TAG_ATTESTATION_BASIC_SURROGATE(0x3E08). /// - [JsonProperty("attestationTypes", Required = Required.Always)] + [JsonPropertyName("attestationTypes"), Required] public string[] AttestationTypes { get; set; } + /// /// Gets or sets a list of alternative VerificationMethodANDCombinations. /// - [JsonProperty("userVerificationDetails", Required = Required.Always)] + [JsonPropertyName("userVerificationDetails"), Required] public VerificationMethodDescriptor[][] UserVerificationDetails { get; set; } + /// /// Gets or sets a 16-bit number representing the bit fields defined by the KEY_PROTECTION constants. /// - [JsonProperty("keyProtection", Required = Required.Always)] + [JsonPropertyName("keyProtection"), Required] public string[] KeyProtection { get; set; } + /// /// Gets or sets a value indicating whether the Uauth private key is restricted by the authenticator to only sign valid FIDO signature assertions. /// @@ -108,64 +122,76 @@ public class MetadataStatement /// If this field is missing, the assumed value is isKeyRestricted=true. /// /// - [JsonProperty("isKeyRestricted")] + [JsonPropertyName("isKeyRestricted")] public bool IsKeyRestricted { get; set; } + /// /// Gets or sets a value indicating whether the Uauth key usage always requires a fresh user verification. /// - [JsonProperty("isFreshUserVerificationRequired")] + [JsonPropertyName("isFreshUserVerificationRequired")] public bool IsFreshUserVerificationRequired { get; set; } + /// /// Gets or sets a 16-bit number representing the bit fields defined by the MATCHER_PROTECTION constants. /// - [JsonProperty("matcherProtection", Required = Required.Always)] + [JsonPropertyName("matcherProtection"), Required] public string[] MatcherProtection { get; set; } + /// /// Gets or sets the authenticator's overall claimed cryptographic strength in bits (sometimes also called security strength or security level). /// /// If this value is absent, the cryptographic strength is unknown. - [JsonProperty("cryptoStrength")] + [JsonPropertyName("cryptoStrength")] public ushort CryptoStrength { get; set; } + /// /// Gets or sets a 32-bit number representing the bit fields defined by the ATTACHMENT_HINT constants. /// - [JsonProperty("attachmentHint")] + [JsonPropertyName("attachmentHint")] public string[] AttachmentHint { get; set; } + /// /// Gets or sets a 16-bit number representing a combination of the bit flags defined by the TRANSACTION_CONFIRMATION_DISPLAY constants. /// - [JsonProperty("tcDisplay", Required = Required.Always)] + [JsonPropertyName("tcDisplay"), Required] public string[] TcDisplay { get; set; } + /// /// Gets or sets the supported MIME content type [RFC2049] for the transaction confirmation display, such as text/plain or image/png. /// - [JsonProperty("tcDisplayContentType")] + [JsonPropertyName("tcDisplayContentType")] public string TcDisplayContentType { get; set; } + /// /// Gets or sets a list of alternative DisplayPNGCharacteristicsDescriptor. /// - [JsonProperty("tcDisplayPNGCharacteristics")] + [JsonPropertyName("tcDisplayPNGCharacteristics")] public DisplayPNGCharacteristicsDescriptor[] TcDisplayPNGCharacteristics { get; set; } + /// /// Gets or sets a list of a PKIX [RFC5280] X.509 certificate that is a valid trust anchor for this authenticator model. /// - [JsonProperty("attestationRootCertificates", Required = Required.Always)] + [JsonPropertyName("attestationRootCertificates"), Required] public string[] AttestationRootCertificates { get; set; } + /// /// Gets or set a list of trust anchors used for ECDAA attestation. /// - [JsonProperty("ecdaaTrustAnchors")] + [JsonPropertyName("ecdaaTrustAnchors")] public EcdaaTrustAnchor[] EcdaaTrustAnchors { get; set; } + /// /// Gets or set a data: url [RFC2397] encoded PNG [PNG] icon for the Authenticator. /// - [JsonProperty("icon")] + [JsonPropertyName("icon")] public string Icon { get; set; } + /// /// Gets or sets a list of extensions supported by the authenticator. /// - [JsonProperty("supportedExtensions")] + [JsonPropertyName("supportedExtensions")] public ExtensionDescriptor[] SupportedExtensions { get; set; } + /// /// Gets or sets a computed hash value of this . /// NOTE: This supports the internal infrastructure of Fido2Net and isn't intented to be used by user code. diff --git a/Src/Fido2.Models/Metadata/PatternAccuracyDescriptor.cs b/Src/Fido2.Models/Metadata/PatternAccuracyDescriptor.cs index fb2752af..ebbca2b8 100644 --- a/Src/Fido2.Models/Metadata/PatternAccuracyDescriptor.cs +++ b/Src/Fido2.Models/Metadata/PatternAccuracyDescriptor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -8,19 +9,19 @@ namespace Fido2NetLib /// /// /// - public class PatternAccuracyDescriptor + public sealed class PatternAccuracyDescriptor { /// /// Gets or sets the number of possible patterns (having the minimum length) out of which exactly one would be the right one, i.e. 1/probability in the case of equal distribution. /// - [JsonProperty("minComplexity", Required = Required.Always)] + [JsonPropertyName("minComplexity"), Required] public ulong MinComplexity { get; set; } /// /// Gets or sets maximum number of false attempts before the authenticator will block authentication using this method (at least temporarily). /// Zero (0) means it will never block. /// - [JsonProperty("maxRetries")] + [JsonPropertyName("maxRetries")] public ushort MaxRetries { get; set; } /// @@ -30,7 +31,7 @@ public class PatternAccuracyDescriptor /// /// All alternative user verification methods MUST be specified appropriately in the metadata under userVerificationDetails. /// - [JsonProperty("blockSlowdown")] + [JsonPropertyName("blockSlowdown")] public ushort BlockSlowdown { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/RgbPaletteEntry.cs b/Src/Fido2.Models/Metadata/RgbPaletteEntry.cs index 9ade0bfc..c492d6be 100644 --- a/Src/Fido2.Models/Metadata/RgbPaletteEntry.cs +++ b/Src/Fido2.Models/Metadata/RgbPaletteEntry.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -13,17 +14,19 @@ public class RgbPaletteEntry /// /// Gets or sets the red channel sample value. /// - [JsonProperty("r", Required = Required.Always)] + [JsonPropertyName("r"), Required] public ushort R { get; set; } + /// /// Gets or sets the green channel sample value. /// - [JsonProperty("g", Required = Required.Always)] + [JsonPropertyName("g"), Required] public ushort G { get; set; } + /// /// Gets or sets the blue channel sample value. /// - [JsonProperty("b", Required = Required.Always)] + [JsonPropertyName("b"), Required] public ushort B { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/StatusReport.cs b/Src/Fido2.Models/Metadata/StatusReport.cs index bf0c16d2..5b959f0e 100644 --- a/Src/Fido2.Models/Metadata/StatusReport.cs +++ b/Src/Fido2.Models/Metadata/StatusReport.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -8,55 +9,62 @@ namespace Fido2NetLib /// /// /// - public class StatusReport + public sealed class StatusReport { /// /// Gets or sets the status of the authenticator. /// Additional fields may be set depending on this value. /// - [JsonProperty("status", Required = Required.Always)] + [JsonPropertyName("status"), Required] public AuthenticatorStatus Status { get; set; } + /// /// Gets or set the ISO-8601 formatted date since when the status code was set, if applicable. /// If no date is given, the status is assumed to be effective while present. /// - [JsonProperty("effectiveDate")] + [JsonPropertyName("effectiveDate")] public string EffectiveDate { get; set; } + /// /// Gets or sets Base64-encoded PKIX certificate value related to the current status, if applicable. /// /// /// Base64-encoded [RFC4648] (not base64url!) / DER [ITU-X690-2008] PKIX certificate. /// - [JsonProperty("certificate")] + [JsonPropertyName("certificate")] public string Certificate { get; set; } + /// /// Gets or sets the HTTPS URL where additional information may be found related to the current status, if applicable. /// /// /// For example a link to a web page describing an available firmware update in the case of status , or a link to a description of an identified issue in the case of status . /// - [JsonProperty("url")] + [JsonPropertyName("url")] public string Url { get; set; } + /// /// Gets or sets a description of the externally visible aspects of the Authenticator Certification evaluation. /// - [JsonProperty("certificationDescriptor")] + [JsonPropertyName("certificationDescriptor")] public string CertificationDescriptor { get; set; } + /// /// Gets or sets the unique identifier for the issued Certification. /// - [JsonProperty("certificateNumber")] + [JsonPropertyName("certificateNumber")] public string CertificateNumber { get; set; } + /// /// Gets or set the version of the Authenticator Certification Policy the implementation is Certified to. /// - [JsonProperty("certificationPolicyVersion")] + [JsonPropertyName("certificationPolicyVersion")] public string CertificationPolicyVersion { get; set; } + /// /// Gets or set the version of the Authenticator Security Requirements the implementation is Certified to. /// - [JsonProperty("certificationRequirementsVersion")] + [JsonPropertyName("certificationRequirementsVersion")] public string CertificationRequirementsVersion { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/UafVersion.cs b/Src/Fido2.Models/Metadata/UafVersion.cs index 8ca8df4c..ceb79067 100644 --- a/Src/Fido2.Models/Metadata/UafVersion.cs +++ b/Src/Fido2.Models/Metadata/UafVersion.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -13,12 +13,12 @@ public class UafVersion /// /// Major version /// - [JsonProperty("major")] + [JsonPropertyName("major")] public ushort Major { get; set; } /// /// Minor version /// - [JsonProperty("minor")] + [JsonPropertyName("minor")] public ushort Minor { get; set; } } } diff --git a/Src/Fido2.Models/Metadata/UserVerificationMethods.cs b/Src/Fido2.Models/Metadata/UserVerificationMethods.cs index 58d5704b..b3401a64 100644 --- a/Src/Fido2.Models/Metadata/UserVerificationMethods.cs +++ b/Src/Fido2.Models/Metadata/UserVerificationMethods.cs @@ -1,6 +1,5 @@ using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -11,7 +10,7 @@ namespace Fido2NetLib * * https://fidoalliance.org/specs/fido-uaf-v1.0-ps-20141208/fido-uaf-reg-v1.0-ps-20141208.html#user-verification-methods */ - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(FidoEnumConverter))] public enum UserVerificationMethods { /// diff --git a/Src/Fido2.Models/Metadata/VerificationMethodDescriptor.cs b/Src/Fido2.Models/Metadata/VerificationMethodDescriptor.cs index 36874986..2f67f964 100644 --- a/Src/Fido2.Models/Metadata/VerificationMethodDescriptor.cs +++ b/Src/Fido2.Models/Metadata/VerificationMethodDescriptor.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -16,22 +16,25 @@ public class VerificationMethodDescriptor /// /// This value MUST be non-zero. /// - [JsonProperty("userVerificationMethod")] + [JsonPropertyName("userVerificationMethod")] public string UserVerificationMethod { get; set; } + /// /// Gets or sets a may optionally be used in the case of method USER_VERIFY_PASSCODE. /// - [JsonProperty("caDesc")] + [JsonPropertyName("caDesc")] public CodeAccuracyDescriptor CaDesc { get; set; } + /// /// Gets or sets a may optionally be used in the case of method USER_VERIFY_FINGERPRINT, USER_VERIFY_VOICEPRINT, USER_VERIFY_FACEPRINT, USER_VERIFY_EYEPRINT, or USER_VERIFY_HANDPRINT. /// - [JsonProperty("baDesc")] + [JsonPropertyName("baDesc")] public BiometricAccuracyDescriptor BaDesc { get; set; } + /// /// Gets or sets a may optionally be used in case of method USER_VERIFY_PATTERN. /// - [JsonProperty("paDesc")] + [JsonPropertyName("paDesc")] public PatternAccuracyDescriptor PaDesc { get; set; } } } diff --git a/Src/Fido2.Models/Objects/AttestationConveyancePreference.cs b/Src/Fido2.Models/Objects/AttestationConveyancePreference.cs index 01c7b226..0d846b1d 100644 --- a/Src/Fido2.Models/Objects/AttestationConveyancePreference.cs +++ b/Src/Fido2.Models/Objects/AttestationConveyancePreference.cs @@ -1,6 +1,5 @@ using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Fido2NetLib.Objects { @@ -8,7 +7,7 @@ namespace Fido2NetLib.Objects /// AttestationConveyancePreference. /// https://w3c.github.io/webauthn/#attestation-convey /// - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(FidoEnumConverter))] public enum AttestationConveyancePreference { /// diff --git a/Src/Fido2.Models/Objects/AttestationVerificationSuccess.cs b/Src/Fido2.Models/Objects/AttestationVerificationSuccess.cs index 9b99b95d..801953a9 100644 --- a/Src/Fido2.Models/Objects/AttestationVerificationSuccess.cs +++ b/Src/Fido2.Models/Objects/AttestationVerificationSuccess.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Fido2NetLib.Objects { diff --git a/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs b/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs index 6ade076f..d99efca0 100644 --- a/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs +++ b/Src/Fido2.Models/Objects/AuthenticationExtensionsClientInputs.cs @@ -1,40 +1,49 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Fido2NetLib.Objects { /// /// This is a dictionary containing the client extension output values for zero or more WebAuthn Extensions /// - public class AuthenticationExtensionsClientInputs + public sealed class AuthenticationExtensionsClientInputs { /// /// This extension allows for passing of conformance tests /// - [JsonProperty("example.extension", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("example.extension")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public object Example { get; set; } + /// /// 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 /// - [JsonProperty("appid", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("appid")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string AppID { get; set; } + /// /// This extension allows a WebAuthn Relying Party to guide the selection of the authenticator that will be leveraged when creating the credential. It is intended primarily for Relying Parties that wish to tightly control the experience around credential creation. /// https://www.w3.org/TR/webauthn/#sctn-authenticator-selection-extension /// - [JsonProperty("authnSel", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("authnSel")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public byte[][] AuthenticatorSelection { get; set; } + /// /// This extension enables the WebAuthn Relying Party to determine which extensions the authenticator supports. /// https://www.w3.org/TR/webauthn/#sctn-supported-extensions-extension /// - [JsonProperty("exts", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("exts")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? Extensions { get; set; } + /// /// This extension enables use of a user verification method. /// https://www.w3.org/TR/webauthn/#sctn-uvm-extension /// - [JsonProperty("uvm", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("uvm")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? UserVerificationMethod { get; set; } } } diff --git a/Src/Fido2.Models/Objects/AuthenticationExtensionsClientOutputs.cs b/Src/Fido2.Models/Objects/AuthenticationExtensionsClientOutputs.cs index 3d50afd9..1883465e 100644 --- a/Src/Fido2.Models/Objects/AuthenticationExtensionsClientOutputs.cs +++ b/Src/Fido2.Models/Objects/AuthenticationExtensionsClientOutputs.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Fido2NetLib.Objects { @@ -7,31 +7,40 @@ public class AuthenticationExtensionsClientOutputs /// /// This extension allows for passing of conformance tests /// - [JsonProperty("example.extension", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("example.extension")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public object Example { get; set; } + +#nullable enable + /// /// 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 /// - [JsonProperty("appid", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("appid")] public bool AppID { get; set; } + /// /// This extension allows a WebAuthn Relying Party to guide the selection of the authenticator that will be leveraged when creating the credential. It is intended primarily for Relying Parties that wish to tightly control the experience around credential creation. /// https://www.w3.org/TR/webauthn/#sctn-authenticator-selection-extension /// - [JsonProperty("authnSel", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("authnSel")] public bool AuthenticatorSelection { get; set; } + /// /// This extension enables the WebAuthn Relying Party to determine which extensions the authenticator supports. /// https://www.w3.org/TR/webauthn/#sctn-supported-extensions-extension /// - [JsonProperty("exts", NullValueHandling = NullValueHandling.Ignore)] - public string[] Extensions { get; set; } + [JsonPropertyName("exts")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string[]? Extensions { get; set; } + /// /// This extension enables use of a user verification method. /// https://www.w3.org/TR/webauthn/#sctn-uvm-extension /// - [JsonProperty("uvm", NullValueHandling = NullValueHandling.Ignore)] - public ulong[][] UserVerificationMethod { get; set; } + [JsonPropertyName("uvm")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ulong[][]? UserVerificationMethod { get; set; } } } diff --git a/Src/Fido2.Models/Objects/AuthenticatorAttachment.cs b/Src/Fido2.Models/Objects/AuthenticatorAttachment.cs index 7c85186c..ecb20848 100644 --- a/Src/Fido2.Models/Objects/AuthenticatorAttachment.cs +++ b/Src/Fido2.Models/Objects/AuthenticatorAttachment.cs @@ -1,6 +1,5 @@ using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Fido2NetLib.Objects { @@ -13,7 +12,7 @@ namespace Fido2NetLib.Objects /// Note: An authenticator attachment modality selection option is available only in the [[Create]](origin, options, sameOriginWithAncestors) operation. The Relying Party may use it to, for example, ensure the user has a roaming credential for authenticating on another client device; or to specifically register a platform credential for easier reauthentication using a particular client device. The [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) operation has no authenticator attachment modality selection option, so the Relying Party SHOULD accept any of the user’s registered credentials. The client and user will then use whichever is available and convenient at the time. /// https://w3c.github.io/webauthn/#attachment /// - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(FidoEnumConverter))] public enum AuthenticatorAttachment { /// diff --git a/Src/Fido2.Models/Objects/AuthenticatorTransport.cs b/Src/Fido2.Models/Objects/AuthenticatorTransport.cs index a3dc6c3e..37ee495d 100644 --- a/Src/Fido2.Models/Objects/AuthenticatorTransport.cs +++ b/Src/Fido2.Models/Objects/AuthenticatorTransport.cs @@ -1,6 +1,5 @@ using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Fido2NetLib.Objects { @@ -8,7 +7,7 @@ namespace Fido2NetLib.Objects /// Authenticators may implement various transports for communicating with clients. This enumeration defines hints as to how clients might communicate with a particular authenticator in order to obtain an assertion for a specific credential. Note that these hints represent the WebAuthn Relying Party's best belief as to how an authenticator may be reached. A Relying Party may obtain a list of transports hints from some attestation statement formats or via some out-of-band mechanism; it is outside the scope of this specification to define that mechanism. /// https://w3c.github.io/webauthn/#transport /// - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(FidoEnumConverter))] public enum AuthenticatorTransport { /// diff --git a/Src/Fido2.Models/Objects/KeyProtection.cs b/Src/Fido2.Models/Objects/KeyProtection.cs index 50d9fa6d..06f86d91 100644 --- a/Src/Fido2.Models/Objects/KeyProtection.cs +++ b/Src/Fido2.Models/Objects/KeyProtection.cs @@ -1,6 +1,5 @@ using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -12,13 +11,12 @@ namespace Fido2NetLib * https://fidoalliance.org/specs/fido-uaf-v1.0-ps-20141208/fido-uaf-reg-v1.0-ps-20141208.html#key-protection-types * type {Object} */ - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(FidoEnumConverter))] public enum KeyProtection { /// /// This flag must be set if the authenticator uses software-based key management. Exclusive in authenticator metadata with KEY_PROTECTION_HARDWARE, KEY_PROTECTION_TEE, KEY_PROTECTION_SECURE_ELEMENT /// - [EnumMember(Value = "software")] SOFTWARE = 1, /// diff --git a/Src/Fido2.Models/Objects/PublicKeyCredentialDescriptor.cs b/Src/Fido2.Models/Objects/PublicKeyCredentialDescriptor.cs index 750941ae..9853d2aa 100644 --- a/Src/Fido2.Models/Objects/PublicKeyCredentialDescriptor.cs +++ b/Src/Fido2.Models/Objects/PublicKeyCredentialDescriptor.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Fido2NetLib.Objects { @@ -22,20 +22,23 @@ public PublicKeyCredentialDescriptor() /// /// This member contains the type of the public key credential the caller is referring to. /// - [JsonProperty("type")] + [JsonPropertyName("type")] public PublicKeyCredentialType? Type { get; set; } = PublicKeyCredentialType.PublicKey; /// /// This member contains the credential ID of the public key credential the caller is referring to. /// [JsonConverter(typeof(Base64UrlConverter))] - [JsonProperty("id")] + [JsonPropertyName("id")] public byte[] Id { get; set; } +#nullable enable + /// /// This OPTIONAL member contains a hint as to how the client might communicate with the managing authenticator of the public key credential the caller is referring to. /// - [JsonProperty("transports", NullValueHandling = NullValueHandling.Ignore)] - public AuthenticatorTransport[] Transports { get; set; } + [JsonPropertyName("transports")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public AuthenticatorTransport[]? Transports { get; set; } }; } diff --git a/Src/Fido2.Models/Objects/PublicKeyCredentialType.cs b/Src/Fido2.Models/Objects/PublicKeyCredentialType.cs index 30961ea5..e4842b62 100644 --- a/Src/Fido2.Models/Objects/PublicKeyCredentialType.cs +++ b/Src/Fido2.Models/Objects/PublicKeyCredentialType.cs @@ -1,6 +1,5 @@ using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Fido2NetLib.Objects { @@ -8,7 +7,7 @@ namespace Fido2NetLib.Objects /// PublicKeyCredentialType. /// https://w3c.github.io/webauthn/#enumdef-publickeycredentialtype /// - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(FidoEnumConverter))] public enum PublicKeyCredentialType { [EnumMember(Value = "public-key")] diff --git a/Src/Fido2.Models/Objects/UserVerificationRequirement.cs b/Src/Fido2.Models/Objects/UserVerificationRequirement.cs index 90befcff..39e0ac7c 100644 --- a/Src/Fido2.Models/Objects/UserVerificationRequirement.cs +++ b/Src/Fido2.Models/Objects/UserVerificationRequirement.cs @@ -1,6 +1,5 @@ using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Fido2NetLib.Objects { @@ -8,7 +7,7 @@ namespace Fido2NetLib.Objects /// A WebAuthn Relying Party may require user verification for some of its operations but not for others, and may use this type to express its needs. /// https://w3c.github.io/webauthn/#enumdef-userverificationrequirement /// - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(FidoEnumConverter))] public enum UserVerificationRequirement { /// diff --git a/Src/Fido2.Models/Objects/Version.cs b/Src/Fido2.Models/Objects/Version.cs index 4a4ec29f..c925397f 100644 --- a/Src/Fido2.Models/Objects/Version.cs +++ b/Src/Fido2.Models/Objects/Version.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -14,13 +14,13 @@ public class Version /// /// Major version. /// - [JsonProperty("major")] + [JsonPropertyName("major")] public ushort Major { get; set; } /// /// Minor version. /// - [JsonProperty("minor")] + [JsonPropertyName("minor")] public ushort Minor { get; set; } } } diff --git a/Src/Fido2/AttestationFormat/AndroidSafetyNet.cs b/Src/Fido2/AttestationFormat/AndroidSafetyNet.cs index 516d3567..f86901e0 100644 --- a/Src/Fido2/AttestationFormat/AndroidSafetyNet.cs +++ b/Src/Fido2/AttestationFormat/AndroidSafetyNet.cs @@ -5,9 +5,10 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Text.Json; + using Fido2NetLib.Objects; using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json.Linq; using PeterO.Cbor; namespace Fido2NetLib @@ -16,7 +17,7 @@ internal sealed class AndroidSafetyNet : AttestationVerifier { private readonly int _driftTolerance; - private X509Certificate2 GetX509Certificate(string certString) + private static X509Certificate2 GetX509Certificate(string certString) { try { @@ -56,33 +57,36 @@ public override (AttestationType, X509Certificate2[]) Verify() if (jwtParts.Length != 3) throw new Fido2VerificationException("SafetyNet response JWT does not have the 3 expected components"); - var jwtHeaderString = jwtParts.First(); - var jwtHeaderJSON = JObject.Parse(Encoding.UTF8.GetString(Base64Url.Decode(jwtHeaderString))); + string jwtHeaderString = jwtParts[0]; + + using var jwtHeaderJsonDoc = JsonDocument.Parse(Base64Url.Decode(jwtHeaderString)); + var jwtHeaderJson = jwtHeaderJsonDoc.RootElement; - var x5cArray = jwtHeaderJSON["x5c"] as JArray; + string[] x5cStrings = jwtHeaderJson.TryGetProperty("x5c", out var x5cEl) && x5cEl.ValueKind is JsonValueKind.Array + ? x5cEl.ToStringArray() + : throw new Fido2VerificationException("SafetyNet response JWT header missing x5c"); - if (x5cArray is null) - throw new Fido2VerificationException("SafetyNet response JWT header missing x5c"); - var x5cStrings = x5cArray.Values().ToList(); - if (x5cStrings.Count == 0) + if (x5cStrings.Length is 0) throw new Fido2VerificationException("No keys were present in the TOC header in SafetyNet response JWT"); - var certs = new List(); + var certs = new X509Certificate2[x5cStrings.Length]; var keys = new List(); - foreach (var certString in x5cStrings) + for (int i = 0; i < certs.Length; i++) { + var certString = x5cStrings[i]; var cert = GetX509Certificate(certString); - certs.Add(cert); + certs[i] = cert; - var ecdsaPublicKey = cert.GetECDsaPublicKey(); - if (ecdsaPublicKey != null) + if (cert.GetECDsaPublicKey() is ECDsa ecdsaPublicKey) + { keys.Add(new ECDsaSecurityKey(ecdsaPublicKey)); - - var rsaPublicKey = cert.GetRSAPublicKey(); - if (rsaPublicKey != null) + } + else if (cert.GetRSAPublicKey() is RSA rsaPublicKey) + { keys.Add(new RsaSecurityKey(rsaPublicKey)); + } } var validationParameters = new TokenValidationParameters diff --git a/Src/Fido2/AttestationFormat/Packed.cs b/Src/Fido2/AttestationFormat/Packed.cs index 50d63a9e..80a4785e 100644 --- a/Src/Fido2/AttestationFormat/Packed.cs +++ b/Src/Fido2/AttestationFormat/Packed.cs @@ -2,9 +2,10 @@ using System.Linq; using System.Runtime.Serialization; using System.Security.Cryptography.X509Certificates; +using System.Text.Json.Serialization; + using Fido2NetLib.Objects; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; + using PeterO.Cbor; namespace Fido2NetLib @@ -18,7 +19,7 @@ internal enum UndesiredAuthenticatorStatus REVOKED = AuthenticatorStatus.REVOKED }; - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(FidoEnumConverter))] internal enum MetadataAttestationType { [EnumMember(Value = "basic_full")] diff --git a/Src/Fido2/AuthenticatorResponse.cs b/Src/Fido2/AuthenticatorResponse.cs index 7835a89a..41cdbadd 100644 --- a/Src/Fido2/AuthenticatorResponse.cs +++ b/Src/Fido2/AuthenticatorResponse.cs @@ -1,7 +1,7 @@ using System; using System.Linq; -using System.Text; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Fido2NetLib { @@ -10,23 +10,22 @@ namespace Fido2NetLib /// public class AuthenticatorResponse { - protected AuthenticatorResponse(ReadOnlySpan clientDataJson) + protected AuthenticatorResponse(ReadOnlySpan utf8EncodedJson) { - if (clientDataJson.Length is 0) - throw new Fido2VerificationException("clientDataJson may not be empty"); + if (utf8EncodedJson.Length is 0) + throw new Fido2VerificationException("utf8EncodedJson may not be empty"); // 1. Let JSONtext be the result of running UTF-8 decode on the value of response.clientDataJSON - var jsonText = Encoding.UTF8.GetString(clientDataJson); - + // 2. Let C, the client data claimed as collected during the credential creation, be the result of running an implementation-specific JSON parser on JSONtext // Note: C may be any implementation-specific data structure representation, as long as C’s components are referenceable, as required by this algorithm. // We call this AuthenticatorResponse AuthenticatorResponse response; try { - response = JsonConvert.DeserializeObject(jsonText); + response = JsonSerializer.Deserialize(utf8EncodedJson)!; } - catch (Exception e) when (e is JsonReaderException || e is JsonSerializationException) + catch (Exception e) when (e is JsonException) { throw new Fido2VerificationException("Malformed clientDataJson"); } @@ -39,18 +38,20 @@ protected AuthenticatorResponse(ReadOnlySpan clientDataJson) } #nullable disable - [JsonConstructor] - private AuthenticatorResponse() + public AuthenticatorResponse() // for deserialization { } #nullable enable + [JsonPropertyName("type")] public string Type { get; set; } [JsonConverter(typeof(Base64UrlConverter))] + [JsonPropertyName("challenge")] public byte[] Challenge { get; set; } + [JsonPropertyName("origin")] public string Origin { get; set; } // todo: add TokenBinding https://www.w3.org/TR/webauthn/#dictdef-tokenbinding diff --git a/Src/Fido2/Extensions/JsonElementExtensions.cs b/Src/Fido2/Extensions/JsonElementExtensions.cs new file mode 100644 index 00000000..69ba6966 --- /dev/null +++ b/Src/Fido2/Extensions/JsonElementExtensions.cs @@ -0,0 +1,23 @@ +using System.Text.Json; + +namespace Fido2NetLib +{ + internal static class JsonElementExtensions + { + public static string[] ToStringArray(this in JsonElement el) + { + var result = new string[el.GetArrayLength()]; + + int i = 0; + + foreach (var item in el.EnumerateArray()) + { + result[i] = item.GetString()!; + + i++; + } + + return result; + } + } +} diff --git a/Src/Fido2/Fido2.csproj b/Src/Fido2/Fido2.csproj index 8405940f..ee58c0d8 100644 --- a/Src/Fido2/Fido2.csproj +++ b/Src/Fido2/Fido2.csproj @@ -5,6 +5,7 @@ + @@ -16,7 +17,6 @@ - @@ -31,6 +31,7 @@ --> + diff --git a/Src/Fido2/IMetadataRepository.cs b/Src/Fido2/IMetadataRepository.cs index 147fd572..da3765a1 100644 --- a/Src/Fido2/IMetadataRepository.cs +++ b/Src/Fido2/IMetadataRepository.cs @@ -6,6 +6,6 @@ public interface IMetadataRepository { Task GetBLOBAsync(); - Task GetMetadataStatement(MetadataBLOBPayload blob, MetadataBLOBPayloadEntry entry); + Task GetMetadataStatementAsync(MetadataBLOBPayload blob, MetadataBLOBPayloadEntry entry); } } diff --git a/Src/Fido2/IMetadataService.cs b/Src/Fido2/IMetadataService.cs index 14f5580d..cd015c51 100644 --- a/Src/Fido2/IMetadataService.cs +++ b/Src/Fido2/IMetadataService.cs @@ -34,6 +34,6 @@ public interface IMetadataService /// /// Initializes the metadata service. /// - Task Initialize(); + Task InitializeAsync(); } } diff --git a/Src/Fido2/Metadata/ConformanceMetadataRepository.cs b/Src/Fido2/Metadata/ConformanceMetadataRepository.cs index 4690d00c..edcedabd 100644 --- a/Src/Fido2/Metadata/ConformanceMetadataRepository.cs +++ b/Src/Fido2/Metadata/ConformanceMetadataRepository.cs @@ -5,13 +5,12 @@ using System.Net.Http; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.IdentityModel.Tokens; -using Microsoft.IdentityModel.Json.Linq; -using Newtonsoft.Json.Linq; namespace Fido2NetLib -{ +{ public sealed class ConformanceMetadataRepository : IMetadataRepository { private const string ROOT_CERT = "MIICaDCCAe6gAwIBAgIPBCqih0DiJLW7+UHXx/o1MAoGCCqGSM49BAMDMGcxCzAJ" + @@ -41,7 +40,7 @@ public ConformanceMetadataRepository(HttpClient client, string origin) _origin = origin; } - public Task GetMetadataStatement(MetadataBLOBPayload blob, MetadataBLOBPayloadEntry entry) + public Task GetMetadataStatementAsync(MetadataBLOBPayload blob, MetadataBLOBPayloadEntry entry) { return Task.FromResult(entry.MetadataStatement); } @@ -53,10 +52,10 @@ public async Task GetBLOBAsync() endpoint = _origin }; - var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(req), Encoding.UTF8, "application/json"); + var content = new StringContent(JsonSerializer.Serialize(req), Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync(_getEndpointsUrl, content); - var result = Newtonsoft.Json.JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - var conformanceEndpoints = new List(result.Result); + var result = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); + var conformanceEndpoints = result.Result; var combinedBlob = new MetadataBLOBPayload { @@ -66,9 +65,9 @@ public async Task GetBLOBAsync() var entries = new List(); - foreach(var BLOBUrl in conformanceEndpoints) + foreach(var blobUrl in conformanceEndpoints) { - var rawBlob = await DownloadStringAsync(BLOBUrl); + var rawBlob = await DownloadStringAsync(blobUrl); MetadataBLOBPayload blob; @@ -97,14 +96,14 @@ public async Task GetBLOBAsync() return combinedBlob; } - protected async Task DownloadStringAsync(string url) + protected Task DownloadStringAsync(string url) { - return await _httpClient.GetStringAsync(url); + return _httpClient.GetStringAsync(url); } - protected async Task DownloadDataAsync(string url) + protected Task DownloadDataAsync(string url) { - return await _httpClient.GetByteArrayAsync(url); + return _httpClient.GetByteArrayAsync(url); } private X509Certificate2 GetX509Certificate(string key) @@ -130,21 +129,19 @@ public async Task DeserializeAndValidateBlob(string rawBLOB if (jwtParts.Length != 3) throw new ArgumentException("The JWT does not have the 3 expected components"); - var blobHeader = jwtParts.First(); - var tokenHeader = JObject.Parse(Encoding.UTF8.GetString(Base64Url.Decode(blobHeader))); + var blobHeader = jwtParts[0]; + using var jsonDoc = JsonDocument.Parse(Base64Url.Decode(blobHeader)); + var tokenHeader = jsonDoc.RootElement; - var blobAlg = tokenHeader["alg"]?.Value(); + var blobAlg = tokenHeader.TryGetProperty("alg", out var algEl) + ? algEl.GetString()! + : throw new ArgumentNullException("No alg value was present in the BLOB header."); - if(blobAlg is null) - throw new ArgumentNullException("No alg value was present in the BLOB header."); - - var x5cArray = tokenHeader["x5c"] as JArray; - - if (x5cArray is null) - throw new ArgumentException("No x5c array was present in the BLOB header."); + var blobCertStrings = tokenHeader.TryGetProperty("x5c", out var x5cEl) && x5cEl.ValueKind is JsonValueKind.Array + ? x5cEl.ToStringArray() + : throw new ArgumentException("No x5c array was present in the BLOB header."); var rootCert = GetX509Certificate(ROOT_CERT); - var blobCertStrings = x5cArray.Values().ToList(); var blobCertificates = new List(); var blobPublicKeys = new List(); @@ -211,7 +208,7 @@ public async Task DeserializeAndValidateBlob(string rawBLOB // otherwise we have to manually validate that the root in the chain we are testing is the root we downloaded if (rootCert.Thumbprint == certChain.ChainElements[^1].Certificate.Thumbprint && // and that the number of elements in the chain accounts for what was in x5c plus the root we added - certChain.ChainElements.Count == (blobCertStrings.Count + 1) && + certChain.ChainElements.Count == (blobCertStrings.Length + 1) && // and that the root cert has exactly one status listed against it certChain.ChainElements[^1].ChainElementStatus.Length == 1 && // and that that status is a status of exactly UntrustedRoot @@ -233,7 +230,7 @@ public async Task DeserializeAndValidateBlob(string rawBLOB var blobPayload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson(); - var blob = Newtonsoft.Json.JsonConvert.DeserializeObject(blobPayload); + var blob = JsonSerializer.Deserialize(blobPayload); blob.JwtAlg = blobAlg; return blob; } diff --git a/Src/Fido2/Metadata/Fido2MetadataServiceRepository.cs b/Src/Fido2/Metadata/Fido2MetadataServiceRepository.cs index 4a4a7945..6b5f09b1 100644 --- a/Src/Fido2/Metadata/Fido2MetadataServiceRepository.cs +++ b/Src/Fido2/Metadata/Fido2MetadataServiceRepository.cs @@ -6,9 +6,9 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json.Linq; namespace Fido2NetLib { @@ -44,13 +44,12 @@ public Fido2MetadataServiceRepository(HttpClient httpClient) _httpClient = httpClient ?? new HttpClient(); } - public async Task GetMetadataStatement(MetadataBLOBPayload blob, MetadataBLOBPayloadEntry entry) + public async Task GetMetadataStatementAsync(MetadataBLOBPayload blob, MetadataBLOBPayloadEntry entry) { var statementBase64Url = await DownloadStringAsync(entry.Url); var statementBytes = Base64Url.Decode(statementBase64Url); - var statementString = Encoding.UTF8.GetString(statementBytes, 0, statementBytes.Length); - var statement = Newtonsoft.Json.JsonConvert.DeserializeObject(statementString); + var statement = JsonSerializer.Deserialize(statementBytes)!; using (HashAlgorithm hasher = CryptoUtils.GetHasher(new HashAlgorithmName(blob.JwtAlg))) { @@ -132,47 +131,43 @@ private async Task DeserializeAndValidateBlobAsync(string r if (jwtParts.Length != 3) throw new ArgumentException("The JWT does not have the 3 expected components"); - var blobHeaderString = jwtParts.First(); - var blobHeader = JObject.Parse(Encoding.UTF8.GetString(Base64Url.Decode(blobHeaderString))); + var blobHeaderString = jwtParts[0]; + using var blobHeaderDoc = JsonDocument.Parse(Base64Url.Decode(blobHeaderString)); + var blobHeader = blobHeaderDoc.RootElement; - var blobAlg = blobHeader["alg"]?.Value(); + string blobAlg = blobHeader.TryGetProperty("alg", out var algEl) + ? algEl.GetString()! + : throw new ArgumentNullException("No alg value was present in the BLOB header."); - if (blobAlg is null) - throw new ArgumentNullException("No alg value was present in the BLOB header."); - - var x5cArray = blobHeader["x5c"] as JArray; - - if (x5cArray is null) - throw new Exception("No x5c array was present in the BLOB header."); - - var keyStrings = x5cArray.Values().ToList(); + string[] keyStrings = blobHeader.TryGetProperty("x5c", out var x5cEl) && x5cEl.ValueKind is JsonValueKind.Array + ? x5cEl.ToStringArray() + : throw new ArgumentNullException("No x5c array was present in the BLOB header."); - if (keyStrings.Count == 0) + if (keyStrings.Length is 0) throw new ArgumentException("No keys were present in the BLOB header."); var rootCert = GetX509Certificate(ROOT_CERT); - var blobCerts = keyStrings.Select(o => GetX509Certificate(o)).ToArray(); - - var keys = new List(); + var blobCerts = new X509Certificate2[keyStrings.Length]; + var keys = new SecurityKey[keyStrings.Length]; - foreach (var certString in keyStrings) + for (int i = 0; i < blobCerts.Length; i++) { - var cert = GetX509Certificate(certString); + var cert = GetX509Certificate(keyStrings[i]); - var ecdsaPublicKey = cert.GetECDsaPublicKey(); - if (ecdsaPublicKey != null) + blobCerts[i] = cert; + + if (cert.GetECDsaPublicKey() is ECDsa ecdsaPublicKey) { - keys.Add(new ECDsaSecurityKey(ecdsaPublicKey)); - continue; + keys[i] = new ECDsaSecurityKey(ecdsaPublicKey); } - - var rsaPublicKey = cert.GetRSAPublicKey(); - if (rsaPublicKey != null) + else if (cert.GetRSAPublicKey() is RSA rsaPublicKey) + { + keys[i] = new RsaSecurityKey(rsaPublicKey); + } + else { - keys.Add(new RsaSecurityKey(rsaPublicKey)); - continue; + throw new Fido2MetadataException("Unknown certificate algorithm"); } - throw new Fido2MetadataException("Unknown certificate algorithm"); } var blobPublicKeys = keys.ToArray(); @@ -224,7 +219,7 @@ private async Task DeserializeAndValidateBlobAsync(string r // otherwise we have to manually validate that the root in the chain we are testing is the root we downloaded if (rootCert.Thumbprint == certChain.ChainElements[^1].Certificate.Thumbprint && // and that the number of elements in the chain accounts for what was in x5c plus the root we added - certChain.ChainElements.Count == (keyStrings.Count + 1) && + certChain.ChainElements.Count == (keyStrings.Length + 1) && // and that the root cert has exactly one status listed against it certChain.ChainElements[^1].ChainElementStatus.Length == 1 && // and that that status is a status of exactly UntrustedRoot @@ -246,7 +241,7 @@ private async Task DeserializeAndValidateBlobAsync(string r var blobPayload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson(); - var blob = Newtonsoft.Json.JsonConvert.DeserializeObject(blobPayload); + var blob = JsonSerializer.Deserialize(blobPayload)!; blob.JwtAlg = blobAlg; return blob; } diff --git a/Src/Fido2/Metadata/FileSystemMetadataRepository.cs b/Src/Fido2/Metadata/FileSystemMetadataRepository.cs index 343ba321..cea9c435 100644 --- a/Src/Fido2/Metadata/FileSystemMetadataRepository.cs +++ b/Src/Fido2/Metadata/FileSystemMetadataRepository.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; -using Newtonsoft.Json; namespace Fido2NetLib { @@ -19,7 +19,7 @@ public FileSystemMetadataRepository(string path) _entries = new Dictionary(); } - public async Task GetMetadataStatement(MetadataBLOBPayload blob, MetadataBLOBPayloadEntry entry) + public async Task GetMetadataStatementAsync(MetadataBLOBPayload blob, MetadataBLOBPayloadEntry entry) { if (_blob is null) await GetBLOBAsync(); @@ -40,7 +40,7 @@ public Task GetBLOBAsync() foreach (var filename in Directory.GetFiles(_path)) { var rawStatement = File.ReadAllText(filename); - var statement = JsonConvert.DeserializeObject(rawStatement); + var statement = JsonSerializer.Deserialize(rawStatement)!; var conformanceEntry = new MetadataBLOBPayloadEntry { AaGuid = statement.AaGuid, diff --git a/Src/Fido2/Metadata/MDSGetEndpointResponse.cs b/Src/Fido2/Metadata/MDSGetEndpointResponse.cs index eb2d4ec9..41878ddd 100644 --- a/Src/Fido2/Metadata/MDSGetEndpointResponse.cs +++ b/Src/Fido2/Metadata/MDSGetEndpointResponse.cs @@ -1,15 +1,15 @@ #nullable disable -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Fido2NetLib { internal sealed class MDSGetEndpointResponse { - [JsonProperty("status", Required = Required.Always)] + [JsonPropertyName("status")] public string Status { get; set; } - [JsonProperty("result", Required = Required.Always)] + [JsonPropertyName("result")] public string[] Result { get; set; } } } diff --git a/Src/Fido2/Objects/AuthenticatorData.cs b/Src/Fido2/Objects/AuthenticatorData.cs index 8b2c6cb3..9f3d2a71 100644 --- a/Src/Fido2/Objects/AuthenticatorData.cs +++ b/Src/Fido2/Objects/AuthenticatorData.cs @@ -68,7 +68,7 @@ public class AuthenticatorData /// public Extensions Extensions; - public AuthenticatorData(byte[] rpIdHash, AuthenticatorFlags flags, uint signCount, AttestedCredentialData acd, Extensions exts) + public AuthenticatorData(byte[] rpIdHash, AuthenticatorFlags flags, uint signCount, AttestedCredentialData acd, Extensions exts = null) { RpIdHash = rpIdHash; _flags = flags; diff --git a/Src/Fido2/SimpleMetadataService.cs b/Src/Fido2/SimpleMetadataService.cs index 6f8ec743..00615161 100644 --- a/Src/Fido2/SimpleMetadataService.cs +++ b/Src/Fido2/SimpleMetadataService.cs @@ -53,7 +53,7 @@ protected virtual async Task LoadEntryStatement(IMetadataRepository repository, { if (entry.AaGuid != null) { - var statement = await repository.GetMetadataStatement(blob, entry); + var statement = await repository.GetMetadataStatementAsync(blob, entry); if (!string.IsNullOrWhiteSpace(statement.AaGuid)) { @@ -79,7 +79,7 @@ protected virtual async Task InitializeRepository(IMetadataRepository repository } } - public virtual async Task Initialize() + public virtual async Task InitializeAsync() { foreach (var repository in _repositories) { diff --git a/Src/Fido2/ToStringJsonConverter.cs b/Src/Fido2/ToStringJsonConverter.cs index ac25da6d..eac8b6d5 100644 --- a/Src/Fido2/ToStringJsonConverter.cs +++ b/Src/Fido2/ToStringJsonConverter.cs @@ -2,31 +2,30 @@ using System; using System.Reflection; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Fido2NetLib { - public class ToStringJsonConverter : JsonConverter + public class ToStringJsonConverter : JsonConverter + where T: notnull { - public override bool CanConvert(Type objectType) + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return true; - } + string text = reader.GetString(); - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var p = objectType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(string) }, null); + var p = typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(string) }, null); if (p != null) { - return p.Invoke(new object[] { (string)reader.Value }); + return (T)p.Invoke(new object[] { text }); } - return null; + throw new JsonException("Invalid T"); } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { - writer.WriteValue(value.ToString()); + writer.WriteStringValue(value.ToString()); } } } diff --git a/Src/Fido2/TypedString.cs b/Src/Fido2/TypedString.cs index 46389a62..5aee2377 100644 --- a/Src/Fido2/TypedString.cs +++ b/Src/Fido2/TypedString.cs @@ -1,9 +1,9 @@ -using Newtonsoft.Json; -using System; +using System; +using System.Text.Json.Serialization; namespace Fido2NetLib { - [JsonConverter(typeof(ToStringJsonConverter))] + [JsonConverter(typeof(ToStringJsonConverter))] public class TypedString : IEquatable { [JsonConstructor] @@ -12,7 +12,7 @@ protected TypedString(string value) Value = value; } - public string Value { get; private set; } + public string Value { get; } public static implicit operator string(TypedString op) { return op.Value; } @@ -34,10 +34,7 @@ public bool Equals(TypedString? other) if (GetType() != other.GetType()) return false; - if (Value == other.Value) - return true; - - return false; + return string.Equals(Value, other.Value, StringComparison.Ordinal); } public override bool Equals(object? obj) @@ -61,7 +58,6 @@ public override bool Equals(object? obj) public override int GetHashCode() { return Value.GetHashCode(); - //throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here..."); } } } diff --git a/Test/Attestation/Apple.cs b/Test/Attestation/Apple.cs index b1a27afa..a8d46a0b 100644 --- a/Test/Attestation/Apple.cs +++ b/Test/Attestation/Apple.cs @@ -10,6 +10,7 @@ using Xunit; using System.Threading.Tasks; using System.Text; +using System.Text.Json; namespace Test.Attestation { @@ -149,11 +150,11 @@ public void TestApplePublicKeyMismatch() _attestationObject.Set("authData", authData); var clientData = new { - Type = "webauthn.create", - Challenge = _challenge, - Origin = "6cc3c9e7967a.ngrok.io", + type = "webauthn.create", + challenge = _challenge, + origin = "6cc3c9e7967a.ngrok.io", }; - var clientDataJson = Encoding.UTF8.GetBytes(Newtonsoft.Json.JsonConvert.SerializeObject(clientData)); + var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(clientData); var attestationResponse = new AuthenticatorAttestationRawResponse { diff --git a/Test/AuthenticatorResponse.cs b/Test/AuthenticatorResponse.cs index 41b75d07..b8c675d0 100644 --- a/Test/AuthenticatorResponse.cs +++ b/Test/AuthenticatorResponse.cs @@ -52,15 +52,14 @@ public async Task TestAuthenticatorOrigins(string origin, string expectedOrigin) SHA256.HashData(Encoding.UTF8.GetBytes(origin)), AuthenticatorFlags.UP | AuthenticatorFlags.AT, 0, - acd, - null - ).ToByteArray(); + acd + ).ToByteArray(); byte[] clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new { - Type = "webauthn.create", - Challenge = challenge, - Origin = rp + type = "webauthn.create", + challenge = challenge, + origin = rp }); var rawResponse = new AuthenticatorAttestationRawResponse { @@ -153,14 +152,13 @@ public void TestAuthenticatorOriginsFail(string origin, string expectedOrigin) SHA256.HashData(Encoding.UTF8.GetBytes(origin)), AuthenticatorFlags.UP | AuthenticatorFlags.AT, 0, - acd, - null - ).ToByteArray(); + acd + ).ToByteArray(); var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new { - Type = "webauthn.create", - Challenge = challenge, - Origin = rp, + type = "webauthn.create", + challenge = challenge, + origin = rp }); var rawResponse = new AuthenticatorAttestationRawResponse @@ -413,9 +411,9 @@ public void TestAuthenticatorAttestationResponseInvalidRawId(byte[] value) var challenge = RandomGenerator.Default.GenerateBytes(128); var rp = "https://www.passwordless.dev/"; byte[] clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new { - Type = "webauthn.create", - Challenge = challenge, - Origin = rp, + type = "webauthn.create", + challenge = challenge, + origin = rp, }); var rawResponse = new AuthenticatorAttestationRawResponse @@ -478,9 +476,9 @@ public void TestAuthenticatorAttestationResponseInvalidRawType() var challenge = RandomGenerator.Default.GenerateBytes(128); var rp = "https://www.passwordless.dev/"; var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new { - Type = "webauthn.create", - Challenge = challenge, - Origin = rp, + type = "webauthn.create", + challenge = challenge, + origin = rp, }); var rawResponse = new AuthenticatorAttestationRawResponse @@ -546,14 +544,14 @@ public void TestAuthenticatorAttestationResponseRpidMismatch() SHA256.HashData(Encoding.UTF8.GetBytes("passwordless.dev")), AuthenticatorFlags.UV, 0, - null, null - ).ToByteArray(); + ).ToByteArray(); + var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new { - Type = "webauthn.create", - Challenge = challenge, - Origin = rp, + type = "webauthn.create", + challenge = challenge, + origin = rp, }); var rawResponse = new AuthenticatorAttestationRawResponse @@ -619,15 +617,14 @@ public void TestAuthenticatorAttestationResponseNotUserPresent() SHA256.HashData(Encoding.UTF8.GetBytes(rp)), AuthenticatorFlags.UV, 0, - null, null - ).ToByteArray(); + ).ToByteArray(); var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new { - Type = "webauthn.create", - Challenge = challenge, - Origin = rp + type = "webauthn.create", + challenge = challenge, + origin = rp }); var rawResponse = new AuthenticatorAttestationRawResponse @@ -693,14 +690,14 @@ public void TestAuthenticatorAttestationResponseNoAttestedCredentialData() SHA256.HashData(Encoding.UTF8.GetBytes(rp)), AuthenticatorFlags.UP | AuthenticatorFlags.UV, 0, - null, null - ).ToByteArray(); + ).ToByteArray(); + var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new { - Type = "webauthn.create", - Challenge = challenge, - Origin = rp, + type = "webauthn.create", + challenge = challenge, + origin = rp, }); var rawResponse = new AuthenticatorAttestationRawResponse @@ -767,14 +764,14 @@ public void TestAuthenticatorAttestationResponseUnknownAttestationType() SHA256.HashData(Encoding.UTF8.GetBytes(rp)), AuthenticatorFlags.AT | AuthenticatorFlags.UP | AuthenticatorFlags.UV, 0, - acd, - null - ).ToByteArray(); + acd + ).ToByteArray(); + var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new { - Type = "webauthn.create", - Challenge = challenge, - Origin = rp, + type = "webauthn.create", + challenge = challenge, + origin = rp, }); var rawResponse = new AuthenticatorAttestationRawResponse @@ -841,14 +838,13 @@ public void TestAuthenticatorAttestationResponseNotUniqueCredId() SHA256.HashData(Encoding.UTF8.GetBytes(rp)), AuthenticatorFlags.AT | AuthenticatorFlags.UP | AuthenticatorFlags.UV, 0, - acd, - null - ).ToByteArray(); + acd + ).ToByteArray(); var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new { - Type = "webauthn.create", - Challenge = challenge, - Origin = rp, + type = "webauthn.create", + challenge = challenge, + origin = rp, }); var rawResponse = new AuthenticatorAttestationRawResponse diff --git a/Test/Base64UrlTest.cs b/Test/Base64UrlTest.cs index c4726b06..80339478 100644 --- a/Test/Base64UrlTest.cs +++ b/Test/Base64UrlTest.cs @@ -8,26 +8,19 @@ namespace fido2_net_lib.Test { public class Base64UrlTest { - [Theory] [MemberData(nameof(GetData))] public void EncodeAndDecodeResultsAreEqual(byte[] data) { // Act - var encodedBytes = Base64Url.Encode(data); - var decodedBytes = Base64Url.Decode(encodedBytes); - + var encodedString = Base64Url.Encode(data); + var decodedBytes = Base64Url.Decode(encodedString); + // Assert Assert.Equal(data, decodedBytes); - } - [Fact] - public void DecodeThrowsOnNull() - { - Assert.Throws(() => - { - var encodedBytes = Base64Url.Decode(null); - }); + // Ensure this also works with the Utf8 decoder + Assert.Equal(data, Base64Url.DecodeUtf8(Encoding.UTF8.GetBytes(encodedString))); } public static IEnumerable GetData() @@ -35,11 +28,11 @@ public static IEnumerable GetData() return new TestDataGenerator(); } - private class TestDataGenerator : TheoryData { public TestDataGenerator() { + Add(Encoding.UTF8.GetBytes("A")); Add(Encoding.UTF8.GetBytes("This is a string fragment to test Base64Url encoding & decoding.")); Add(Array.Empty()); } diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index f2af4db1..94f30097 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -1,22 +1,22 @@ -using Fido2NetLib.Objects; -using Fido2NetLib; -using Newtonsoft.Json; -using System; +using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Linq; -using Xunit; -using System.Threading.Tasks; using System.Security.Cryptography; -using PeterO.Cbor; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Asn1; +using Fido2NetLib; +using Fido2NetLib.Objects; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using System.Text; using NSec.Cryptography; -using Asn1; -using System.Security.Cryptography.X509Certificates; -using System.Buffers.Binary; +using PeterO.Cbor; +using Xunit; namespace fido2_net_lib.Test { @@ -49,7 +49,7 @@ static Fido2Tests() memCache, provider.GetService>()); - service.Initialize().Wait(); + service.InitializeAsync().Wait(); _metadataService = service; @@ -73,7 +73,7 @@ static Fido2Tests() private T Get(string filename) { - return JsonConvert.DeserializeObject(File.ReadAllText(filename)); + return JsonSerializer.Deserialize(File.ReadAllText(filename)); } public abstract class Attestation @@ -104,13 +104,12 @@ public byte[] _clientDataJson { get { - var clientData = new + return JsonSerializer.SerializeToUtf8Bytes(new { - Type = "webauthn.create", - Challenge = _challenge, - Origin = rp, - }; - return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(clientData)); + type = "webauthn.create", + challenge = _challenge, + origin = rp + }); } } public byte[] _clientDataHash => SHA256.HashData(_clientDataJson); @@ -382,8 +381,8 @@ public void TestStringIsSerializable() { var x2 = new AuthenticatorSelection(); x2.UserVerification = UserVerificationRequirement.Discouraged; - var json = JsonConvert.SerializeObject(x2); - var c3 = JsonConvert.DeserializeObject(json); + var json = JsonSerializer.Serialize(x2); + var c3 = JsonSerializer.Deserialize(json); Assert.Equal(UserVerificationRequirement.Discouraged, c3.UserVerification); @@ -398,17 +397,23 @@ public void TestStringIsSerializable() // testing where string and membername mismatch var y1 = AuthenticatorAttachment.CrossPlatform; - var yjson = JsonConvert.SerializeObject(y1); + var yjson = JsonSerializer.Serialize(y1); Assert.Equal("\"cross-platform\"", yjson); - var y2 = JsonConvert.DeserializeObject(yjson); + var y2 = JsonSerializer.Deserialize(yjson); Assert.Equal(AuthenticatorAttachment.CrossPlatform, y2); // test list of typedstrings - var z1 = new[] { AuthenticatorTransport.Ble, AuthenticatorTransport.Usb, AuthenticatorTransport.Nfc, AuthenticatorTransport.Internal }; - var zjson = JsonConvert.SerializeObject(z1); - var z2 = JsonConvert.DeserializeObject(zjson); + var z1 = new[] { + AuthenticatorTransport.Ble, + AuthenticatorTransport.Usb, + AuthenticatorTransport.Nfc, + AuthenticatorTransport.Internal + }; + + var zjson = JsonSerializer.Serialize(z1); + var z2 = JsonSerializer.Deserialize(zjson); Assert.All(z2, (x) => z1.Contains(x)); Assert.True(z1.SequenceEqual(z2)); @@ -422,20 +427,20 @@ public async Task TestFido2AssertionAsync() //var key2 = "45-43-53-31-20-00-00-00-1D-60-44-D7-92-A0-0C-1E-3B-F9-58-5A-28-43-92-FD-F6-4F-BB-7F-8E-86-33-38-30-A4-30-5D-4E-2C-71-E3-53-3C-7B-98-81-99-FE-A9-DA-D9-24-8E-04-BD-C7-86-40-D3-03-1E-6E-00-81-7D-85-C3-A2-19-C9-21-85-8D"; //var key2 = "45-43-53-31-20-00-00-00-A9-E9-12-2A-37-8A-F0-74-E7-BA-52-54-B0-91-55-46-DB-21-E5-2C-01-B8-FB-69-CD-E5-ED-02-B6-C3-16-E3-1A-59-16-C1-43-87-0D-04-B9-94-7F-CF-56-E5-AA-5E-96-8C-5B-27-8F-83-F4-E2-50-AB-B3-F6-28-A1-F8-9E"; - var options = JsonConvert.DeserializeObject(File.ReadAllText("./attestationNoneOptions.json")); - var response = JsonConvert.DeserializeObject(File.ReadAllText("./attestationNoneResponse.json")); + var options = JsonSerializer.Deserialize(File.ReadAllText("./attestationNoneOptions.json")); + var response = JsonSerializer.Deserialize(File.ReadAllText("./attestationNoneResponse.json")); var o = AuthenticatorAttestationResponse.Parse(response); await o.VerifyAsync(options, _config, (x) => Task.FromResult(true), _metadataService, null); var credId = "F1-3C-7F-08-3C-A2-29-E0-B4-03-E8-87-34-6E-FC-7F-98-53-10-3A-30-91-75-67-39-7A-D1-D8-AF-87-04-61-87-EF-95-31-85-60-F3-5A-1A-2A-CF-7D-B0-1D-06-B9-69-F9-AB-F4-EC-F3-07-3E-CF-0F-71-E8-84-E8-41-20"; var allowedCreds = new List() { - new PublicKeyCredentialDescriptor() - { - Id = Convert.FromHexString(credId.Replace("-", "")), - Type = PublicKeyCredentialType.PublicKey - } - }; + new PublicKeyCredentialDescriptor() + { + Id = Convert.FromHexString(credId.Replace("-", "")), + Type = PublicKeyCredentialType.PublicKey + } + }; // assertion @@ -449,8 +454,8 @@ public async Task TestFido2AssertionAsync() [Fact] public async Task TestParsingAsync() { - var jsonPost = JsonConvert.DeserializeObject(File.ReadAllText("./json1.json")); - var options = JsonConvert.DeserializeObject(File.ReadAllText("./options1.json")); + var jsonPost = JsonSerializer.Deserialize(File.ReadAllText("./json1.json")); + var options = JsonSerializer.Deserialize(File.ReadAllText("./options1.json")); Assert.NotNull(jsonPost); @@ -488,9 +493,9 @@ public void MetadataBLOBPayloadEntry_Can_Be_JSON_Roundtripped() input.MetadataStatement.UserVerificationDetails = Array.Empty(); input.MetadataStatement.AttestationRootCertificates = new string[] { "..." }; - var json = JsonConvert.SerializeObject(input); + var json = JsonSerializer.Serialize(input); - var output = JsonConvert.DeserializeObject(json); + var output = JsonSerializer.Deserialize(json); Assert.Equal(input.AaGuid, output.AaGuid); @@ -509,8 +514,8 @@ public void TestAuthenticatorDataPa2rsing() [Fact] public async Task TestU2FAttestationAsync() { - var jsonPost = JsonConvert.DeserializeObject(File.ReadAllText("./attestationResultsU2F.json")); - var options = JsonConvert.DeserializeObject(File.ReadAllText("./attestationOptionsU2F.json")); + var jsonPost = JsonSerializer.Deserialize(File.ReadAllText("./attestationResultsU2F.json")); + var options = JsonSerializer.Deserialize(File.ReadAllText("./attestationOptionsU2F.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); await o.VerifyAsync(options, _config, (x) => Task.FromResult(true), _metadataService, null); byte[] ad = o.AttestationObject.AuthData; @@ -519,8 +524,8 @@ public async Task TestU2FAttestationAsync() [Fact] public async Task TestPackedAttestationAsync() { - var jsonPost = JsonConvert.DeserializeObject(File.ReadAllText("./attestationResultsPacked.json")); - var options = JsonConvert.DeserializeObject(File.ReadAllText("./attestationOptionsPacked.json")); + var jsonPost = JsonSerializer.Deserialize(File.ReadAllText("./attestationResultsPacked.json")); + var options = JsonSerializer.Deserialize(File.ReadAllText("./attestationOptionsPacked.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); await o.VerifyAsync(options, _config, (x) => Task.FromResult(true), _metadataService, null); byte[] ad = o.AttestationObject.AuthData; @@ -533,8 +538,8 @@ public async Task TestPackedAttestationAsync() [Fact] public async Task TestNoneAttestationAsync() { - var jsonPost = JsonConvert.DeserializeObject(File.ReadAllText("./attestationResultsNone.json")); - var options = JsonConvert.DeserializeObject(File.ReadAllText("./attestationOptionsNone.json")); + var jsonPost = JsonSerializer.Deserialize(File.ReadAllText("./attestationResultsNone.json")); + var options = JsonSerializer.Deserialize(File.ReadAllText("./attestationOptionsNone.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); await o.VerifyAsync(options, _config, (x) => Task.FromResult(true), _metadataService, null); @@ -542,8 +547,8 @@ public async Task TestNoneAttestationAsync() [Fact] public async Task TestTPMSHA256AttestationAsync() { - var jsonPost = JsonConvert.DeserializeObject(File.ReadAllText("./attestationTPMSHA256Response.json")); - var options = JsonConvert.DeserializeObject(File.ReadAllText("./attestationTPMSHA256Options.json")); + var jsonPost = JsonSerializer.Deserialize(File.ReadAllText("./attestationTPMSHA256Response.json")); + var options = JsonSerializer.Deserialize(File.ReadAllText("./attestationTPMSHA256Options.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); await o.VerifyAsync(options, _config, (x) => Task.FromResult(true), _metadataService, null); byte[] ad = o.AttestationObject.AuthData; @@ -552,8 +557,8 @@ public async Task TestTPMSHA256AttestationAsync() [Fact] public async Task TestTPMSHA1AttestationAsync() { - var jsonPost = JsonConvert.DeserializeObject(File.ReadAllText("./attestationTPMSHA1Response.json")); - var options = JsonConvert.DeserializeObject(File.ReadAllText("./attestationTPMSHA1Options.json")); + var jsonPost = JsonSerializer.Deserialize(File.ReadAllText("./attestationTPMSHA1Response.json")); + var options = JsonSerializer.Deserialize(File.ReadAllText("./attestationTPMSHA1Options.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); await o.VerifyAsync(options, _config, (x) => Task.FromResult(true), _metadataService, null); byte[] ad = o.AttestationObject.AuthData; @@ -562,8 +567,8 @@ public async Task TestTPMSHA1AttestationAsync() [Fact] public async Task TestAndroidKeyAttestationAsync() { - var jsonPost = JsonConvert.DeserializeObject(File.ReadAllText("./attestationAndroidKeyResponse.json")); - var options = JsonConvert.DeserializeObject(File.ReadAllText("./attestationAndroidKeyOptions.json")); + var jsonPost = JsonSerializer.Deserialize(File.ReadAllText("./attestationAndroidKeyResponse.json")); + var options = JsonSerializer.Deserialize(File.ReadAllText("./attestationAndroidKeyOptions.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); await o.VerifyAsync(options, _config, (x) => Task.FromResult(true), _metadataService, null); byte[] ad = o.AttestationObject.AuthData; @@ -573,8 +578,8 @@ public async Task TestAndroidKeyAttestationAsync() [Fact(Skip = "Need to determine how best to validate expired certificates")] public async Task TestAppleAttestationAsync() { - var jsonPost = JsonConvert.DeserializeObject(File.ReadAllText("./attestationAppleResponse.json")); - var options = JsonConvert.DeserializeObject(File.ReadAllText("./attestationAppleOptions.json")); + var jsonPost = JsonSerializer.Deserialize(File.ReadAllText("./attestationAppleResponse.json")); + var options = JsonSerializer.Deserialize(File.ReadAllText("./attestationAppleOptions.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); var config = new Fido2Configuration { Origin = "https://6cc3c9e7967a.ngrok.io/" }; await o.VerifyAsync(options, config, (x) => Task.FromResult(true), _metadataService, null); @@ -585,8 +590,8 @@ public async Task TestAppleAttestationAsync() [Fact] public async Task TaskPackedAttestation512() { - var jsonPost = JsonConvert.DeserializeObject(File.ReadAllText("./attestationResultsPacked512.json")); - var options = JsonConvert.DeserializeObject(File.ReadAllText("./attestationOptionsPacked512.json")); + var jsonPost = JsonSerializer.Deserialize(File.ReadAllText("./attestationResultsPacked512.json")); + var options = JsonSerializer.Deserialize(File.ReadAllText("./attestationOptionsPacked512.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); await o.VerifyAsync(options, _config, (x) => Task.FromResult(true), _metadataService, null); byte[] ad = o.AttestationObject.AuthData; @@ -596,8 +601,8 @@ public async Task TaskPackedAttestation512() [Fact] public async Task TestTrustKeyAttestationAsync() { - var jsonPost = JsonConvert.DeserializeObject(File.ReadAllText("./attestationResultTrustKeyT110.json")); - var options = JsonConvert.DeserializeObject(File.ReadAllText("./attestationOptionsTrustKeyT110.json")); + var jsonPost = JsonSerializer.Deserialize(File.ReadAllText("./attestationResultTrustKeyT110.json")); + var options = JsonSerializer.Deserialize(File.ReadAllText("./attestationOptionsTrustKeyT110.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); await o.VerifyAsync(options, _config, (x) => Task.FromResult(true), _metadataService, null); byte[] ad = o.AttestationObject.AuthData; @@ -611,8 +616,8 @@ public async Task TestTrustKeyAttestationAsync() [Fact] public async Task TestInvalidU2FAttestationASync() { - var jsonPost = JsonConvert.DeserializeObject(File.ReadAllText("./attestationResultsATKey.json")); - var options = JsonConvert.DeserializeObject(File.ReadAllText("./attestationOptionsATKey.json")); + var jsonPost = JsonSerializer.Deserialize(File.ReadAllText("./attestationResultsATKey.json")); + var options = JsonSerializer.Deserialize(File.ReadAllText("./attestationOptionsATKey.json")); var o = AuthenticatorAttestationResponse.Parse(jsonPost); await o.VerifyAsync(options, _config, (x) => Task.FromResult(true), _metadataService, null); byte[] ad = o.AttestationObject.AuthData; @@ -722,8 +727,6 @@ internal static byte[] EcDsaSigFromSig(byte[] sig, int keySize) return ecdsasig.Encode(); } - - [Fact] public void TestAssertionResponse() { @@ -868,11 +871,11 @@ internal static async Task MakeAssertionResponse(CO var clientData = new { - Type = "webauthn.get", - Challenge = challenge, - Origin = rp, + type = "webauthn.get", + challenge = challenge, + origin = rp, }; - var clientDataJson = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(clientData)); + var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(clientData); var hashedClientDataJson = SHA256.HashData(clientDataJson); byte[] data = new byte[authData.Length + hashedClientDataJson.Length]; diff --git a/Test/MetadataServiceTests.cs b/Test/MetadataServiceTests.cs index cd620381..9294f15e 100644 --- a/Test/MetadataServiceTests.cs +++ b/Test/MetadataServiceTests.cs @@ -11,20 +11,18 @@ namespace Test { public class MetadataServiceTests { - [Fact] public async Task ConformanceTestClient() { var client = new ConformanceMetadataRepository(null, "http://localhost/"); var blob = await client.GetBLOBAsync(); - + Assert.True(blob.Entries.Length > 0); - var entry_1 = await client.GetMetadataStatement(blob, blob.Entries[blob.Entries.Length - 1]); + var entry_1 = await client.GetMetadataStatementAsync(blob, blob.Entries[^1]); Assert.NotNull(entry_1.Description); - } [Fact] @@ -50,7 +48,7 @@ public async Task DistributedCacheMetadataService_Works() memCache, provider.GetService>()); - await service.Initialize(); + await service.InitializeAsync(); var entry = service.GetEntry(Guid.Parse("6d44ba9b-f6ec-2e49-b930-0c8fe920cb73")); diff --git a/Test/PubKeyCredParamTests.cs b/Test/PubKeyCredParamTests.cs index 07c87c11..9da1b214 100644 --- a/Test/PubKeyCredParamTests.cs +++ b/Test/PubKeyCredParamTests.cs @@ -1,7 +1,7 @@ -using Fido2NetLib; -using Fido2NetLib.Objects; +using System.Text.Json; -using Newtonsoft.Json; +using Fido2NetLib; +using Fido2NetLib.Objects; using Xunit; @@ -14,7 +14,7 @@ public void CanDeserializeES256() { string json = @"{""type"":""public-key"",""alg"":-7}"; - var model = JsonConvert.DeserializeObject(json); + var model = JsonSerializer.Deserialize(json); Assert.Equal(PublicKeyCredentialType.PublicKey, model.Type); Assert.Equal(COSE.Algorithm.ES256, model.Alg); @@ -25,7 +25,7 @@ public void CanDeserializeES256K() { string json = @"{""type"":""public-key"",""alg"":-47}"; - var model = JsonConvert.DeserializeObject(json); + var model = JsonSerializer.Deserialize(json); Assert.Equal(PublicKeyCredentialType.PublicKey, model.Type); Assert.Equal(COSE.Algorithm.ES256K, model.Alg); diff --git a/Test/TestFiles/attestationNoneResponse.json b/Test/TestFiles/attestationNoneResponse.json index b971ec81..7d349fb2 100644 --- a/Test/TestFiles/attestationNoneResponse.json +++ b/Test/TestFiles/attestationNoneResponse.json @@ -3,7 +3,7 @@ "rawId": "8Tx_CDyiKeC0A-iHNG78f5hTEDowkXVnOXrR2K-HBGGH75UxhWDzWhoqz32wHQa5afmr9OzzBz7PD3HohOhBIA", "type": "public-key", "response": { - "AttestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQPE8fwg8oingtAPohzRu_H-YUxA6MJF1Zzl60divhwRhh--VMYVg81oaKs99sB0GuWn5q_Ts8wc-zw9x6IToQSClAQIDJiABIVggHWBE15KgDB47-VhaKEOS_fZPu3-OhjM4MKQwXU4sceMiWCBTPHuYgZn-qdrZJI4EvceGQNMDHm4AgX2Fw6IZySGFjQ", - "clientDataJson": "eyJjaGFsbGVuZ2UiOiJhbkNDSXhGUnlMRXlJZ29ISHBrSm94VWp4MmF4QTlzV2JSdzNUYjd5V05mVGZ3NGQyZFBtd096bXBRODRSbFhCM1JSTlpqRGR4dE1EOHhZNDNSVGhNdyIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0OjQ0MzI5IiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9" + "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQPE8fwg8oingtAPohzRu_H-YUxA6MJF1Zzl60divhwRhh--VMYVg81oaKs99sB0GuWn5q_Ts8wc-zw9x6IToQSClAQIDJiABIVggHWBE15KgDB47-VhaKEOS_fZPu3-OhjM4MKQwXU4sceMiWCBTPHuYgZn-qdrZJI4EvceGQNMDHm4AgX2Fw6IZySGFjQ", + "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJhbkNDSXhGUnlMRXlJZ29ISHBrSm94VWp4MmF4QTlzV2JSdzNUYjd5V05mVGZ3NGQyZFBtd096bXBRODRSbFhCM1JSTlpqRGR4dE1EOHhZNDNSVGhNdyIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0OjQ0MzI5IiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9" } } \ No newline at end of file