Skip to content

Commit 2e96bf2

Browse files
committed
Add CTAP 2.2/2.3 info members to AuthenticatorInfo
This commit adds support for two new CTAP 2.2/2.3 members to the AuthenticatorInfo class, matching the functionality added in the Yubico Java SDK (yubikit-android PR #229): - EncCredStoreState: Encrypted credential store state that platforms can use to detect credential store changes across resets. This is a 32-byte encrypted value (16-byte IV + 16-byte ciphertext). - AuthenticatorConfigCommands: List of authenticator config commands supported by the authenticator. Null indicates no support, while an empty list means the command is supported but no specific commands are available. - GetCredStoreState(): New method to decrypt the credential store state using a persistent PIN/UV authentication token. Uses HKDF-SHA-256 for key derivation and AES-128-CBC for decryption, following the same pattern as the existing GetIdentifier() method. The implementation uses CBOR map keys 0x1E and 0x1F for parsing these fields from authenticatorGetInfo responses.
1 parent 757ff6f commit 2e96bf2

File tree

1 file changed

+64
-0
lines changed

1 file changed

+64
-0
lines changed

Yubico.YubiKey/src/Yubico/YubiKey/Fido2/AuthenticatorInfo.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public class AuthenticatorInfo
5959
private const int KeyPinComplexityPolicy = 0x1B;
6060
private const int KeyPinComplexityPolicyUrl = 0x1C;
6161
private const int KeyMaxPinLength = 0x1D;
62+
private const int KeyEncCredStoreState = 0x1E;
63+
private const int KeyAuthenticatorConfigCommands = 0x1F;
6264

6365
/// <summary>
6466
/// An <see cref="Aaguid"/> is defined in the standard as 16 bytes, no
@@ -366,6 +368,26 @@ public class AuthenticatorInfo
366368
/// </summary>
367369
public IReadOnlyList<string> AttestationFormats { get; } = new List<string>();
368370

371+
/// <summary>
372+
/// If present, an encrypted credential store state that the platform can use to detect credential store changes across resets.
373+
/// The platform must use the persistent UV auth token as input to decrypt the state.
374+
/// If <c>null</c>, the authenticator does not support this feature.
375+
/// The encrypted state is 32 bytes: the first 16 bytes are the IV,
376+
/// and the second 16 bytes are the ciphertext.
377+
/// The encryption algorithm is AES-128-CBC.
378+
/// The key is derived from the persistent UV auth token using HKDF-SHA-256
379+
/// with the info string "encCredStoreState" and a salt of 32 bytes of 0x00.
380+
/// The plaintext is 16 bytes.
381+
/// </summary>
382+
public ReadOnlyMemory<byte>? EncCredStoreState { get; }
383+
384+
/// <summary>
385+
/// If present, a list of authenticator config commands supported by the authenticator.
386+
/// If <c>null</c>, the authenticator does not support the authenticatorConfig command or does not report supported commands.
387+
/// An empty list indicates that the authenticatorConfig command is supported but no specific commands are available.
388+
/// </summary>
389+
public IReadOnlyList<int>? AuthenticatorConfigCommands { get; }
390+
369391
// The default constructor explicitly defined. We don't want it to be
370392
// used.
371393
private AuthenticatorInfo()
@@ -503,6 +525,14 @@ public AuthenticatorInfo(ReadOnlyMemory<byte> cborEncoding)
503525
MaximumPinLength = cborMap.Contains(KeyMaxPinLength)
504526
? cborMap.ReadInt32(KeyMaxPinLength)
505527
: 63;
528+
529+
EncCredStoreState = cborMap.Contains(KeyEncCredStoreState)
530+
? cborMap.ReadByteString(KeyEncCredStoreState)
531+
: null;
532+
533+
AuthenticatorConfigCommands = cborMap.Contains(KeyAuthenticatorConfigCommands)
534+
? cborMap.ReadArray<int>(KeyAuthenticatorConfigCommands)
535+
: null;
506536
}
507537
catch (CborContentException cborException)
508538
{
@@ -592,6 +622,40 @@ List<PinUvAuthProtocol> ParsePinUvAuthProtocols(CborMap<int> cborMap)
592622
return decryptedIdentifier;
593623
}
594624

625+
/// <summary>
626+
/// Retrieves the credential store state derived from the encrypted credential store state, using the provided persistent UV authentication token.
627+
/// </summary>
628+
/// <param name="persistentPinUvAuthToken">
629+
/// The persistent PIN/UV authentication token used to derive the key for decryption.
630+
/// </param>
631+
/// <returns>
632+
/// The decrypted credential store state as a read-only memory block of bytes, or null if the encrypted credential store state is not set.
633+
/// </returns>
634+
public ReadOnlyMemory<byte>? GetCredStoreState(ReadOnlyMemory<byte> persistentPinUvAuthToken)
635+
{
636+
if (EncCredStoreState is null)
637+
{
638+
return null;
639+
}
640+
641+
if (persistentPinUvAuthToken.Length == 0)
642+
{
643+
return null;
644+
}
645+
646+
Span<byte> iv = stackalloc byte[16];
647+
Span<byte> ct = stackalloc byte[16];
648+
Span<byte> salt = stackalloc byte[32];
649+
EncCredStoreState.Value.Span[..16].CopyTo(iv);
650+
EncCredStoreState.Value.Span[16..].CopyTo(ct);
651+
652+
var key = HkdfUtilities.DeriveKey(persistentPinUvAuthToken.Span, salt, "encCredStoreState"u8, 16);
653+
var decryptedCredStoreState = AesUtilities.AesCbcDecrypt(key.Span, iv, ct);
654+
CryptographicOperations.ZeroMemory(key.Span);
655+
656+
return decryptedCredStoreState;
657+
}
658+
595659
/// <summary>
596660
/// Get the value of the given <c>option</c> in this
597661
/// <c>AuthenticatorInfo</c>.

0 commit comments

Comments
 (0)