Expose authenticator AAGUID as Guid instead of raw bytes (#43)

* Expose authenticator AAGUID as Guid instead of raw bytes

The Win32 WebAuthNGetAuthenticatorList API returns the authenticator
identifier as a big-endian encoded GUID, which is in fact the
Authenticator Attestation GUID (AAGUID). Decode it into a Guid in the
wrapper instead of surfacing the raw bytes (previously Base64Url-encoded
in the UI and PowerShell output).

- Rename AuthenticatorDetails.AuthenticatorId (byte[]) to AaGuid (Guid)
  and decode the big-endian bytes in ApiHelper.Translate.
- Display the value as a GUID in the PasskeyUI authenticator list,
  renaming the column to 'AAGUID'.
- Update the Get-PasskeyAuthenticator format views accordingly.
- Update API docs and CHANGELOG.

* Remove unused EndianBitConverter helper

EndianBitConverter was dead code: all of its members (ToUInt32BigEndian,
ToUInt16BigEndian, ToGuidBigEndian, SwapBytes) were only referenced
within the file itself. Big-endian GUID decoding now goes through the
GuidPolyfill.Create helper used elsewhere in the codebase.

* Make AuthenticatorDetails.AaGuid nullable

Return null instead of Guid.Empty when the Win32 API does not provide a
valid 16-byte authenticator identifier, so the absence of an AAGUID is
represented distinctly rather than as an all-zero GUID.

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Grafnetter
2026-06-04 21:58:45 +02:00
committed by GitHub
co-authored by Claude
parent 2265792276
commit 3a05986c82
7 changed files with 29 additions and 62 deletions
@@ -29,17 +29,17 @@ Corresponds to WEBAUTHN_AUTHENTICATOR_DETAILS.
## Properties
### <a id="DSInternals_Win32_WebAuthn_AuthenticatorDetails_AuthenticatorId"></a> AuthenticatorId
### <a id="DSInternals_Win32_WebAuthn_AuthenticatorDetails_AaGuid"></a> AaGuid
The authenticator Id.
The Authenticator Attestation GUID (AAGUID) identifying the model of the authenticator.
```csharp
public byte[]? AuthenticatorId { get; set; }
public Guid? AaGuid { get; set; }
```
#### Property Value
[byte](https://learn.microsoft.com/dotnet/api/system.byte)\[\]?
[Guid](https://learn.microsoft.com/dotnet/api/system.guid)?
### <a id="DSInternals_Win32_WebAuthn_AuthenticatorDetails_AuthenticatorLogo"></a> AuthenticatorLogo
+1
View File
@@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file. The format
- `WebAuthnApi.AuthenticatorGetAssertion` now prefers the native `pbAuthenticationResponseJSON` returned in `WEBAUTHN_ASSERTION_VERSION_6` when present, deserializing the full credential (including `clientExtensionResults`) verbatim from the OS-produced JSON. The legacy field-by-field assembly and per-extension recomputation remain as a fallback for older OS versions.
- `WebAuthnApi.AuthenticatorMakeCredential` now prefers the native `pbRegistrationResponseJSON` returned in `WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_8` when present, deserializing the full credential verbatim from the OS-produced JSON. The legacy field-by-field assembly and per-extension recomputation remain as a fallback for older OS versions.
- The `AuthenticatorDetails.AuthenticatorId` property (returned by `Get-PasskeyAuthenticator`) was renamed to `AaGuid` and its type changed from `byte[]` to `Guid?`. The binary identifier returned by the Win32 API is a big-endian encoded Authenticator Attestation GUID (AAGUID), so it is now decoded and surfaced as a `Guid` (or `null` when absent) instead of a Base64Url-encoded string.
## [3.1.0] - 2026-05-14
@@ -12,8 +12,8 @@
<ListEntry>
<ListItems>
<ListItem>
<Label>AuthenticatorId</Label>
<ScriptBlock>[System.Buffers.Text.Base64Url]::EncodeToString($PSItem.AuthenticatorId)</ScriptBlock>
<Label>AAGUID</Label>
<PropertyName>AaGuid</PropertyName>
</ListItem>
<ListItem>
<PropertyName>AuthenticatorName</PropertyName>
@@ -36,8 +36,8 @@
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>AuthenticatorId</Label>
<Width>45</Width>
<Label>AAGUID</Label>
<Width>38</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>AuthenticatorName</Label>
@@ -51,7 +51,7 @@
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<ScriptBlock>[System.Buffers.Text.Base64Url]::EncodeToString($PSItem.AuthenticatorId)</ScriptBlock>
<PropertyName>AaGuid</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>AuthenticatorName</PropertyName>
@@ -1,4 +1,6 @@
namespace DSInternals.Win32.WebAuthn;
using System;
namespace DSInternals.Win32.WebAuthn;
/// <summary>
/// Information about an authenticator.
@@ -7,9 +9,9 @@
public sealed class AuthenticatorDetails
{
/// <summary>
/// The authenticator Id.
/// The Authenticator Attestation GUID (AAGUID) identifying the model of the authenticator.
/// </summary>
public byte[]? AuthenticatorId { get; set; }
public Guid? AaGuid { get; set; }
/// <summary>
/// The authenticator name.
@@ -253,7 +253,7 @@ namespace DSInternals.Win32.WebAuthn.Interop
{
result.Add(new AuthenticatorDetails
{
AuthenticatorId = authenticator.AuthenticatorId,
AaGuid = DecodeAaGuid(authenticator.AuthenticatorId),
AuthenticatorName = authenticator.AuthenticatorName,
AuthenticatorLogo = DecodeBinaryLogo(authenticator.AuthenticatorLogo),
Locked = authenticator.Locked
@@ -263,6 +263,17 @@ namespace DSInternals.Win32.WebAuthn.Interop
return result;
}
/// <summary>
/// Decodes the authenticator identifier returned by the Win32 API, which is the AAGUID encoded as a big-endian GUID.
/// </summary>
private static Guid? DecodeAaGuid(byte[]? authenticatorId)
{
// The AAGUID is always a 16-byte big-endian encoded GUID.
return authenticatorId is { Length: 16 }
? Guid.Create(authenticatorId, bigEndian: true)
: null;
}
/// <summary>
/// Converts a byte array logo (expected UTF-8 SVG) to a string.
/// </summary>
@@ -1,47 +0,0 @@
using System;
namespace DSInternals.Win32.WebAuthn.Interop
{
internal static class EndianBitConverter
{
public static uint ToUInt32BigEndian(this byte[] bytes, int startIndex = 0)
{
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return BitConverter.ToUInt32(bytes, startIndex);
}
public static ushort ToUInt16BigEndian(this byte[] bytes, int startIndex = 0)
{
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return BitConverter.ToUInt16(bytes, startIndex);
}
public static Guid ToGuidBigEndian(this byte[] bytes)
{
if (BitConverter.IsLittleEndian)
{
bytes.SwapBytes(0, 3);
bytes.SwapBytes(1, 2);
bytes.SwapBytes(4, 5);
bytes.SwapBytes(6, 7);
}
return new Guid(bytes);
}
public static void SwapBytes(this byte[] bytes, int index1, int index2)
{
var temp = bytes[index1];
bytes[index1] = bytes[index2];
bytes[index2] = temp;
}
}
}
@@ -56,10 +56,10 @@
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Authenticator ID" Width="280" MinWidth="80" SortMemberPath="AuthenticatorId">
<DataGridTemplateColumn Header="AAGUID" Width="280" MinWidth="80" SortMemberPath="AaGuid">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:ClipboardEnabledTextBlock Text="{Binding AuthenticatorId, Converter={StaticResource ByteArrayToBase64UrlConverter}}" />
<local:ClipboardEnabledTextBlock Text="{Binding AaGuid}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>