File: Passkeys\Ctap2CborReader.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.Formats.Cbor;

namespace Microsoft.AspNetCore.Identity;

/// <summary>
/// A variation of <see cref="CborReader"/> that is used to read COSE keys in a CTAP2 canonical CBOR encoding form.
/// </summary>
internal sealed class Ctap2CborReader : CborReader
{
    private int _remainingKeys;
    private int? _lastReadLabel;

    public static Ctap2CborReader Create(ReadOnlyMemory<byte> data)
    {
        var reader = new Ctap2CborReader(data);
        if (reader.ReadStartMap() is not { } keyCount)
        {
            throw new CborContentException("CTAP2 canonical CBOR encoding form requires there to be a definite number of keys.");
        }
        reader._remainingKeys = keyCount;
        return reader;
    }

    private Ctap2CborReader(ReadOnlyMemory<byte> data)
        : base(data, CborConformanceMode.Ctap2Canonical)
    {
    }

    public bool TryReadCoseKeyLabel(int expectedLabel)
    {
        // The 'expectedLabel' parameter can hold a label that
        // was read when handling a previous optional field.
        // We only need to read the next label if uninhabited.
        if (_lastReadLabel is null)
        {
            // Check that we have not reached the end of the COSE key object.
            if (_remainingKeys == 0)
            {
                return false;
            }

            _lastReadLabel = ReadInt32();
        }

        if (expectedLabel != _lastReadLabel.Value)
        {
            return false;
        }

        // Read was successful - vacate '_lastReadLabel' to advance reads.
        _lastReadLabel = null;
        _remainingKeys--;
        return true;
    }

    public void ReadCoseKeyLabel(int expectedLabel)
    {
        if (!TryReadCoseKeyLabel(expectedLabel))
        {
            throw new CborContentException("Unexpected COSE key label");
        }
    }
}