File: Passkeys\CredentialHelpers.cs
Web Access
Project: src\src\Identity\test\Identity.Test\Microsoft.AspNetCore.Identity.Test.csproj (Microsoft.AspNetCore.Identity.Test)
// 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.Formats.Cbor;
 
namespace Microsoft.AspNetCore.Identity.Test;
 
internal static class CredentialHelpers
{
    public static ReadOnlyMemory<byte> MakeAttestedCredentialData(in AttestedCredentialDataArgs args)
    {
        const int AaguidLength = 16;
        const int CredentialIdLengthLength = 2;
        var length = AaguidLength + CredentialIdLengthLength + args.CredentialId.Length + args.CredentialPublicKey.Length;
        var result = new byte[length];
        var offset = 0;
 
        args.Aaguid.Span.CopyTo(result.AsSpan(offset, AaguidLength));
        offset += AaguidLength;
 
        BinaryPrimitives.WriteUInt16BigEndian(result.AsSpan(offset, CredentialIdLengthLength), (ushort)args.CredentialId.Length);
        offset += CredentialIdLengthLength;
 
        args.CredentialId.Span.CopyTo(result.AsSpan(offset));
        offset += args.CredentialId.Length;
 
        args.CredentialPublicKey.Span.CopyTo(result.AsSpan(offset));
        offset += args.CredentialPublicKey.Length;
 
        if (offset != result.Length)
        {
            throw new InvalidOperationException($"Expected attested credential data length '{length}', but got '{offset}'.");
        }
 
        return result;
    }
 
    public static ReadOnlyMemory<byte> MakeAuthenticatorData(in AuthenticatorDataArgs args)
    {
        const int RpIdHashLength = 32;
        const int AuthenticatorDataFlagsLength = 1;
        const int SignCountLength = 4;
        var length =
            RpIdHashLength +
            AuthenticatorDataFlagsLength +
            SignCountLength +
            (args.AttestedCredentialData?.Length ?? 0) +
            (args.Extensions?.Length ?? 0);
        var result = new byte[length];
        var offset = 0;
 
        args.RpIdHash.Span.CopyTo(result.AsSpan(offset, RpIdHashLength));
        offset += RpIdHashLength;
 
        result[offset] = (byte)args.Flags;
        offset += AuthenticatorDataFlagsLength;
 
        BinaryPrimitives.WriteUInt32BigEndian(result.AsSpan(offset, SignCountLength), args.SignCount);
        offset += SignCountLength;
 
        if (args.AttestedCredentialData is { } attestedCredentialData)
        {
            attestedCredentialData.Span.CopyTo(result.AsSpan(offset));
            offset += attestedCredentialData.Length;
        }
 
        if (args.Extensions is { } extensions)
        {
            extensions.Span.CopyTo(result.AsSpan(offset));
            offset += extensions.Length;
        }
 
        if (offset != result.Length)
        {
            throw new InvalidOperationException($"Expected authenticator data length '{length}', but got '{offset}'.");
        }
 
        return result;
    }
 
    public static ReadOnlyMemory<byte> MakeAttestationObject(in AttestationObjectArgs args)
    {
        var writer = new CborWriter(CborConformanceMode.Ctap2Canonical);
        writer.WriteStartMap(args.CborMapLength);
        if (args.Format is { } format)
        {
            writer.WriteTextString("fmt");
            writer.WriteTextString(format);
        }
        if (args.AttestationStatement is { } attestationStatement)
        {
            writer.WriteTextString("attStmt");
            writer.WriteEncodedValue(attestationStatement.Span);
        }
        if (args.AuthenticatorData is { } authenticatorData)
        {
            writer.WriteTextString("authData");
            writer.WriteByteString(authenticatorData.Span);
        }
        writer.WriteEndMap();
        return writer.Encode();
    }
}