| File: Passkeys\AuthenticatorData.cs | Web Access |
| Project: src\aspnetcore\src\Identity\Core\src\Microsoft.AspNetCore.Identity.csproj (Microsoft.AspNetCore.Identity) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers.Binary; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Formats.Cbor; namespace Microsoft.AspNetCore.Identity; /// <summary> /// Encodes contextual bindings made by an authenticator. /// </summary> /// <remarks> /// See <see href="https://www.w3.org/TR/webauthn-3/#authenticator-data"/> /// </remarks> internal sealed class AuthenticatorData { /// <summary> /// Gets or sets the SHA-256 hash of the Relying Party ID the credential is scoped to. /// </summary> public required ReadOnlyMemory<byte> RpIdHash { get; init; } /// <summary> /// Gets or sets the flags for this authenticator data. /// </summary> public required AuthenticatorDataFlags Flags { get; init; } /// <summary> /// Gets or sets the signature counter. /// </summary> public required uint SignCount { get; init; } /// <summary> /// Gets or sets the attested credential data. /// </summary> public AttestedCredentialData? AttestedCredentialData { get; init; } /// <summary> /// Gets or sets the extension-defined authenticator data. /// </summary> public ReadOnlyMemory<byte>? Extensions { get; init; } /// <summary> /// Gets whether the user is present. /// </summary> public bool IsUserPresent => Flags.HasFlag(AuthenticatorDataFlags.UserPresent); /// <summary> /// Gets whether the user is verified. /// </summary> public bool IsUserVerified => Flags.HasFlag(AuthenticatorDataFlags.UserVerified); /// <summary> /// Gets whether the public key credential source is backup eligible. /// </summary> public bool IsBackupEligible => Flags.HasFlag(AuthenticatorDataFlags.BackupEligible); /// <summary> /// Gets whether the public key credential source is currently backed up. /// </summary> public bool IsBackedUp => Flags.HasFlag(AuthenticatorDataFlags.BackedUp); /// <summary> /// Gets whether the authenticator data has extensions. /// </summary> public bool HasExtensionsData => Flags.HasFlag(AuthenticatorDataFlags.HasExtensionData); /// <summary> /// Gets whether the authenticator added attested credential data. /// </summary> [MemberNotNullWhen(true, nameof(AttestedCredentialData))] public bool HasAttestedCredentialData => Flags.HasFlag(AuthenticatorDataFlags.HasAttestedCredentialData); public static AuthenticatorData Parse(ReadOnlyMemory<byte> bytes) { try { return ParseCore(bytes); } catch (PasskeyException) { throw; } catch (Exception ex) { throw PasskeyException.InvalidAuthenticatorDataFormat(ex); } } private static AuthenticatorData ParseCore(ReadOnlyMemory<byte> bytes) { const int RpIdHashLength = 32; const int AuthenticatorDataFlagsLength = 1; const int SignCountLength = 4; const int MinLength = RpIdHashLength + AuthenticatorDataFlagsLength + SignCountLength; // Min length specified in https://www.w3.org/TR/webauthn-3/#authenticator-data Debug.Assert(MinLength == 37); if (bytes.Length < MinLength) { throw PasskeyException.InvalidAuthenticatorDataLength(bytes.Length); } var offset = 0; var rpIdHash = bytes.Slice(offset, RpIdHashLength); offset += RpIdHashLength; var flags = (AuthenticatorDataFlags)bytes.Span[offset]; offset += AuthenticatorDataFlagsLength; var signCount = BinaryPrimitives.ReadUInt32BigEndian(bytes.Slice(offset, SignCountLength).Span); offset += SignCountLength; AttestedCredentialData? attestedCredentialData = null; if (flags.HasFlag(AuthenticatorDataFlags.HasAttestedCredentialData)) { var remaining = bytes[offset..]; attestedCredentialData = AttestedCredentialData.Parse(remaining, out var bytesRead); offset += bytesRead; } ReadOnlyMemory<byte>? extensions = default; if (flags.HasFlag(AuthenticatorDataFlags.HasExtensionData)) { var reader = new CborReader(bytes[offset..]); extensions = reader.ReadEncodedValue(); offset += extensions.Value.Length; } if (offset != bytes.Length) { // Leftover bytes signifies a possible parsing error. throw PasskeyException.InvalidAuthenticatorDataFormat(); } return new() { RpIdHash = rpIdHash, Flags = flags, SignCount = signCount, AttestedCredentialData = attestedCredentialData, Extensions = extensions, }; } }