diff --git a/Yubico.Core/tests/Yubico/Core/Devices/Hid/HidTranslatorTests.cs b/Yubico.Core/tests/Yubico/Core/Devices/Hid/HidTranslatorTests.cs index 32616a6d..7f983995 100644 --- a/Yubico.Core/tests/Yubico/Core/Devices/Hid/HidTranslatorTests.cs +++ b/Yubico.Core/tests/Yubico/Core/Devices/Hid/HidTranslatorTests.cs @@ -144,16 +144,18 @@ public void TestSpecializedKeyboardSupportsModhexString(KeyboardLayout layout) } #if Windows +#pragma warning disable CA1825 [Theory] [MemberData(nameof(GetTestData))] public void GetChar_GivenHidCode_ReturnsCorrectChar(KeyboardLayout layout, (char, byte)[] testData) { - HidCodeTranslator hid = HidCodeTranslator.GetInstance(layout); + var hid = HidCodeTranslator.GetInstance(layout); foreach ((char ch, byte code) item in testData) { Assert.Equal(item.ch, hid[item.code]); } } +#pragma warning restore CA1825 #endif public static IEnumerable GetTestData() diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/ConnectionManager.cs b/Yubico.YubiKey/src/Yubico/YubiKey/ConnectionManager.cs index aca9824c..01ec1a5b 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/ConnectionManager.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/ConnectionManager.cs @@ -179,7 +179,7 @@ public bool TryCreateConnection( { IHidDevice { UsagePage: HidUsagePage.Fido } d => new FidoConnection(d), IHidDevice { UsagePage: HidUsagePage.Keyboard } d => new KeyboardConnection(d), - ISmartCardDevice d => new CcidConnection(d, application), + ISmartCardDevice d => new SmartcardConnection(d, application), _ => throw new NotSupportedException(ExceptionMessages.DeviceTypeNotRecognized) }; @@ -266,7 +266,7 @@ public bool TryCreateConnection( return false; } - connection = new CcidConnection(smartCardDevice, applicationId); + connection = new SmartcardConnection(smartCardDevice, applicationId); _ = _openConnections.Add(yubiKeyDevice); } finally diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/DeviceInfoHelper.cs b/Yubico.YubiKey/src/Yubico/YubiKey/DeviceInfoHelper.cs new file mode 100644 index 00000000..5591b69f --- /dev/null +++ b/Yubico.YubiKey/src/Yubico/YubiKey/DeviceInfoHelper.cs @@ -0,0 +1,107 @@ +// Copyright 2023 Yubico AB +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using Yubico.Core.Logging; +using Yubico.Core.Tlv; +using Yubico.YubiKey.Management.Commands; + +namespace Yubico.YubiKey +{ + internal static class DeviceInfoHelper + { + /// + /// Fetches and aggregates device configuration details from a YubiKey using multiple APDU commands, + /// paging through the data as needed until all configuration data is retrieved. + /// This method processes the responses, accumulating TLV-encoded data into a single dictionary. + /// + /// The specific type of IGetPagedDeviceInfoCommand, e.g. GetPagedDeviceInfoCommand, which will then allow for returning the appropriate response. + /// The connection interface to communicate with a YubiKey. + /// A YubiKeyDeviceInfo object containing all relevant device information. + /// Thrown when the command fails to retrieve successful response statuses from the YubiKey. + public static YubiKeyDeviceInfo GetDeviceInfo(IYubiKeyConnection connection) + where TCommand : IGetPagedDeviceInfoCommand>>>, new() + { + Logger log = Log.GetLogger(); + + int page = 0; + var pages = new Dictionary>(); + + bool hasMoreData = true; + while (hasMoreData) + { + IYubiKeyResponseWithData>> response = connection.SendCommand(new TCommand {Page = (byte)page++}); + if (response.Status == ResponseStatus.Success) + { + Dictionary> tlvData = response.GetData(); + foreach (KeyValuePair> tlv in tlvData) + { + pages.Add(tlv.Key, tlv.Value); + } + + const int moreDataTag = 0x10; + hasMoreData = tlvData.TryGetValue(moreDataTag, out ReadOnlyMemory hasMoreDataByte) + && hasMoreDataByte.Span.Length == 1 + && hasMoreDataByte.Span[0] == 1; + } + else + { + log.LogError("Failed to get device info page-{Page}: {Error} {Message}", + page, response.StatusWord, response.StatusMessage); + + return new YubiKeyDeviceInfo(); // TODO What to return here? Null? Empty? Exception? + } + } + + return YubiKeyDeviceInfo.CreateFromResponseData(pages); + } + + /// + /// Attempts to create a dictionary from a TLV-encoded byte array by parsing and extracting tag-value pairs. + /// + /// The byte array containing TLV-encoded data. + /// When successful, contains a dictionary mapping integer tags to their corresponding values as byte arrays. + /// True if the dictionary was successfully created; false otherwise. + public static bool TryCreateApduDictionaryFromResponseData( + ReadOnlyMemory tlvData, out Dictionary> result) + { + Logger log = Log.GetLogger(); + result = new Dictionary>(); + + if (tlvData.IsEmpty) + { + log.LogWarning("ResponseAPDU data was empty!"); + return false; + } + + int tlvDataLength = tlvData.Span[0]; + if (tlvDataLength == 0 || 1 + tlvDataLength > tlvData.Length) + { + log.LogWarning("TLV Data length was out of expected ranges. {Length}", tlvDataLength); + return false; + } + + var tlvReader = new TlvReader(tlvData.Slice(1, tlvDataLength)); + while (tlvReader.HasData) + { + int tag = tlvReader.PeekTag(); + ReadOnlyMemory value = tlvReader.ReadValue(tag); + result.Add(tag, value); + } + + return true; + } + } +} diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/FidoDeviceInfoFactory.cs b/Yubico.YubiKey/src/Yubico/YubiKey/FidoDeviceInfoFactory.cs index 3240c602..a513e6db 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/FidoDeviceInfoFactory.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/FidoDeviceInfoFactory.cs @@ -13,12 +13,11 @@ // limitations under the License. using System; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using Microsoft.Extensions.Logging; using Yubico.Core.Devices.Hid; using Yubico.Core.Logging; using Yubico.YubiKey.DeviceExtensions; +using Yubico.YubiKey.U2f.Commands; namespace Yubico.YubiKey { @@ -56,7 +55,8 @@ public static YubiKeyDeviceInfo GetDeviceInfo(IHidDevice device) ykDeviceInfo.FirmwareVersion = firmwareVersion; } - if (ykDeviceInfo.FirmwareVersion < FirmwareVersion.V4_0_0 && ykDeviceInfo.AvailableUsbCapabilities == YubiKeyCapabilities.None) + if (ykDeviceInfo.FirmwareVersion < FirmwareVersion.V4_0_0 && + ykDeviceInfo.AvailableUsbCapabilities == YubiKeyCapabilities.None) { ykDeviceInfo.AvailableUsbCapabilities = YubiKeyCapabilities.FidoU2f; } @@ -64,25 +64,21 @@ public static YubiKeyDeviceInfo GetDeviceInfo(IHidDevice device) return ykDeviceInfo; } - private static bool TryGetDeviceInfoFromFido(IHidDevice device, [MaybeNullWhen(returnValue: false)] out YubiKeyDeviceInfo yubiKeyDeviceInfo) + private static bool TryGetDeviceInfoFromFido(IHidDevice device, + [MaybeNullWhen(returnValue: false)] + out YubiKeyDeviceInfo yubiKeyDeviceInfo) { Logger log = Log.GetLogger(); try { log.LogInformation("Attempting to read device info via the FIDO interface management command."); - using var FidoConnection = new FidoConnection(device); - - U2f.Commands.GetDeviceInfoResponse response = FidoConnection.SendCommand(new U2f.Commands.GetDeviceInfoCommand()); - - if (response.Status == ResponseStatus.Success) - { - yubiKeyDeviceInfo = response.GetData(); - log.LogInformation("Successfully read device info via FIDO interface management command."); - return true; - } - - log.LogError("Failed to get device info from management application: {Error} {Message}", response.StatusWord, response.StatusMessage); + using var connection = new FidoConnection(device); + yubiKeyDeviceInfo = DeviceInfoHelper.GetDeviceInfo(connection); + + log.LogInformation("Successfully read device info via FIDO interface management command."); + return true; + //TODO Handle exceptions? } catch (NotImplementedException e) { @@ -100,29 +96,38 @@ private static bool TryGetDeviceInfoFromFido(IHidDevice device, [MaybeNullWhen(r ErrorHandler(e, "Must have elevated privileges in Windows to access FIDO device directly."); } - log.LogWarning("Failed to read device info through the management interface. This may be expected for older YubiKeys."); + log.LogWarning( + "Failed to read device info through the management interface. This may be expected for older YubiKeys."); + yubiKeyDeviceInfo = null; + return false; } - private static bool TryGetFirmwareVersionFromFido(IHidDevice device, [MaybeNullWhen(returnValue: false)] out FirmwareVersion firmwareVersion) + private static bool TryGetFirmwareVersionFromFido(IHidDevice device, + [MaybeNullWhen(returnValue: false)] + out FirmwareVersion firmwareVersion) { Logger log = Log.GetLogger(); try { log.LogInformation("Attempting to read firmware version through FIDO."); - using var FidoConnection = new FidoConnection(device); + using var fidoConnection = new FidoConnection(device); - Fido2.Commands.VersionResponse response = FidoConnection.SendCommand(new Fido2.Commands.VersionCommand()); + Fido2.Commands.VersionResponse response = + fidoConnection.SendCommand(new Fido2.Commands.VersionCommand()); if (response.Status == ResponseStatus.Success) { firmwareVersion = response.GetData(); log.LogInformation("Firmware version: {Version}", firmwareVersion.ToString()); + return true; } - log.LogError("Reading firmware version via FIDO failed with: {Error} {Message}", response.StatusWord, response.StatusMessage); + + log.LogError("Reading firmware version via FIDO failed with: {Error} {Message}", response.StatusWord, + response.StatusMessage); } catch (NotImplementedException e) { @@ -142,10 +147,11 @@ private static bool TryGetFirmwareVersionFromFido(IHidDevice device, [MaybeNullW log.LogWarning("Failed to read firmware version through FIDO."); firmwareVersion = null; + return false; } - private static void ErrorHandler(Exception exception, string message) - => Log.GetLogger().LogWarning(exception, message); + private static void ErrorHandler(Exception exception, string message) => + Log.GetLogger().LogWarning(exception, message); } } diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/IGetPagedDeviceInfoCommand.cs b/Yubico.YubiKey/src/Yubico/YubiKey/IGetPagedDeviceInfoCommand.cs new file mode 100644 index 00000000..22a651db --- /dev/null +++ b/Yubico.YubiKey/src/Yubico/YubiKey/IGetPagedDeviceInfoCommand.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace Yubico.YubiKey +{ + public interface IGetPagedDeviceInfoCommand : IYubiKeyCommand + where T : IYubiKeyResponseWithData>> + { + public byte Page { get; set; } + + } +} + diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/IYubiKeyDevice.cs b/Yubico.YubiKey/src/Yubico/YubiKey/IYubiKeyDevice.cs index ea57b68f..8a12f215 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/IYubiKeyDevice.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/IYubiKeyDevice.cs @@ -662,5 +662,7 @@ void SetLegacyDeviceConfiguration( byte challengeResponseTimeout, bool touchEjectEnabled, int autoEjectTimeout = 0); + + void SetIsNfcRestricted(bool enabled); } } diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/IYubiKeyDeviceInfo.cs b/Yubico.YubiKey/src/Yubico/YubiKey/IYubiKeyDeviceInfo.cs index b0748942..41d38be5 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/IYubiKeyDeviceInfo.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/IYubiKeyDeviceInfo.cs @@ -38,6 +38,11 @@ public interface IYubiKeyDeviceInfo /// The NFC features that are currently enabled over NFC. /// public YubiKeyCapabilities EnabledNfcCapabilities { get; } + + /// + /// TODO + /// + public bool IsNfcRestricted { get; } /// /// The serial number of the YubiKey, if one is present. diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/KeyboardDeviceInfoFactory.cs b/Yubico.YubiKey/src/Yubico/YubiKey/KeyboardDeviceInfoFactory.cs index 097f8d1b..dc20952b 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/KeyboardDeviceInfoFactory.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/KeyboardDeviceInfoFactory.cs @@ -17,6 +17,7 @@ using Yubico.Core.Devices.Hid; using Yubico.Core.Logging; using Yubico.YubiKey.DeviceExtensions; +using Yubico.YubiKey.Otp.Commands; namespace Yubico.YubiKey { @@ -73,18 +74,21 @@ private static bool TryGetDeviceInfoFromKeyboard(IHidDevice device, [MaybeNullWh try { log.LogInformation("Attempting to read device info via the management command over the keyboard interface."); - using var KeyboardConnection = new KeyboardConnection(device); - - Otp.Commands.GetDeviceInfoResponse response = KeyboardConnection.SendCommand(new Otp.Commands.GetDeviceInfoCommand()); - - if (response.Status == ResponseStatus.Success) - { - yubiKeyDeviceInfo = response.GetData(); - log.LogInformation("Successfully read device info via the keyboard management command."); - return true; - } - - log.LogError("Failed to get device info from the keyboard management command: {Error} {Message}", response.StatusWord, response.StatusMessage); + using var connection = new KeyboardConnection(device); + yubiKeyDeviceInfo = DeviceInfoHelper.GetDeviceInfo(connection); + //TODO Handle exceptions? + return true; + + // Otp.Commands.GetDeviceInfoResponse response = keyboardConnection.SendCommand(new Otp.Commands.GetDeviceInfoCommand()); + // + // if (response.Status == ResponseStatus.Success) + // { + // yubiKeyDeviceInfo = response.GetData(); + // log.LogInformation("Successfully read device info via the keyboard management command."); + // return true; + // } + + // log.LogError("Failed to get device info from the keyboard management command: {Error} {Message}", response.StatusWord, response.StatusMessage); } catch (KeyboardConnectionException e) { diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetDeviceInfoCommand.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetDeviceInfoCommand.cs index c4541dec..f5d9989c 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetDeviceInfoCommand.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetDeviceInfoCommand.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using Yubico.Core.Iso7816; namespace Yubico.YubiKey.Management.Commands @@ -22,6 +23,7 @@ namespace Yubico.YubiKey.Management.Commands /// /// This class has a corresponding partner class /// + [Obsolete("This class has been replaced by GetPagedDeviceInfoCommand")] public class GetDeviceInfoCommand : IYubiKeyCommand { private const byte GetDeviceInfoInstruction = 0x1D; diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetDeviceInfoResponse.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetDeviceInfoResponse.cs index 10dff965..769540b4 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetDeviceInfoResponse.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetDeviceInfoResponse.cs @@ -21,6 +21,7 @@ namespace Yubico.YubiKey.Management.Commands /// The response to the command, containing the YubiKey's /// device configuration details. /// + [Obsolete("This class has been replaced by GetPagedDeviceInfoResponse")] public class GetDeviceInfoResponse : YubiKeyResponse, IYubiKeyResponseWithData { /// @@ -51,7 +52,7 @@ public YubiKeyDeviceInfo GetData() if (ResponseApdu.Data.Length > 255) { - throw new MalformedYubiKeyResponseException() + throw new MalformedYubiKeyResponseException { ResponseClass = nameof(GetDeviceInfoResponse), ActualDataLength = ResponseApdu.Data.Length @@ -60,7 +61,7 @@ public YubiKeyDeviceInfo GetData() if (!YubiKeyDeviceInfo.TryCreateFromResponseData(ResponseApdu.Data, out YubiKeyDeviceInfo? deviceInfo)) { - throw new MalformedYubiKeyResponseException() + throw new MalformedYubiKeyResponseException { ResponseClass = nameof(GetDeviceInfoResponse), }; diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetPagedDeviceInfoCommand.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetPagedDeviceInfoCommand.cs new file mode 100644 index 00000000..82e307bc --- /dev/null +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetPagedDeviceInfoCommand.cs @@ -0,0 +1,60 @@ +// Copyright 2021 Yubico AB +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using Yubico.Core.Iso7816; + +namespace Yubico.YubiKey.Management.Commands +{ + /// + /// Gets detailed information about the YubiKey and its current configuration. + /// + /// + /// This class has a corresponding partner class + /// + public sealed class GetPagedDeviceInfoCommand : IGetPagedDeviceInfoCommand + { + private const byte GetDeviceInfoInstruction = 0x1D; + public byte Page { get; set; } + + /// + /// Gets the YubiKeyApplication to which this command belongs. + /// + /// + /// + /// + public YubiKeyApplication Application => YubiKeyApplication.Management; + + /// + /// Initializes a new instance of the class. + /// + public GetPagedDeviceInfoCommand() + { + + } + + /// + public CommandApdu CreateCommandApdu() => new CommandApdu + { + Ins = GetDeviceInfoInstruction, + P1 = Page + }; + + /// + public GetPagedDeviceInfoResponse CreateResponseForApdu(ResponseApdu responseApdu) => + new GetPagedDeviceInfoResponse(responseApdu); + } +} + diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetPagedDeviceInfoResponse.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetPagedDeviceInfoResponse.cs new file mode 100644 index 00000000..7a04af65 --- /dev/null +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/GetPagedDeviceInfoResponse.cs @@ -0,0 +1,73 @@ +// Copyright 2021 Yubico AB +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using Yubico.Core.Iso7816; + +namespace Yubico.YubiKey.Management.Commands +{ + /// + /// The response to the command, containing the YubiKey's + /// device configuration details. + /// + public class GetPagedDeviceInfoResponse : YubiKeyResponse, + IYubiKeyResponseWithData>> + { + /// + /// Constructs a GetPagedDeviceInfoResponse instance based on a ResponseApdu received from the YubiKey. + /// + /// + /// The ResponseApdu returned by the YubiKey. + /// + public GetPagedDeviceInfoResponse(ResponseApdu responseApdu) + : base(responseApdu) + { + + } + + /// + /// Retrieves and converts the response data from an APDU response into a dictionary of TLV tags and their corresponding values. + /// + /// A dictionary mapping integer tags to their corresponding values as byte arrays. + /// Thrown when the response status is not successful. + /// Thrown when the APDU data length exceeds expected bounds or if the data conversion fails. + public Dictionary> GetData() + { + if (Status != ResponseStatus.Success) + { + throw new InvalidOperationException(StatusMessage); + } + + if (ResponseApdu.Data.Length > 255) + { + throw new MalformedYubiKeyResponseException + { + ResponseClass = nameof(GetPagedDeviceInfoResponse), + ActualDataLength = ResponseApdu.Data.Length + }; + } + + if (!DeviceInfoHelper.TryCreateApduDictionaryFromResponseData(ResponseApdu.Data, out Dictionary> result)) + { + throw new MalformedYubiKeyResponseException + { + ResponseClass = nameof(GetPagedDeviceInfoResponse), + }; + } + + return result; + } + } +} diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/SetDeviceInfoBaseCommand.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/SetDeviceInfoBaseCommand.cs index 2403bf68..07f76148 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/SetDeviceInfoBaseCommand.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Management/Commands/SetDeviceInfoBaseCommand.cs @@ -32,6 +32,7 @@ public class SetDeviceInfoBaseCommand private const byte ConfigurationUnlockPresentTag = 0x0b; private const byte ResetAfterConfigTag = 0x0c; private const byte NfcEnabledCapabilitiesTag = 0x0e; + private const byte NfcRestrictedTag = 0x17; private byte[]? _lockCode; @@ -100,13 +101,18 @@ public int? AutoEjectTimeout /// updates. Useful if enabling or disabling capabilities. /// public bool ResetAfterConfig { get; set; } - + + /// + /// TODO + /// + public bool IsNfcRestricted { get; set; } /// /// Initializes a new instance of the class. /// protected SetDeviceInfoBaseCommand() { + } protected SetDeviceInfoBaseCommand(SetDeviceInfoBaseCommand baseCommand) @@ -115,6 +121,7 @@ protected SetDeviceInfoBaseCommand(SetDeviceInfoBaseCommand baseCommand) { EnabledUsbCapabilities = baseCommand.EnabledUsbCapabilities; EnabledNfcCapabilities = baseCommand.EnabledNfcCapabilities; + IsNfcRestricted = baseCommand.IsNfcRestricted; ChallengeResponseTimeout = baseCommand.ChallengeResponseTimeout; AutoEjectTimeout = baseCommand.AutoEjectTimeout; DeviceFlags = baseCommand.DeviceFlags; @@ -243,6 +250,11 @@ private byte[] GetTlvData() { buffer.WriteValue(ConfigurationUnlockPresentTag, unlockCode); } + + if (IsNfcRestricted) + { + buffer.WriteByte(NfcRestrictedTag, 1); + } return buffer.Encode(); } diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetDeviceInfoCommand.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetDeviceInfoCommand.cs index 4ce1412d..543f062e 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetDeviceInfoCommand.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetDeviceInfoCommand.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using Yubico.Core.Iso7816; namespace Yubico.YubiKey.Otp.Commands @@ -22,6 +23,7 @@ namespace Yubico.YubiKey.Otp.Commands /// /// This class has a corresponding partner class /// + [Obsolete("This class has been replaced by GetPagedDeviceInfoCommand")] public class GetDeviceInfoCommand : IYubiKeyCommand { /// diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetDeviceInfoResponse.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetDeviceInfoResponse.cs index aeabb327..2eeb1afb 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetDeviceInfoResponse.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetDeviceInfoResponse.cs @@ -21,6 +21,7 @@ namespace Yubico.YubiKey.Otp.Commands /// The response to the command, containing the YubiKey's /// device configuration details. /// + [Obsolete("This class has been replaced by GetPagedDeviceInfoResponse")] public class GetDeviceInfoResponse : OtpResponse, IYubiKeyResponseWithData { /// @@ -58,7 +59,7 @@ public YubiKeyDeviceInfo GetData() if (ResponseApdu.Data.Length > 255) { - throw new MalformedYubiKeyResponseException() + throw new MalformedYubiKeyResponseException { ResponseClass = nameof(GetDeviceInfoResponse), ActualDataLength = ResponseApdu.Data.Length @@ -67,7 +68,7 @@ public YubiKeyDeviceInfo GetData() if (!YubiKeyDeviceInfo.TryCreateFromResponseData(ResponseApdu.Data, out YubiKeyDeviceInfo? deviceInfo)) { - throw new MalformedYubiKeyResponseException() + throw new MalformedYubiKeyResponseException { ResponseClass = nameof(GetDeviceInfoResponse), }; diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetPagedDeviceInfoCommand.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetPagedDeviceInfoCommand.cs new file mode 100644 index 00000000..c10073ed --- /dev/null +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetPagedDeviceInfoCommand.cs @@ -0,0 +1,60 @@ +// Copyright 2021 Yubico AB +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Yubico.Core.Iso7816; + + +namespace Yubico.YubiKey.Otp.Commands +{ + /// + /// Gets detailed information about the YubiKey and its current configuration. + /// + /// + /// This class has a corresponding partner class + /// + public class GetPagedDeviceInfoCommand : IGetPagedDeviceInfoCommand + { + /// + /// Gets the YubiKeyApplication to which this command belongs. + /// + /// + /// YubiKeyApplication.Otp + /// + public YubiKeyApplication Application => YubiKeyApplication.Otp; + + public byte Page { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public GetPagedDeviceInfoCommand() + { + + } + + /// + public CommandApdu CreateCommandApdu() => new CommandApdu() + { + Ins = OtpConstants.RequestSlotInstruction, + P1 = OtpConstants.GetDeviceInfoSlot, + Data = new []{Page} + }; + + /// + public GetPagedDeviceInfoResponse CreateResponseForApdu(ResponseApdu responseApdu) => + new GetPagedDeviceInfoResponse(responseApdu); + } + + +} diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetPagedDeviceInfoResponse.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetPagedDeviceInfoResponse.cs new file mode 100644 index 00000000..aa961384 --- /dev/null +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Otp/Commands/GetPagedDeviceInfoResponse.cs @@ -0,0 +1,72 @@ +// Copyright 2021 Yubico AB +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using Yubico.Core.Iso7816; + +namespace Yubico.YubiKey.Otp.Commands +{ + /// + /// The response to the command, containing the YubiKey's + /// device configuration details. + /// + public class GetPagedDeviceInfoResponse : OtpResponse, IYubiKeyResponseWithData>> + { + /// + /// Constructs a GetPagedDeviceInfoResponse instance based on a ResponseApdu received from the YubiKey. + /// + /// + /// The ResponseApdu returned by the YubiKey. + /// + public GetPagedDeviceInfoResponse(ResponseApdu responseApdu) : + base(responseApdu) + { + + } + + /// + /// Retrieves and converts the response data from an APDU response into a dictionary of TLV tags and their corresponding values. + /// + /// A dictionary mapping integer tags to their corresponding values as byte arrays. + /// Thrown when the response status is not successful. + /// Thrown when the APDU data length exceeds expected bounds or if the data conversion fails. + public Dictionary> GetData() + { + if (Status != ResponseStatus.Success) + { + throw new InvalidOperationException(StatusMessage); + } + + if (ResponseApdu.Data.Length > 255) + { + throw new MalformedYubiKeyResponseException + { + ResponseClass = nameof(GetPagedDeviceInfoResponse), + ActualDataLength = ResponseApdu.Data.Length + }; + } + + if (!DeviceInfoHelper.TryCreateApduDictionaryFromResponseData(ResponseApdu.Data, out Dictionary> result)) + { + throw new MalformedYubiKeyResponseException + { + ResponseClass = nameof(GetPagedDeviceInfoResponse), + }; + } + + return result; + } + } +} diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/GetDataCommand.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/GetDataCommand.cs index 94308f30..568d8914 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/GetDataCommand.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/GetDataCommand.cs @@ -38,7 +38,7 @@ namespace Yubico.YubiKey.Piv.Commands /// will not need to use this command. For example, if you want to get a /// certificate from a YubiKey, use . /// Or if you want to store/retrieve Key History, use - /// and + /// and /// along with the class. Under the /// covers, these APIs will ultimately call this command. But the application /// that uses the SDK can simply make the specific API calls, rather than use diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/PutDataCommand.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/PutDataCommand.cs index a55adfff..2702f151 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/PutDataCommand.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/PutDataCommand.cs @@ -58,7 +58,7 @@ namespace Yubico.YubiKey.Piv.Commands /// not need to use this command. For example, if you want to put a /// certificate onto a YubiKey, use . /// Or if you want to store/retrieve Key History, use - /// and + /// and /// along with the class. Under the /// covers, these APIs will ultimately call this command. But the application /// that uses the SDK can simply make the specific API calls, rather than use diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Objects/PivDataObject.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Objects/PivDataObject.cs index a4c4ae8f..6c1d4d1f 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Objects/PivDataObject.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Objects/PivDataObject.cs @@ -22,7 +22,7 @@ namespace Yubico.YubiKey.Piv.Objects /// Data Object. /// /// - /// Generally you will use one of the methods to + /// Generally you will use one of the methods to /// get the specified data out of a YubiKey. The formatted data will be /// parsed and the resulting object will present the data in a more readable /// form. You can then update the data and call diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Objects.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Objects.cs index 204b581e..c0b064c2 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Objects.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Objects.cs @@ -173,7 +173,7 @@ public sealed partial class PivSession : IDisposable /// PIV data objects. /// /// - /// This is the same as the method that takes no + /// This is the same as the method that takes no /// arguments, except this one will get the data in the storage location /// specified by dataTag, as opposed to the defined data tag for /// the class T. This method will still expect the data to be diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Scp03/Scp03CcidConnection.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Scp03/Scp03Connection.cs similarity index 92% rename from Yubico.YubiKey/src/Yubico/YubiKey/Scp03/Scp03CcidConnection.cs rename to Yubico.YubiKey/src/Yubico/YubiKey/Scp03/Scp03Connection.cs index fbb352bf..c434bbf3 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Scp03/Scp03CcidConnection.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Scp03/Scp03Connection.cs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Linq; using Yubico.Core.Devices.SmartCard; using Yubico.YubiKey.Pipelines; @@ -20,14 +19,14 @@ namespace Yubico.YubiKey { - internal class Scp03CcidConnection : CcidConnection, IScp03YubiKeyConnection + internal class Scp03Connection : SmartcardConnection, IScp03YubiKeyConnection { private bool _disposed; // If an Scp03ApduTransform is used, keep this copy so it can be disposed. private readonly Scp03ApduTransform _scp03ApduTransform; - public Scp03CcidConnection( + public Scp03Connection( ISmartCardDevice smartCardDevice, YubiKeyApplication yubiKeyApplication, StaticKeys scp03Keys) @@ -36,7 +35,7 @@ public Scp03CcidConnection( _scp03ApduTransform = SetObject(yubiKeyApplication, scp03Keys); } - public Scp03CcidConnection(ISmartCardDevice smartCardDevice, byte[] applicationId, StaticKeys scp03Keys) + public Scp03Connection(ISmartCardDevice smartCardDevice, byte[] applicationId, StaticKeys scp03Keys) : base(smartCardDevice, YubiKeyApplication.Unknown, applicationId) { YubiKeyApplication setError = YubiKeyApplication.Unknown; diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Scp03/Scp03YubiKeyDevice.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Scp03/Scp03YubiKeyDevice.cs index 075891e3..b69bfec2 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Scp03/Scp03YubiKeyDevice.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Scp03/Scp03YubiKeyDevice.cs @@ -45,12 +45,12 @@ public Scp03YubiKeyDevice(YubiKeyDevice device, StaticKeys staticKeys) if (!(application is null)) { - return new Scp03CcidConnection(GetSmartCardDevice(), (YubiKeyApplication)application, StaticKeys); + return new Scp03Connection(GetSmartCardDevice(), (YubiKeyApplication)application, StaticKeys); } if (!(applicationId is null)) { - return new Scp03CcidConnection(GetSmartCardDevice(), applicationId, StaticKeys); + return new Scp03Connection(GetSmartCardDevice(), applicationId, StaticKeys); } return null; diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/SmartCardDeviceInfoFactory.cs b/Yubico.YubiKey/src/Yubico/YubiKey/SmartCardDeviceInfoFactory.cs index 0461cce9..dd6f9952 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/SmartCardDeviceInfoFactory.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/SmartCardDeviceInfoFactory.cs @@ -17,6 +17,7 @@ using Yubico.Core.Devices.SmartCard; using Yubico.Core.Logging; using Yubico.YubiKey.DeviceExtensions; +using Yubico.YubiKey.Management.Commands; namespace Yubico.YubiKey { @@ -67,67 +68,74 @@ public static YubiKeyDeviceInfo GetDeviceInfo(ISmartCardDevice device) ykDeviceInfo.FirmwareVersion = firmwareVersion; } - if (ykDeviceInfo.FirmwareVersion < FirmwareVersion.V4_0_0 && ykDeviceInfo.AvailableUsbCapabilities == YubiKeyCapabilities.None) + if (ykDeviceInfo.FirmwareVersion < FirmwareVersion.V4_0_0 && + ykDeviceInfo.AvailableUsbCapabilities == YubiKeyCapabilities.None) { - ykDeviceInfo.AvailableUsbCapabilities = YubiKeyCapabilities.Oath | YubiKeyCapabilities.OpenPgp | YubiKeyCapabilities.Piv | YubiKeyCapabilities.Ccid; + ykDeviceInfo.AvailableUsbCapabilities = YubiKeyCapabilities.Oath | YubiKeyCapabilities.OpenPgp | + YubiKeyCapabilities.Piv | YubiKeyCapabilities.Ccid; } return ykDeviceInfo; } - private static bool TryGetDeviceInfoFromManagement(ISmartCardDevice device, [MaybeNullWhen(returnValue: false)] out YubiKeyDeviceInfo yubiKeyDeviceInfo) + private static bool TryGetDeviceInfoFromManagement( + ISmartCardDevice device, + [MaybeNullWhen(returnValue: false)] out YubiKeyDeviceInfo yubiKeyDeviceInfo) { Logger log = Log.GetLogger(); try { log.LogInformation("Attempting to read device info via the management application."); - using var smartCardConnection = new CcidConnection(device, YubiKeyApplication.Management); + using var connection = new SmartcardConnection(device, YubiKeyApplication.Management); + yubiKeyDeviceInfo = DeviceInfoHelper.GetDeviceInfo(connection); - Management.Commands.GetDeviceInfoResponse response = smartCardConnection.SendCommand(new Management.Commands.GetDeviceInfoCommand()); - - if (response.Status == ResponseStatus.Success) - { - yubiKeyDeviceInfo = response.GetData(); - log.LogInformation("Successfully read device info via management application."); - return true; - } - - log.LogError("Failed to get device info from the management application: {Error} {Message}", response.StatusWord, response.StatusMessage); + log.LogInformation("Successfully read device info via management application."); + return true; } catch (Core.Iso7816.ApduException e) { - ErrorHandler(e, "An ISO 7816 application has encountered an error when trying to get device info from management."); + ErrorHandler(e, + "An ISO 7816 application has encountered an error when trying to get device info from management."); } - log.LogWarning("Failed to read device info through the management interface. This may be expected for older YubiKeys."); + log.LogWarning( + "Failed to read device info through the management interface. This may be expected for older YubiKeys."); + yubiKeyDeviceInfo = null; + return false; } private static bool TryGetFirmwareVersionFromOtp(ISmartCardDevice device, - [MaybeNullWhen(returnValue: false)] out FirmwareVersion firmwareVersion) + [MaybeNullWhen(returnValue: false)] + out FirmwareVersion firmwareVersion) { Logger log = Log.GetLogger(); try { log.LogInformation("Attempting to read firmware version through OTP."); - using var ccidConnection = new CcidConnection(device, YubiKeyApplication.Otp); + using var ccidConnection = new SmartcardConnection(device, YubiKeyApplication.Otp); - Otp.Commands.ReadStatusResponse response = ccidConnection.SendCommand(new Otp.Commands.ReadStatusCommand()); + Otp.Commands.ReadStatusResponse response = + ccidConnection.SendCommand(new Otp.Commands.ReadStatusCommand()); if (response.Status == ResponseStatus.Success) { firmwareVersion = response.GetData().FirmwareVersion; log.LogInformation("Firmware version: {Version}", firmwareVersion.ToString()); + return true; } - log.LogError("Reading firmware version via OTP failed with: {Error} {Message}", response.StatusWord, response.StatusMessage); + + log.LogError("Reading firmware version via OTP failed with: {Error} {Message}", response.StatusWord, + response.StatusMessage); } catch (Core.Iso7816.ApduException e) { - ErrorHandler(e, "An ISO 7816 application has encountered an error when trying to get firmware version from OTP."); + ErrorHandler(e, + "An ISO 7816 application has encountered an error when trying to get firmware version from OTP."); } catch (MalformedYubiKeyResponseException e) { @@ -136,18 +144,20 @@ private static bool TryGetFirmwareVersionFromOtp(ISmartCardDevice device, log.LogWarning("Failed to read firmware version through OTP. This may be expected over USB."); firmwareVersion = null; + return false; } private static bool TryGetFirmwareVersionFromPiv(ISmartCardDevice device, - [MaybeNullWhen(returnValue: false)] out FirmwareVersion firmwareVersion) + [MaybeNullWhen(returnValue: false)] + out FirmwareVersion firmwareVersion) { Logger log = Log.GetLogger(); try { log.LogInformation("Attempting to read firmware version through the PIV application."); - using IYubiKeyConnection connection = new CcidConnection(device, YubiKeyApplication.Piv); + using IYubiKeyConnection connection = new SmartcardConnection(device, YubiKeyApplication.Piv); Piv.Commands.VersionResponse response = connection.SendCommand(new Piv.Commands.VersionCommand()); @@ -155,18 +165,22 @@ private static bool TryGetFirmwareVersionFromPiv(ISmartCardDevice device, { firmwareVersion = response.GetData(); log.LogInformation("Firmware version: {Version}", firmwareVersion.ToString()); + return true; } - log.LogError("Reading firmware version via PIV failed with: {Error} {Message}", response.StatusWord, response.StatusMessage); + log.LogError("Reading firmware version via PIV failed with: {Error} {Message}", response.StatusWord, + response.StatusMessage); } catch (Core.Iso7816.ApduException e) { - ErrorHandler(e, "An ISO 7816 application has encountered an error when trying to get firmware version from PIV."); + ErrorHandler(e, + "An ISO 7816 application has encountered an error when trying to get firmware version from PIV."); } log.LogWarning("Failed to read firmware version through PIV."); firmwareVersion = null; + return false; } @@ -177,22 +191,26 @@ private static bool TryGetSerialNumberFromOtp(ISmartCardDevice device, out int? try { log.LogInformation("Attempting to read serial number through the OTP application."); - using var ccidConnection = new CcidConnection(device, YubiKeyApplication.Otp); + using var ccidConnection = new SmartcardConnection(device, YubiKeyApplication.Otp); - Otp.Commands.GetSerialNumberResponse response = ccidConnection.SendCommand(new Otp.Commands.GetSerialNumberCommand()); + Otp.Commands.GetSerialNumberResponse response = + ccidConnection.SendCommand(new Otp.Commands.GetSerialNumberCommand()); if (response.Status == ResponseStatus.Success) { serialNumber = response.GetData(); log.LogInformation("Serial number: {SerialNumber}", serialNumber); + return true; } - log.LogError("Reading serial number via OTP failed with: {Error} {Message}", response.StatusWord, response.StatusMessage); + log.LogError("Reading serial number via OTP failed with: {Error} {Message}", response.StatusWord, + response.StatusMessage); } catch (Core.Iso7816.ApduException e) { - ErrorHandler(e, "An ISO 7816 application has encountered an error when trying to get serial number from OTP."); + ErrorHandler(e, + "An ISO 7816 application has encountered an error when trying to get serial number from OTP."); } catch (MalformedYubiKeyResponseException e) { @@ -202,6 +220,7 @@ private static bool TryGetSerialNumberFromOtp(ISmartCardDevice device, out int? log.LogWarning("Failed to read serial number through OTP."); serialNumber = null; + return false; } @@ -212,30 +231,35 @@ private static bool TryGetSerialNumberFromPiv(ISmartCardDevice device, out int? try { log.LogInformation("Attempting to read serial number through the PIV application."); - using IYubiKeyConnection connection = new CcidConnection(device, YubiKeyApplication.Piv); + using IYubiKeyConnection connection = new SmartcardConnection(device, YubiKeyApplication.Piv); - Piv.Commands.GetSerialNumberResponse response = connection.SendCommand(new Piv.Commands.GetSerialNumberCommand()); + Piv.Commands.GetSerialNumberResponse response = + connection.SendCommand(new Piv.Commands.GetSerialNumberCommand()); if (response.Status == ResponseStatus.Success) { serialNumber = response.GetData(); log.LogInformation("Serial number: {SerialNumber}", serialNumber); + return true; } - log.LogError("Reading serial number via PIV failed with: {Error} {Message}", response.StatusWord, response.StatusMessage); + log.LogError("Reading serial number via PIV failed with: {Error} {Message}", response.StatusWord, + response.StatusMessage); } catch (Core.Iso7816.ApduException e) { - ErrorHandler(e, "An ISO 7816 application has encountered an error when trying to get serial number from PIV."); + ErrorHandler(e, + "An ISO 7816 application has encountered an error when trying to get serial number from PIV."); } log.LogWarning("Failed to read serial number through PIV."); serialNumber = null; + return false; } - private static void ErrorHandler(Exception exception, string message) - => Log.GetLogger().LogWarning(exception, message); + private static void ErrorHandler(Exception exception, string message) => + Log.GetLogger().LogWarning(exception, message); } } diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/CcidConnection.cs b/Yubico.YubiKey/src/Yubico/YubiKey/SmartcardConnection.cs similarity index 94% rename from Yubico.YubiKey/src/Yubico/YubiKey/CcidConnection.cs rename to Yubico.YubiKey/src/Yubico/YubiKey/SmartcardConnection.cs index b9eb6eca..a87ca8f1 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/CcidConnection.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/SmartcardConnection.cs @@ -24,7 +24,7 @@ namespace Yubico.YubiKey { - internal class CcidConnection : IYubiKeyConnection + internal class SmartcardConnection : IYubiKeyConnection { private readonly Logger _log = Log.GetLogger(); @@ -36,7 +36,7 @@ internal class CcidConnection : IYubiKeyConnection public ISelectApplicationData? SelectApplicationData { get; set; } - protected CcidConnection(ISmartCardDevice smartCardDevice, YubiKeyApplication application, byte[]? applicationId) + protected SmartcardConnection(ISmartCardDevice smartCardDevice, YubiKeyApplication application, byte[]? applicationId) { if (applicationId is null && application == YubiKeyApplication.Unknown) { @@ -53,7 +53,7 @@ protected CcidConnection(ISmartCardDevice smartCardDevice, YubiKeyApplication ap _apduPipeline = new CommandChainingTransform(_apduPipeline); } - public CcidConnection(ISmartCardDevice smartCardDevice, YubiKeyApplication yubiKeyApplication) + public SmartcardConnection(ISmartCardDevice smartCardDevice, YubiKeyApplication yubiKeyApplication) : this(smartCardDevice, yubiKeyApplication, null) { if (yubiKeyApplication == YubiKeyApplication.Fido2) @@ -67,7 +67,7 @@ public CcidConnection(ISmartCardDevice smartCardDevice, YubiKeyApplication yubiK SelectApplication(); } - public CcidConnection(ISmartCardDevice smartCardDevice, byte[] applicationId) + public SmartcardConnection(ISmartCardDevice smartCardDevice, byte[] applicationId) : this(smartCardDevice, YubiKeyApplication.Unknown, applicationId) { if (applicationId.SequenceEqual(YubiKeyApplication.Fido2.GetIso7816ApplicationId())) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetDeviceInfoCommand.cs b/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetDeviceInfoCommand.cs index bb7f2b37..64c76f77 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetDeviceInfoCommand.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetDeviceInfoCommand.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using Yubico.Core.Iso7816; namespace Yubico.YubiKey.U2f.Commands @@ -19,6 +20,10 @@ namespace Yubico.YubiKey.U2f.Commands /// /// Gets detailed information about the YubiKey and its current configuration. /// + /// + /// This class has a corresponding partner class + /// + [Obsolete("This class has been replaced by GetPagedDeviceInfoCommand")] public sealed class GetDeviceInfoCommand : IYubiKeyCommand { private const byte GetDeviceInfoInstruction = 0xC2; diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetDeviceInfoResponse.cs b/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetDeviceInfoResponse.cs index 72b4af5f..116a8314 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetDeviceInfoResponse.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetDeviceInfoResponse.cs @@ -21,6 +21,7 @@ namespace Yubico.YubiKey.U2f.Commands /// The response to the command, containing the YubiKey's /// device configuration details. /// + [Obsolete("This class has been replaced by GetPagedDeviceInfoResponse")] public sealed class GetDeviceInfoResponse : U2fResponse, IYubiKeyResponseWithData { @@ -51,7 +52,7 @@ public YubiKeyDeviceInfo GetData() if (ResponseApdu.Data.Length > 255) { - throw new MalformedYubiKeyResponseException() + throw new MalformedYubiKeyResponseException { ResponseClass = nameof(GetDeviceInfoResponse), ActualDataLength = ResponseApdu.Data.Length @@ -60,7 +61,7 @@ public YubiKeyDeviceInfo GetData() if (!YubiKeyDeviceInfo.TryCreateFromResponseData(ResponseApdu.Data, out YubiKeyDeviceInfo? deviceInfo)) { - throw new MalformedYubiKeyResponseException() + throw new MalformedYubiKeyResponseException { ResponseClass = nameof(GetDeviceInfoResponse), }; diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetPagedDeviceInfoCommand.cs b/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetPagedDeviceInfoCommand.cs new file mode 100644 index 00000000..7169eeb7 --- /dev/null +++ b/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetPagedDeviceInfoCommand.cs @@ -0,0 +1,54 @@ +// Copyright 2021 Yubico AB +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Yubico.Core.Iso7816; + +namespace Yubico.YubiKey.U2f.Commands +{ + /// + /// Gets detailed information about the YubiKey and its current configuration. + /// + public sealed class GetPagedDeviceInfoCommand : IGetPagedDeviceInfoCommand + { + private const byte GetDeviceInfoInstruction = 0xC2; + public byte Page { get; set; } + + /// + /// Gets the YubiKeyApplication to which this command belongs. + /// + /// + /// YubiKeyApplication.FidoU2f + /// + public YubiKeyApplication Application => YubiKeyApplication.FidoU2f; + + /// + /// Constructs an instance of the class. + /// + public GetPagedDeviceInfoCommand() + { + + } + + /// + public CommandApdu CreateCommandApdu() => new CommandApdu + { + Ins = GetDeviceInfoInstruction, + Data = new []{ Page } + }; + + /// + public GetPagedDeviceInfoResponse CreateResponseForApdu(ResponseApdu responseApdu) => + new GetPagedDeviceInfoResponse(responseApdu); + } +} diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetPagedDeviceInfoResponse.cs b/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetPagedDeviceInfoResponse.cs new file mode 100644 index 00000000..435aed5b --- /dev/null +++ b/Yubico.YubiKey/src/Yubico/YubiKey/U2f/Commands/GetPagedDeviceInfoResponse.cs @@ -0,0 +1,72 @@ +// Copyright 2021 Yubico AB +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using Yubico.Core.Iso7816; + +namespace Yubico.YubiKey.U2f.Commands +{ + /// + /// The response to the command, containing the YubiKey's + /// device configuration details. + /// + /// + public class GetPagedDeviceInfoResponse : YubiKeyResponse, IYubiKeyResponseWithData>> + { + /// + /// Constructs a GetPagedDeviceInfoResponse instance based on a ResponseApdu received from the YubiKey. + /// + /// + /// The ResponseApdu returned by the YubiKey. + /// + public GetPagedDeviceInfoResponse(ResponseApdu responseApdu) : + base(responseApdu) + { + } + + /// + /// Retrieves and converts the response data from an APDU response into a dictionary of TLV tags and their corresponding values. + /// + /// A dictionary mapping integer tags to their corresponding values as byte arrays. + /// Thrown when the response status is not successful. + /// Thrown when the APDU data length exceeds expected bounds or if the data conversion fails. + public Dictionary> GetData() + { + if (Status != ResponseStatus.Success) + { + throw new InvalidOperationException(StatusMessage); + } + + if (ResponseApdu.Data.Length > 255) + { + throw new MalformedYubiKeyResponseException + { + ResponseClass = nameof(GetPagedDeviceInfoResponse), + ActualDataLength = ResponseApdu.Data.Length + }; + } + + if (!DeviceInfoHelper.TryCreateApduDictionaryFromResponseData(ResponseApdu.Data, out Dictionary> result)) + { + throw new MalformedYubiKeyResponseException + { + ResponseClass = nameof(GetPagedDeviceInfoResponse), + }; + } + + return result; + } + } +} diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDevice.Instance.cs b/Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDevice.Instance.cs index 207d3549..77f19c0a 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDevice.Instance.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDevice.Instance.cs @@ -41,6 +41,9 @@ public partial class YubiKeyDevice : IYubiKeyDevice /// public YubiKeyCapabilities EnabledNfcCapabilities => _yubiKeyInfo.EnabledNfcCapabilities; + /// + public bool IsNfcRestricted => _yubiKeyInfo.IsNfcRestricted; + /// public int? SerialNumber => _yubiKeyInfo.SerialNumber; @@ -378,14 +381,14 @@ private bool TryConnectScp03( _log.LogInformation("Connecting via the SmartCard interface."); WaitForReclaimTimeout(Transport.SmartCard); return scp03Keys is null ? - new CcidConnection(_smartCardDevice, applicationId) - : new Scp03CcidConnection(_smartCardDevice, applicationId, scp03Keys); + new SmartcardConnection(_smartCardDevice, applicationId) + : new Scp03Connection(_smartCardDevice, applicationId, scp03Keys); } _log.LogInformation( (applicationId is null ? "No application given." : "No smart card interface present.") + "Unable to establish connection to YubiKey."); - + return null; } @@ -395,7 +398,7 @@ private bool TryConnectScp03( { _log.LogInformation("Connecting via the SmartCard interface."); WaitForReclaimTimeout(Transport.SmartCard); - return new Scp03CcidConnection(_smartCardDevice, (YubiKeyApplication)application, scp03Keys); + return new Scp03Connection(_smartCardDevice, (YubiKeyApplication)application, scp03Keys); } return null; @@ -424,7 +427,7 @@ private bool TryConnectScp03( { _log.LogInformation("Connecting via the SmartCard interface."); WaitForReclaimTimeout(Transport.SmartCard); - return new CcidConnection(_smartCardDevice, (YubiKeyApplication)application); + return new SmartcardConnection(_smartCardDevice, (YubiKeyApplication)application); } _log.LogInformation("No smart card interface present. Unable to establish connection to YubiKey."); @@ -512,6 +515,22 @@ public void SetAutoEjectTimeout(int seconds) } } + //TODO make documentation + public void SetIsNfcRestricted(bool enabled) + { + var setCommand = new MgmtCmd.SetDeviceInfoCommand + { + IsNfcRestricted = enabled + }; + + IYubiKeyResponse setConfigurationResponse = SendConfiguration(setCommand); + + if (setConfigurationResponse.Status != ResponseStatus.Success) + { + throw new InvalidOperationException(setConfigurationResponse.StatusMessage); + } + } + /// public void SetDeviceFlags(DeviceFlags deviceFlags) { @@ -676,7 +695,7 @@ private IYubiKeyResponse SendConfiguration(MgmtCmd.SetDeviceInfoBaseCommand base { IYubiKeyConnection? connection = null; try - { + { IYubiKeyCommand command; if (TryConnect(YubiKeyApplication.Management, out connection)) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDevice.Static.cs b/Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDevice.Static.cs index 7a0db8d3..185f606d 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDevice.Static.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDevice.Static.cs @@ -75,6 +75,7 @@ public static IEnumerable FindByTransport(Transport transport = Logger log = Log.GetLogger(); log.LogInformation("FindByTransport {Transport}", transport); + if (transport == Transport.None) { throw new ArgumentException(ExceptionMessages.InvalidConnectionTypeNone, nameof(transport)); @@ -83,18 +84,14 @@ public static IEnumerable FindByTransport(Transport transport = // If the caller is looking only for HidFido, and this is Windows, // and the process is not running elevated, we can't use the YubiKey, // so throw an exception. - if (transport == Transport.HidFido) + if (transport == Transport.HidFido && + SdkPlatformInfo.OperatingSystem == SdkPlatform.Windows && + !SdkPlatformInfo.IsElevated) { - if (SdkPlatformInfo.OperatingSystem == SdkPlatform.Windows) - { - if (!SdkPlatformInfo.IsElevated) - { - throw new UnauthorizedAccessException( - string.Format( - CultureInfo.CurrentCulture, - ExceptionMessages.HidFidoWindowsNotElevated)); - } - } + throw new UnauthorizedAccessException( + string.Format( + CultureInfo.CurrentCulture, + ExceptionMessages.HidFidoWindowsNotElevated)); } // Return any key that has at least one overlapping available transport with the requested transports. diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDeviceInfo.cs b/Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDeviceInfo.cs index 27c1a0c0..32967de7 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDeviceInfo.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/YubiKeyDeviceInfo.cs @@ -14,6 +14,7 @@ using System; using System.Buffers.Binary; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Yubico.Core.Logging; @@ -39,6 +40,12 @@ public class YubiKeyDeviceInfo : IYubiKeyDeviceInfo private const byte NfcPrePersCapabilitiesTag = 0x0d; private const byte NfcEnabledCapabilitiesTag = 0x0e; private const byte MoreDataTag = 0x10; + // private const byte PartNumberTag = 0x13; + // private const byte FipsCapableTag = 0x14; + // private const byte FipsApprovedTag = 0x15; + // private const byte PinComplexityTag = 0x16; + private const byte NfcRestrictedTag = 0x17; + // private const byte ResetBlockedTag = 0x18; private const byte TemplateStorageVersionTag = 0x20; private const byte ImageProcessorVersionTag = 0x21; @@ -102,6 +109,8 @@ public class YubiKeyDeviceInfo : IYubiKeyDeviceInfo /// public bool ConfigurationLocked { get; set; } + + public bool IsNfcRestricted { get; set; } /// /// Constructs a default instance of YubiKeyDeviceInfo. @@ -264,6 +273,10 @@ internal static bool TryCreateFromResponseData( }; log.SensitiveLogInformation("ImageProcessorVersion: {ImageProcessorVersion}", deviceInfo.ImageProcessorVersion.ToString()); break; + + case NfcRestrictedTag: + deviceInfo.IsNfcRestricted = tlvReader.ReadByte(NfcRestrictedTag) == 1; + break; default: Debug.Assert(false, "Encountered an unrecognized tag in DeviceInfo. Ignoring."); @@ -281,6 +294,125 @@ internal static bool TryCreateFromResponseData( return true; } + /// + /// Gets the YubiKey's device configuration details. + /// + /// + /// The ResponseApdu data as byte array returned by the YubiKey. The first byte + /// is the overall length of the TLV data, followed by the TLV data. + /// + /// + /// On success, the is returned using this out parameter. + /// did not meet formatting requirements. + /// + internal static YubiKeyDeviceInfo CreateFromResponseData( + Dictionary> responseApduData) + { + bool fipsSeriesFlag = false; + bool skySeriesFlag = false; + + var deviceInfo = new YubiKeyDeviceInfo(); + foreach (KeyValuePair> tagValuePair in responseApduData) + { + switch (tagValuePair.Key) + { + case UsbPrePersCapabilitiesTag: + deviceInfo.AvailableUsbCapabilities = GetYubiKeyCapabilities(tagValuePair.Value.Span); + break; + + case SerialNumberTag: + deviceInfo.SerialNumber = BinaryPrimitives.ReadInt32BigEndian(tagValuePair.Value.Span); + break; + + case UsbEnabledCapabilitiesTag: + deviceInfo.EnabledUsbCapabilities = GetYubiKeyCapabilities(tagValuePair.Value.Span); + break; + + case FormFactorTag: + byte formFactorValue = tagValuePair.Value.Span[0]; + deviceInfo.FormFactor = (FormFactor)(formFactorValue & FormFactorMask); + fipsSeriesFlag = (formFactorValue & FipsMask) == FipsMask; + skySeriesFlag = (formFactorValue & SkyMask) == SkyMask; + break; + + case FirmwareVersionTag: + ReadOnlySpan firmwareValue = tagValuePair.Value.Span; + deviceInfo.FirmwareVersion = new FirmwareVersion + { + Major = firmwareValue[0], + Minor = firmwareValue[1], + Patch = firmwareValue[2] + }; + break; + + case AutoEjectTimeoutTag: + deviceInfo.AutoEjectTimeout = BinaryPrimitives.ReadUInt16BigEndian(tagValuePair.Value.Span); + break; + + case ChallengeResponseTimeoutTag: + deviceInfo.ChallengeResponseTimeout = tagValuePair.Value.Span[0]; + break; + + case DeviceFlagsTag: + deviceInfo.DeviceFlags = (DeviceFlags)tagValuePair.Value.Span[0]; + break; + + case ConfigurationLockPresentTag: + deviceInfo.ConfigurationLocked = tagValuePair.Value.Span[0] == 1; + break; + + case NfcPrePersCapabilitiesTag: + deviceInfo.AvailableNfcCapabilities = GetYubiKeyCapabilities(tagValuePair.Value.Span); + break; + + case NfcEnabledCapabilitiesTag: + deviceInfo.EnabledNfcCapabilities = GetYubiKeyCapabilities(tagValuePair.Value.Span); + break; + + case TemplateStorageVersionTag: + ReadOnlySpan fpChipVersion = tagValuePair.Value.Span; + deviceInfo.TemplateStorageVersion = new TemplateStorageVersion + { + Major = fpChipVersion[0], + Minor = fpChipVersion[1], + Patch = fpChipVersion[2] + }; + break; + + case ImageProcessorVersionTag: + ReadOnlySpan ipChipVersion = tagValuePair.Value.Span; + deviceInfo.ImageProcessorVersion = new ImageProcessorVersion + { + Major = ipChipVersion[0], + Minor = ipChipVersion[1], + Patch = ipChipVersion[2] + }; + break; + + case NfcRestrictedTag: + deviceInfo.IsNfcRestricted = tagValuePair.Value.Span[0] == 1; + break; + case IapDetectionTag: + case MoreDataTag: + // Ignore these tags for now + break; + + //Todo add more cases, needs run test + default: + Debug.Assert(false, "Encountered an unrecognized tag in DeviceInfo. Ignoring."); + break; + } + } + + deviceInfo.IsFipsSeries = deviceInfo.FirmwareVersion >= _fipsFlagInclusiveLowerBound + ? fipsSeriesFlag + : deviceInfo.IsFipsVersion; + + deviceInfo.IsSkySeries |= skySeriesFlag; + + return deviceInfo; + } + internal YubiKeyDeviceInfo Merge(YubiKeyDeviceInfo? second) { second ??= new YubiKeyDeviceInfo(); @@ -299,34 +431,31 @@ internal YubiKeyDeviceInfo Merge(YubiKeyDeviceInfo? second) IsFipsSeries = IsFipsSeries || second.IsFipsSeries, - FormFactor = - FormFactor != FormFactor.Unknown - ? FormFactor - : second.FormFactor, + FormFactor = FormFactor != FormFactor.Unknown + ? FormFactor + : second.FormFactor, - FirmwareVersion = - FirmwareVersion != new FirmwareVersion() - ? FirmwareVersion - : second.FirmwareVersion, + FirmwareVersion = FirmwareVersion != new FirmwareVersion() + ? FirmwareVersion + : second.FirmwareVersion, - AutoEjectTimeout = - DeviceFlags.HasFlag(DeviceFlags.TouchEject) + AutoEjectTimeout = DeviceFlags.HasFlag(DeviceFlags.TouchEject) ? AutoEjectTimeout : second.DeviceFlags.HasFlag(DeviceFlags.TouchEject) ? second.AutoEjectTimeout : default, - ChallengeResponseTimeout = - ChallengeResponseTimeout != default - ? ChallengeResponseTimeout - : second.ChallengeResponseTimeout, + ChallengeResponseTimeout = ChallengeResponseTimeout != default + ? ChallengeResponseTimeout + : second.ChallengeResponseTimeout, DeviceFlags = DeviceFlags | second.DeviceFlags, - ConfigurationLocked = - ConfigurationLocked != default - ? ConfigurationLocked - : second.ConfigurationLocked, + ConfigurationLocked = ConfigurationLocked != default + ? ConfigurationLocked + : second.ConfigurationLocked, + + IsNfcRestricted = IsNfcRestricted || second.IsNfcRestricted }; } diff --git a/Yubico.YubiKey/tests/sandbox/Plugins/GregPlugin.cs b/Yubico.YubiKey/tests/sandbox/Plugins/GregPlugin.cs index 999ac5de..a3107bd1 100644 --- a/Yubico.YubiKey/tests/sandbox/Plugins/GregPlugin.cs +++ b/Yubico.YubiKey/tests/sandbox/Plugins/GregPlugin.cs @@ -14,14 +14,8 @@ using System; using System.Linq; -using System.Text; -using System.Threading; using Microsoft.Extensions.Logging; using Serilog; -using Yubico.Core.Logging; -using Yubico.YubiKey.Fido2; -using Yubico.YubiKey.YubiHsmAuth; -using Yubico.YubiKey.YubiHsmAuth.Commands; namespace Yubico.YubiKey.TestApp.Plugins { @@ -44,26 +38,21 @@ public override bool Execute() builder => builder .AddSerilog(log) .AddFilter(level => level >= LogLevel.Information)); + + + + + + IYubiKeyDevice? yubiKey = YubiKeyDevice.FindByTransport(Transport.All).First(); - IYubiKeyDevice? yubiKey = YubiKeyDevice.FindAll().First(); + // IYubiKeyDevice? yubiKey = YubiKeyDevice.FindByTransport(Transport.HidFido).First(); - Console.WriteLine($"YubiKey Version: {yubiKey.FirmwareVersion}"); - - using (var hsmAuth = new YubiHsmAuthSession(yubiKey)) - { - string label = "mycred"; - byte[] password = new byte[16]; - Encoding.ASCII.GetBytes("abc123").CopyTo(password, 0); - byte[] hostChallenge = { 0, 1, 2, 3, 4, 5, 6, 7 }; - byte[] hsmDeviceChallenge = { 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }; - - var command = new GetAes128SessionKeysCommand(label, password, hostChallenge, hsmDeviceChallenge); - - Console.WriteLine("Calling calculate..."); - GetAes128SessionKeysResponse? response = hsmAuth.Connection.SendCommand(command); - Console.WriteLine($"Calculate returned with {response.Status}"); - } + Console.Error.WriteLine($"YubiKey Version: {yubiKey.FirmwareVersion}"); + Console.Error.WriteLine("NFC Before Value: "+ yubiKey.IsNfcRestricted); + yubiKey.SetIsNfcRestricted(true); + + Console.Error.WriteLine("NFC AFter Value: "+ yubiKey.IsNfcRestricted); return true; } } diff --git a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/HollowYubiKeyDevice.cs b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/HollowYubiKeyDevice.cs index d2a85485..bcfd4944 100644 --- a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/HollowYubiKeyDevice.cs +++ b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/HollowYubiKeyDevice.cs @@ -50,6 +50,8 @@ public sealed class HollowYubiKeyDevice : IYubiKeyDevice /// public YubiKeyCapabilities EnabledNfcCapabilities { get; private set; } + public bool IsNfcRestricted { get; } + /// public int? SerialNumber { get; private set; } @@ -237,5 +239,10 @@ public void SetLegacyDeviceConfiguration(YubiKeyCapabilities yubiKeyInterfaces, { throw new NotImplementedException(); } + + public void SetIsNfcRestricted(bool enabled) + { + throw new NotImplementedException(); + } } }