Skip to content

Update access modifier of VersionQualifier #261

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Yubico.YubiKey/src/Yubico/YubiKey/IYubiKeyDeviceInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ public interface IYubiKeyDeviceInfo
/// The version of the firmware currently running on the YubiKey.
/// </summary>
public FirmwareVersion FirmwareVersion { get; }

/// <summary>
/// Represents a version qualifier for a firmware version.
/// A version qualifier typically includes the firmware version, a type (such as Alpha, Beta, or Final),
/// and an iteration number.
/// </summary>
public VersionQualifier VersionQualifier { get; }

/// <summary>
/// A string representation of the firmware version currently running on the YubiKey
/// which may include a version qualifier.
/// </summary>
public string VersionName { get; }

/// <summary>
/// The version of the chip/firmware storing the fingerprints (the second
Expand Down
25 changes: 16 additions & 9 deletions Yubico.YubiKey/src/Yubico/YubiKey/VersionQualifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace Yubico.YubiKey;
/// Represents the type of version qualifier for a firmware version.
/// The version qualifier type indicates whether the version is an Alpha, Beta, or Final release.
/// </summary>
internal enum VersionQualifierType : byte
public enum VersionQualifierType : byte
{
Alpha = 0x00,
Beta = 0x01,
Expand All @@ -32,12 +32,13 @@ internal enum VersionQualifierType : byte
/// A version qualifier typically includes the firmware version, a type (such as Alpha, Beta, or Final),
/// and an iteration number.
/// </summary>
internal class VersionQualifier
public class VersionQualifier
{
/// <summary>
/// Represents the firmware version associated with this qualifier.
/// </summary>
public FirmwareVersion FirmwareVersion { get; }

/// <summary>
/// Represents the type of version qualifier, such as Alpha, Beta, or Final.
/// </summary>
Expand All @@ -62,9 +63,10 @@ internal class VersionQualifier
/// <exception cref="ArgumentNullException"></exception>
public VersionQualifier(FirmwareVersion firmwareVersion, VersionQualifierType type, long iteration)
{
if (iteration < 0 || iteration > uint.MaxValue)
if (iteration is < 0 or > uint.MaxValue)
{
throw new ArgumentOutOfRangeException(nameof(iteration),
throw new ArgumentOutOfRangeException(
nameof(iteration),
$"Iteration must be between 0 and {uint.MaxValue}.");
}

Expand All @@ -82,13 +84,18 @@ public VersionQualifier()
{
FirmwareVersion = new FirmwareVersion();
Type = VersionQualifierType.Final;
Iteration = 0;
}

/// <summary>
/// Returns a string that represents the current <see cref="VersionQualifier"/>.
/// </summary>
public override string ToString() => $"{FirmwareVersion}.{Type.ToString().ToLowerInvariant()}.{Iteration}";
public override bool Equals(object obj) => obj is VersionQualifier other &&
FirmwareVersion.Equals(other.FirmwareVersion) &&
Type == other.Type &&
Iteration == other.Iteration;

public override bool Equals(object? obj) =>
obj is VersionQualifier other &&
FirmwareVersion.Equals(other.FirmwareVersion) &&
Type == other.Type &&
Iteration == other.Iteration;

public override int GetHashCode() => HashCode.Combine(FirmwareVersion, Type, Iteration);
}
64 changes: 35 additions & 29 deletions Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDevice.Instance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,67 +31,73 @@ public partial class YubiKeyDevice : IYubiKeyDevice
#region IYubiKeyDeviceInfo

/// <inheritdoc />
public YubiKeyCapabilities AvailableUsbCapabilities => _yubiKeyInfo.AvailableUsbCapabilities;
public YubiKeyCapabilities AvailableUsbCapabilities => _yubiKeyDeviceInfo.AvailableUsbCapabilities;

/// <inheritdoc />
public YubiKeyCapabilities EnabledUsbCapabilities => _yubiKeyInfo.EnabledUsbCapabilities;
public YubiKeyCapabilities EnabledUsbCapabilities => _yubiKeyDeviceInfo.EnabledUsbCapabilities;

/// <inheritdoc />
public YubiKeyCapabilities AvailableNfcCapabilities => _yubiKeyInfo.AvailableNfcCapabilities;
public YubiKeyCapabilities AvailableNfcCapabilities => _yubiKeyDeviceInfo.AvailableNfcCapabilities;

/// <inheritdoc />
public YubiKeyCapabilities EnabledNfcCapabilities => _yubiKeyInfo.EnabledNfcCapabilities;
public YubiKeyCapabilities EnabledNfcCapabilities => _yubiKeyDeviceInfo.EnabledNfcCapabilities;

/// <inheritdoc />
public YubiKeyCapabilities FipsApproved => _yubiKeyInfo.FipsApproved;
public YubiKeyCapabilities FipsApproved => _yubiKeyDeviceInfo.FipsApproved;

/// <inheritdoc />
public YubiKeyCapabilities FipsCapable => _yubiKeyInfo.FipsCapable;
public YubiKeyCapabilities FipsCapable => _yubiKeyDeviceInfo.FipsCapable;

/// <inheritdoc />
public YubiKeyCapabilities ResetBlocked => _yubiKeyInfo.ResetBlocked;
public YubiKeyCapabilities ResetBlocked => _yubiKeyDeviceInfo.ResetBlocked;

/// <inheritdoc />
public bool IsNfcRestricted => _yubiKeyInfo.IsNfcRestricted;
public bool IsNfcRestricted => _yubiKeyDeviceInfo.IsNfcRestricted;

/// <inheritdoc />
public string? PartNumber => _yubiKeyInfo.PartNumber;
public string? PartNumber => _yubiKeyDeviceInfo.PartNumber;

/// <inheritdoc />
public bool IsPinComplexityEnabled => _yubiKeyInfo.IsPinComplexityEnabled;
public bool IsPinComplexityEnabled => _yubiKeyDeviceInfo.IsPinComplexityEnabled;

/// <inheritdoc />
public int? SerialNumber => _yubiKeyInfo.SerialNumber;
public int? SerialNumber => _yubiKeyDeviceInfo.SerialNumber;

/// <inheritdoc />
public bool IsFipsSeries => _yubiKeyInfo.IsFipsSeries;
public bool IsFipsSeries => _yubiKeyDeviceInfo.IsFipsSeries;

/// <inheritdoc />
public bool IsSkySeries => _yubiKeyInfo.IsSkySeries;
public bool IsSkySeries => _yubiKeyDeviceInfo.IsSkySeries;

/// <inheritdoc />
public FormFactor FormFactor => _yubiKeyInfo.FormFactor;
public FormFactor FormFactor => _yubiKeyDeviceInfo.FormFactor;

/// <inheritdoc />
public FirmwareVersion FirmwareVersion => _yubiKeyInfo.FirmwareVersion;
public FirmwareVersion FirmwareVersion => _yubiKeyDeviceInfo.FirmwareVersion;

/// <inheritdoc />
public VersionQualifier VersionQualifier => _yubiKeyDeviceInfo.VersionQualifier;

/// <inheritdoc />
public string VersionName => _yubiKeyDeviceInfo.VersionName;

/// <inheritdoc />
public TemplateStorageVersion? TemplateStorageVersion => _yubiKeyInfo.TemplateStorageVersion;
public TemplateStorageVersion? TemplateStorageVersion => _yubiKeyDeviceInfo.TemplateStorageVersion;

/// <inheritdoc />
public ImageProcessorVersion? ImageProcessorVersion => _yubiKeyInfo.ImageProcessorVersion;
public ImageProcessorVersion? ImageProcessorVersion => _yubiKeyDeviceInfo.ImageProcessorVersion;

/// <inheritdoc />
public int AutoEjectTimeout => _yubiKeyInfo.AutoEjectTimeout;
public int AutoEjectTimeout => _yubiKeyDeviceInfo.AutoEjectTimeout;

/// <inheritdoc />
public byte ChallengeResponseTimeout => _yubiKeyInfo.ChallengeResponseTimeout;
public byte ChallengeResponseTimeout => _yubiKeyDeviceInfo.ChallengeResponseTimeout;

/// <inheritdoc />
public DeviceFlags DeviceFlags => _yubiKeyInfo.DeviceFlags;
public DeviceFlags DeviceFlags => _yubiKeyDeviceInfo.DeviceFlags;

/// <inheritdoc />
public bool ConfigurationLocked => _yubiKeyInfo.ConfigurationLocked;
public bool ConfigurationLocked => _yubiKeyDeviceInfo.ConfigurationLocked;

#endregion

Expand All @@ -109,7 +115,7 @@ public partial class YubiKeyDevice : IYubiKeyDevice
private ISmartCardDevice? _smartCardDevice;
private IHidDevice? _hidFidoDevice;
private IHidDevice? _hidKeyboardDevice;
private IYubiKeyDeviceInfo _yubiKeyInfo;
private IYubiKeyDeviceInfo _yubiKeyDeviceInfo;

private ConnectionFactory ConnectionFactory =>
new ConnectionFactory(
Expand Down Expand Up @@ -149,9 +155,9 @@ public Transport AvailableTransports
/// Constructs a <see cref="YubiKeyDevice"/> instance.
/// </summary>
/// <param name="device">A valid device; either a smart card, keyboard, or FIDO device.</param>
/// <param name="info">The YubiKey device information that describes the device.</param>
/// <param name="deviceInfo">The YubiKey device information that describes the device.</param>
/// <exception cref="ArgumentException">An unrecognized device type was given.</exception>
public YubiKeyDevice(IDevice device, IYubiKeyDeviceInfo info)
public YubiKeyDevice(IDevice device, IYubiKeyDeviceInfo deviceInfo)
{
switch (device)
{
Expand All @@ -170,7 +176,7 @@ public YubiKeyDevice(IDevice device, IYubiKeyDeviceInfo info)

_log.LogInformation("Created a YubiKeyDevice based on the {Transport} transport.", LastActiveTransport);

_yubiKeyInfo = info;
_yubiKeyDeviceInfo = deviceInfo;
IsNfcDevice = _smartCardDevice?.IsNfcTransport() ?? false;
LastActiveTransport = GetTransportIfOnlyDevice();
}
Expand All @@ -192,7 +198,7 @@ public YubiKeyDevice(
_hidFidoDevice = hidFidoDevice;
_hidKeyboardDevice = hidKeyboardDevice;

_yubiKeyInfo = yubiKeyDeviceInfo;
_yubiKeyDeviceInfo = yubiKeyDeviceInfo;
IsNfcDevice = smartCardDevice?.IsNfcTransport() ?? false;
LastActiveTransport = GetTransportIfOnlyDevice(); // Must be after setting the three device fields.
}
Expand Down Expand Up @@ -228,13 +234,13 @@ internal void Merge(IDevice device, IYubiKeyDeviceInfo info)
MergeDevice(device);

// Then merge the YubiKey device information / metadata
if (_yubiKeyInfo is YubiKeyDeviceInfo first && info is YubiKeyDeviceInfo second)
if (_yubiKeyDeviceInfo is YubiKeyDeviceInfo first && info is YubiKeyDeviceInfo second)
{
_yubiKeyInfo = first.Merge(second);
_yubiKeyDeviceInfo = first.Merge(second);
}
else
{
_yubiKeyInfo = info;
_yubiKeyDeviceInfo = info;
}
}

Expand Down
46 changes: 31 additions & 15 deletions Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDeviceInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Microsoft.Extensions.Logging;
using Yubico.Core.Logging;
using Yubico.Core.Tlv;

namespace Yubico.YubiKey
Expand Down Expand Up @@ -75,6 +76,7 @@ public class YubiKeyDeviceInfo : IYubiKeyDeviceInfo
/// <inheritdoc />
public FormFactor FormFactor { get; set; }

/// <inheritdoc />
public string VersionName => VersionQualifier.Type == VersionQualifierType.Final
? FirmwareVersion.ToString()
: VersionQualifier.ToString();
Expand All @@ -83,7 +85,7 @@ public class YubiKeyDeviceInfo : IYubiKeyDeviceInfo
public FirmwareVersion FirmwareVersion { get; set; }

/// <inheritdoc />
internal VersionQualifier VersionQualifier { get; set; }
public VersionQualifier VersionQualifier { get; set; }

/// <inheritdoc />
public TemplateStorageVersion? TemplateStorageVersion { get; set; }
Expand Down Expand Up @@ -269,12 +271,24 @@ internal static YubiKeyDeviceInfo CreateFromResponseData(Dictionary<int, ReadOnl
}
}

SetFipsSeries(deviceInfo, fipsSeriesFlag);
SetSkySeries(deviceInfo, skySeriesFlag);
SetFirmwareVersionAndQualifier(responseApduData, deviceInfo);

return deviceInfo;
}

private static void SetSkySeries(YubiKeyDeviceInfo deviceInfo, bool skySeriesFlag) => deviceInfo.IsSkySeries |= skySeriesFlag;

private static void SetFipsSeries(YubiKeyDeviceInfo deviceInfo, bool fipsSeriesFlag)
{
deviceInfo.IsFipsSeries = deviceInfo.FirmwareVersion >= _fipsFlagInclusiveLowerBound
? fipsSeriesFlag
: deviceInfo.IsFipsVersion;
}

deviceInfo.IsSkySeries |= skySeriesFlag;

private static void SetFirmwareVersionAndQualifier(Dictionary<int, ReadOnlyMemory<byte>> responseApduData, YubiKeyDeviceInfo deviceInfo)
{
if (!responseApduData.TryGetValue(YubikeyDeviceManagementTags.VersionQualifierTag, out var versionQualifierBytes))
{
deviceInfo.VersionQualifier = new VersionQualifier(deviceInfo.FirmwareVersion, VersionQualifierType.Final, 0);
Expand All @@ -286,21 +300,21 @@ internal static YubiKeyDeviceInfo CreateFromResponseData(Dictionary<int, ReadOnl
throw new ArgumentException("Invalid data length.");
}

const byte TAG_VERSION = 0x01;
const byte TAG_TYPE = 0x02;
const byte TAG_ITERATION = 0x03;
const byte tagVersion = 0x01;
const byte tagType = 0x02;
const byte tagIteration = 0x03;

var data = TlvObjects.DecodeDictionary(versionQualifierBytes.Span);

if (!data.TryGetValue(TAG_VERSION, out var firmwareVersionBytes))
if (!data.TryGetValue(tagVersion, out var firmwareVersionBytes))
{
throw new ArgumentException("Missing TLV field: TAG_VERSION.");
}
if (!data.TryGetValue(TAG_TYPE, out var versionTypeBytes))
if (!data.TryGetValue(tagType, out var versionTypeBytes))
{
throw new ArgumentException("Missing TLV field: TAG_TYPE.");
}
if (!data.TryGetValue(TAG_ITERATION, out var iterationBytes))
if (!data.TryGetValue(tagIteration, out var iterationBytes))
{
throw new ArgumentException("Missing TLV field: TAG_ITERATION.");
}
Expand All @@ -318,13 +332,15 @@ internal static YubiKeyDeviceInfo CreateFromResponseData(Dictionary<int, ReadOnl
bool isFinalVersion = deviceInfo.VersionQualifier.Type == VersionQualifierType.Final;
if (!isFinalVersion)
{
var Logger = Core.Logging.Log.GetLogger<YubiKeyDeviceInfo>();
Logger.LogDebug("Overriding behavioral version with {FirmwareString}", deviceInfo.VersionQualifier.FirmwareVersion);
var logger = Log.GetLogger<YubiKeyDeviceInfo>();
logger.LogDebug("Overriding behavioral version with {FirmwareString}", deviceInfo.VersionQualifier.FirmwareVersion);
}

var computedVersion = isFinalVersion ? deviceInfo.FirmwareVersion : deviceInfo.VersionQualifier.FirmwareVersion;
deviceInfo.FirmwareVersion = computedVersion;
return deviceInfo;
var finalVersion = isFinalVersion
? deviceInfo.FirmwareVersion
: deviceInfo.VersionQualifier.FirmwareVersion;

deviceInfo.FirmwareVersion = finalVersion;
}

private static string? GetPartNumber(ReadOnlySpan<byte> valueSpan)
Expand Down Expand Up @@ -372,7 +388,7 @@ internal YubiKeyDeviceInfo Merge(YubiKeyDeviceInfo? second)
? FirmwareVersion
: second.FirmwareVersion,

VersionQualifier = VersionQualifier != new VersionQualifier()
VersionQualifier = !Equals(VersionQualifier, new VersionQualifier())
? VersionQualifier
: second.VersionQualifier,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,12 @@ public void Attest_Imported_ThrowsException(
Session.CreateAttestationStatement(PivSlot.Retired1));
}

[SkippableTheory(typeof(DeviceNotFoundException))]
[SkippableTheory(typeof(DeviceNotFoundException), typeof(NotSupportedException))]
[InlineData(KeyType.RSA1024, StandardTestDevice.Fw5)]
[InlineData(KeyType.RSA2048, StandardTestDevice.Fw5)]
[InlineData(KeyType.RSA3072, StandardTestDevice.Fw5)]
[InlineData(KeyType.RSA4096, StandardTestDevice.Fw5)]
[InlineData(KeyType.ECP256, StandardTestDevice.Fw5Fips)]
[InlineData(KeyType.ECP256, StandardTestDevice.Fw5)]
[InlineData(KeyType.Ed25519, StandardTestDevice.Fw5)]
public void AttestGenerated(
KeyType keyType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public void ChangeRetry_SetsToDefault(StandardTestDevice testDeviceType)

using (var pivSession = new PivSession(testDevice))
{
pivSession.ResetApplication();
try
{
var collectorObj = new Simple39KeyCollector();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ public sealed class HollowYubiKeyDevice : IYubiKeyDevice
/// <inheritdoc />
public FirmwareVersion FirmwareVersion { get; set; }

public VersionQualifier VersionQualifier { get; }
public string VersionName => VersionQualifier.Type == VersionQualifierType.Final
? FirmwareVersion.ToString()
: VersionQualifier.ToString();

/// <inheritdoc />
public TemplateStorageVersion TemplateStorageVersion { get; set; }

Expand Down Expand Up @@ -113,6 +118,8 @@ public HollowYubiKeyDevice(bool alwaysAuthenticatePiv = false)
// We initialize this to zeros, but if you need a version,
// the setter is public. HollowConnection takes a version
// and feeds it back in the ReadStatusCommand.

VersionQualifier = new VersionQualifier();
FirmwareVersion = new FirmwareVersion
{
Major = 0,
Expand Down
Loading