File: Passkeys\AttestationObject.cs
Web Access
Project: src\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.Formats.Cbor;
 
namespace Microsoft.AspNetCore.Identity;
 
/// <summary>
/// Represents an authenticator attestation object, which contains the attestation statement and authenticator data.
/// </summary>
/// <remarks>
/// See <see href="https://www.w3.org/TR/webauthn-3/#attestation-object"/>.
/// </remarks>
internal sealed class AttestationObject
{
    /// <summary>
    /// Gets or sets the attestation statement format.
    /// </summary>
    /// <remarks>
    /// See <see href="https://www.w3.org/TR/webauthn-3/#attestation-statement-format"/>.
    /// </remarks>
    public required string Format { get; init; }
 
    /// <summary>
    /// Gets or sets the attestation statement.
    /// </summary>
    /// <remarks>
    /// See <see href="https://www.w3.org/TR/webauthn-3/#attestation-statement"/>.
    /// </remarks>
    public required ReadOnlyMemory<byte> AttestationStatement { get; init; }
 
    /// <summary>
    /// Gets or sets the authenticator data.
    /// </summary>
    /// <remarks>
    /// See <see href="https://www.w3.org/TR/webauthn-3/#authenticator-data"/>.
    /// </remarks>
    public required ReadOnlyMemory<byte> AuthenticatorData { get; init; }
 
    public static AttestationObject Parse(ReadOnlyMemory<byte> data)
    {
        try
        {
            return ParseCore(data);
        }
        catch (PasskeyException)
        {
            throw;
        }
        catch (CborContentException ex)
        {
            throw PasskeyException.InvalidAttestationObjectFormat(ex);
        }
        catch (InvalidOperationException ex)
        {
            throw PasskeyException.InvalidAttestationObjectFormat(ex);
        }
        catch (Exception ex)
        {
            throw PasskeyException.InvalidAttestationObject(ex);
        }
    }
 
    private static AttestationObject ParseCore(ReadOnlyMemory<byte> data)
    {
        var reader = new CborReader(data);
        _ = reader.ReadStartMap();
 
        string? format = null;
        ReadOnlyMemory<byte>? attestationStatement = default;
        ReadOnlyMemory<byte>? authenticatorData = default;
 
        while (reader.PeekState() != CborReaderState.EndMap)
        {
            var key = reader.ReadTextString();
            switch (key)
            {
                case "fmt":
                    format = reader.ReadTextString();
                    break;
                case "attStmt":
                    attestationStatement = reader.ReadEncodedValue();
                    break;
                case "authData":
                    authenticatorData = reader.ReadByteString();
                    break;
                default:
                    // Unknown key - skip.
                    reader.SkipValue();
                    break;
            }
        }
 
        if (format is null)
        {
            throw PasskeyException.MissingAttestationStatementFormat();
        }
 
        if (!attestationStatement.HasValue)
        {
            throw PasskeyException.MissingAttestationStatement();
        }
 
        if (!authenticatorData.HasValue)
        {
            throw PasskeyException.MissingAuthenticatorData();
        }
 
        return new()
        {
            Format = format,
            AttestationStatement = attestationStatement.Value,
            AuthenticatorData = authenticatorData.Value
        };
    }
}