diff --git a/.editorconfig b/.editorconfig index 33ca8339e..4650be58f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -21,6 +21,10 @@ root = true dotnet_diagnostic.ide0073.severity = suggestion file_header_template = Copyright 2024 Yubico AB\n\nLicensed under the Apache License, Version 2.0 (the "License").\nYou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an "AS IS" BASIS, \nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License. +# ReSharper properties +resharper_csharp_wrap_after_declaration_lpar = true +resharper_csharp_wrap_parameters_style = chop_always + # C# files [*.cs] @@ -357,4 +361,4 @@ dotnet_diagnostic.ca1014.severity = none # var preferences csharp_style_var_elsewhere = false:warning csharp_style_var_for_built_in_types = false:warning -csharp_style_var_when_type_is_apparent = true:warning \ No newline at end of file +csharp_style_var_when_type_is_apparent = true:warning diff --git a/Yubico.Core/src/Yubico/Core/Devices/SmartCard/DesktopSmartCardConnection.cs b/Yubico.Core/src/Yubico/Core/Devices/SmartCard/DesktopSmartCardConnection.cs index cfd30ac68..28e507a8a 100644 --- a/Yubico.Core/src/Yubico/Core/Devices/SmartCard/DesktopSmartCardConnection.cs +++ b/Yubico.Core/src/Yubico/Core/Devices/SmartCard/DesktopSmartCardConnection.cs @@ -104,7 +104,7 @@ public IDisposable BeginTransaction(out bool cardWasReset) uint result = SCardBeginTransaction(_cardHandle); _log.SCardApiCall(nameof(SCardBeginTransaction), result); - // Sometime the smart card is left in a state where it needs to be reset prior to beginning + // Sometimes the smart card is left in a state where it needs to be reset prior to beginning // a transaction. We should automatically handle this case. if (result == ErrorCode.SCARD_W_RESET_CARD) { diff --git a/Yubico.Core/src/Yubico/Core/Tlv/TlvObject.cs b/Yubico.Core/src/Yubico/Core/Tlv/TlvObject.cs new file mode 100644 index 000000000..08f703258 --- /dev/null +++ b/Yubico.Core/src/Yubico/Core/Tlv/TlvObject.cs @@ -0,0 +1,198 @@ +// Copyright 2024 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.IO; +using System.Linq; + +namespace Yubico.Core.Tlv +{ + /// + /// Tag, length, Value structure that helps to parse APDU response data. + /// This class handles BER-TLV encoded data with determinate length. + /// + public class TlvObject + { + /// + /// Returns the tag. + /// + public int Tag { get; } + + /// + /// Returns the value. + /// + public Memory Value => _bytes.Skip(_offset).Take(Length).ToArray(); + + /// + /// Returns the length of the value. + /// + public int Length { get; } + + private readonly byte[] _bytes; + private readonly int _offset; + + /// + /// Creates a new TLV (Tag-Length-Value) object with the specified tag and value. + /// + /// The tag value, must be between 0x00 and 0xFFFF. + /// The value data as a read-only span of bytes. + /// Thrown when the tag value is outside the supported range (0x00-0xFFFF). + /// + /// This constructor creates a BER-TLV encoded structure where: + /// - The tag is encoded in the minimum number of bytes needed + /// - The length is encoded according to BER-TLV rules + /// - The value is stored as provided + /// + public TlvObject(int tag, ReadOnlySpan value) + { + // Validate that the tag is within the supported range (0x00-0xFFFF) + if (tag < 0 || tag > 0xFFFF) + { + throw new TlvException(ExceptionMessages.TlvUnsupportedTag); + } + + Tag = tag; + // Create a copy of the input value + byte[] valueBuffer = value.ToArray(); + using var ms = new MemoryStream(); + + // Convert tag to bytes and remove leading zeros, maintaining BER-TLV format + byte[] tagBytes = BitConverter.GetBytes(tag).Reverse().SkipWhile(b => b == 0).ToArray(); + ms.Write(tagBytes, 0, tagBytes.Length); + + Length = valueBuffer.Length; + // For lengths < 128 (0x80), use single byte encoding + if (Length < 0x80) + { + ms.WriteByte((byte)Length); + } + // For lengths >= 128, use multi-byte encoding with length indicator + else + { + // Convert length to bytes and remove leading zeros + byte[] lnBytes = BitConverter.GetBytes(Length).Reverse().SkipWhile(b => b == 0).ToArray(); + // Write number of length bytes with high bit set (0x80 | number of bytes) + ms.WriteByte((byte)(0x80 | lnBytes.Length)); + // Write the actual length bytes + ms.Write(lnBytes, 0, lnBytes.Length); + } + + // Store the position where the value begins + _offset = (int)ms.Position; + + // Write the value bytes + ms.Write(valueBuffer, 0, Length); + + // Store the complete TLV encoding + _bytes = ms.ToArray(); + } + + /// + /// Returns a copy ofthe Tlv as a BER-TLV encoded byte array. + /// + public Memory GetBytes() => _bytes.ToArray(); + + /// + /// Parse a Tlv from a BER-TLV encoded byte array. + /// + /// A byte array containing the TLV encoded data (and nothing more). + /// The parsed Tlv + public static TlvObject Parse(ReadOnlySpan data) + { + ReadOnlySpan buffer = data; + return ParseFrom(ref buffer); + } + + /// + /// Parses a TLV from a BER-TLV encoded byte array. + /// + /// A byte array containing the TLV encoded data. + /// The parsed + /// + /// This method will parse a TLV from the given buffer and return the parsed Tlv. + /// The method will consume the buffer as much as necessary to parse the TLV. + /// + /// Thrown if the buffer does not contain a valid TLV. + internal static TlvObject ParseFrom(ref ReadOnlySpan buffer) + { + // The first byte of the TLV is the tag. + int tag = buffer[0]; + + // Determine if the tag is in long form. + // Long form tags have more than one byte, starting with 0x1F. + if ((buffer[0] & 0x1F) == 0x1F) + { + // Ensure there is enough data for a long form tag. + if (buffer.Length < 2) + { + throw new ArgumentException("Insufficient data for long form tag"); + } + // Combine the first two bytes to form the tag. + tag = (buffer[0] << 8) | buffer[1]; + buffer = buffer[2..]; // Skip the tag bytes + } + else + { + buffer = buffer[1..]; // Skip the tag byte + } + + if (buffer.Length < 1) + { + throw new ArgumentException("Insufficient data for length"); + } + + // Read the length of the TLV value. + int length = buffer[0]; + buffer = buffer[1..]; + + // If the length is more than one byte, process remaining bytes. + if (length > 0x80) + { + int lengthLn = length - 0x80; + length = 0; + for (int i = 0; i < lengthLn; i++) + { + length = (length << 8) | buffer[0]; + buffer = buffer[1..]; + } + } + + ReadOnlySpan value = buffer[..length]; + buffer = buffer[length..]; // Advance the buffer to the end of the value + + return new TlvObject(tag, value); + } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + /// + /// The string is of the form Tlv(0xTAG, LENGTH, VALUE). + /// + /// The tag is written out in hexadecimal, prefixed by 0x. + /// The length is written out in decimal. + /// The value is written out in hexadecimal. + /// + /// + public override string ToString() + { +#if NETSTANDARD2_1_OR_GREATER + return $"Tlv(0x{Tag:X}, {Length}, {BitConverter.ToString(Value.ToArray()).Replace("-", "", StringComparison.Ordinal)})"; +#else + return $"Tlv(0x{Tag:X}, {Length}, {BitConverter.ToString(Value.ToArray()).Replace("-", "")})"; +#endif + } + } +} diff --git a/Yubico.Core/src/Yubico/Core/Tlv/TlvObjects.cs b/Yubico.Core/src/Yubico/Core/Tlv/TlvObjects.cs new file mode 100644 index 000000000..aa75d1d23 --- /dev/null +++ b/Yubico.Core/src/Yubico/Core/Tlv/TlvObjects.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Yubico.Core.Tlv +{ + /// + /// Utility methods to encode and decode BER-TLV data. + /// + public static class TlvObjects + { + /// + /// Decodes a sequence of BER-TLV encoded data into a list of Tlvs. + /// + /// Sequence of TLV encoded data + /// List of + public static IReadOnlyList DecodeList(ReadOnlySpan data) + { + var tlvs = new List(); + ReadOnlySpan buffer = data; + while (!buffer.IsEmpty) + { + var tlv = TlvObject.ParseFrom(ref buffer); + tlvs.Add(tlv); + } + + return tlvs.AsReadOnly(); + } + + /// + /// Decodes a sequence of BER-TLV encoded data into a mapping of Tag-Value pairs. + /// Iteration order is preserved. If the same tag occurs more than once only the latest will be kept. + /// + /// Sequence of TLV encoded data + /// Dictionary of Tag-Value pairs + public static IReadOnlyDictionary> DecodeMap(ReadOnlySpan data) + { + var tlvs = new Dictionary>(); + ReadOnlySpan buffer = data; + while (!buffer.IsEmpty) + { + var tlv = TlvObject.ParseFrom(ref buffer); + tlvs[tlv.Tag] = tlv.Value; + } + + return tlvs; + } + + /// + /// Encodes a list of Tlvs into a sequence of BER-TLV encoded data. + /// + /// List of Tlvs to encode + /// BER-TLV encoded list + public static byte[] EncodeList(IEnumerable list) + { + if (list is null) + { + throw new ArgumentNullException(nameof(list)); + } + + using var stream = new MemoryStream(); + using var writer = new BinaryWriter(stream); + foreach (TlvObject? tlv in list) + { + ReadOnlyMemory bytes = tlv.GetBytes(); + writer.Write(bytes.Span.ToArray()); + } + + return stream.ToArray(); + } + + /// + /// Encodes an array of Tlvs into a sequence of BER-TLV encoded data. + /// + /// Array of Tlvs to encode + /// BER-TLV encoded array + public static byte[] EncodeMany(params TlvObject[] tlvs) => EncodeList(tlvs); + + /// + /// Decode a single TLV encoded object, returning only the value. + /// + /// The expected tag value of the given TLV data + /// The TLV data + /// The value of the TLV + /// If the TLV tag differs from expectedTag + public static Memory UnpackValue(int expectedTag, ReadOnlySpan tlvData) + { + var tlv = TlvObject.Parse(tlvData); + if (tlv.Tag != expectedTag) + { + throw new InvalidOperationException($"Expected tag: {expectedTag:X2}, got {tlv.Tag:X2}"); + } + + return tlv.Value.ToArray(); + } + } +} diff --git a/Yubico.Core/tests/Yubico/Core/Tlv/TlvObjectTests.cs b/Yubico.Core/tests/Yubico/Core/Tlv/TlvObjectTests.cs new file mode 100644 index 000000000..3f6325405 --- /dev/null +++ b/Yubico.Core/tests/Yubico/Core/Tlv/TlvObjectTests.cs @@ -0,0 +1,164 @@ +// Copyright 2024 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 System.Linq; +using Xunit; + +namespace Yubico.Core.Tlv.UnitTests +{ + public class TlvObjectTests + { + [Fact] + public void TestDoubleByteTags() + { + var tlv = TlvObject.Parse(new byte[] { 0x7F, 0x49, 0 }); + Assert.Equal(0x7F49, tlv.Tag); + Assert.Equal(0, tlv.Length); + + tlv = TlvObject.Parse(new byte[] { 0x80, 0 }); + Assert.Equal(0x80, tlv.Tag); + Assert.Equal(0, tlv.Length); + + tlv = new TlvObject(0x7F49, null); + Assert.Equal(0x7F49, tlv.Tag); + Assert.Equal(0, tlv.Length); + Assert.Equal(new byte[] { 0x7F, 0x49, 0 }, tlv.GetBytes()); + + tlv = new TlvObject(0x80, null); + Assert.Equal(0x80, tlv.Tag); + Assert.Equal(0, tlv.Length); + Assert.Equal(new byte[] { 0x80, 0 }, tlv.GetBytes()); + } + + + [Fact] + public void TlvObject_Encode_ReturnsCorrectBytes() + { + // Arrange + int tag = 0x1234; + byte[] value = { 0x01, 0x02, 0x03 }; + TlvObject tlv = new TlvObject(tag, value); + + // Act + byte[] encodedBytes = tlv.GetBytes().ToArray(); + + // Assert + byte[] expectedBytes = { 0x12, 0x34, 0x03, 0x01, 0x02, 0x03 }; + Assert.True(encodedBytes.SequenceEqual(expectedBytes)); + } + + [Fact] + public void TestUnwrap() + { + TlvObjects.UnpackValue(0x80, new byte[] { 0x80, 0 }); + + TlvObjects.UnpackValue(0x7F49, new byte[] { 0x7F, 0x49, 0 }); + + var value = TlvObjects.UnpackValue(0x7F49, new byte[] { 0x7F, 0x49, 3, 1, 2, 3 }); + Assert.Equal(new byte[] { 1, 2, 3 }, value); + } + + [Fact] + public void TestUnwrapThrowsException() + { + Assert.Throws(() => TlvObjects.UnpackValue(0x7F48, new byte[] { 0x7F, 0x49, 0 })); + } + + [Fact] + public void DecodeList_ValidInput_ReturnsCorrectTlvs() + { + var input = new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x02, 0xAA, 0xBB }; + var result = TlvObjects.DecodeList(input); + + Assert.Equal(2, result.Count); + Assert.Equal(0x01, result[0].Tag); + Assert.Equal(new byte[] { 0xFF }, result[0].Value.ToArray()); + Assert.Equal(0x02, result[1].Tag); + Assert.Equal(new byte[] { 0xAA, 0xBB }, result[1].Value.ToArray()); + } + + [Fact] + public void DecodeList_EmptyInput_ReturnsEmptyList() + { + var result = TlvObjects.DecodeList(Array.Empty()); + Assert.Empty(result); + } + + [Fact] + public void DecodeMap_ValidInput_ReturnsCorrectDictionary() + { + var input = new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x02, 0xAA, 0xBB }; + var result = TlvObjects.DecodeMap(input); + + Assert.Equal(2, result.Count); + Assert.Equal(new byte[] { 0xFF }, result[0x01].ToArray()); + Assert.Equal(new byte[] { 0xAA, 0xBB }, result[0x02].ToArray()); + } + + [Fact] + public void DecodeMap_DuplicateTags_KeepsLastValue() + { + var input = new byte[] { 0x01, 0x01, 0xFF, 0x01, 0x01, 0xEE }; + var result = TlvObjects.DecodeMap(input); + + Assert.Single(result); + Assert.Equal(new byte[] { 0xEE }, result[0x01].ToArray()); + } + + [Fact] + public void EncodeList_ValidInput_ReturnsCorrectBytes() + { + var tlvs = new List + { + new TlvObject(0x01, new byte[] { 0xFF }), + new TlvObject(0x02, new byte[] { 0xAA, 0xBB }) + }; + + var result = TlvObjects.EncodeList(tlvs); + Assert.Equal(new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x02, 0xAA, 0xBB }, result.ToArray()); + } + + [Fact] + public void EncodeList_EmptyInput_ReturnsEmptyArray() + { + var result = TlvObjects.EncodeList(new List()); + Assert.Empty(result.ToArray()); + } + + [Fact] + public void UnpackValue_CorrectTag_ReturnsValue() + { + var input = new byte[] { 0x01, 0x02, 0xAA, 0xBB }; + var result = TlvObjects.UnpackValue(0x01, input); + Assert.Equal(new byte[] { 0xAA, 0xBB }, result.ToArray()); + } + + [Fact] + public void UnpackValue_IncorrectTag_ThrowsBadResponseException() + { + var input = new byte[] { 0x01, 0x02, 0xAA, 0xBB }; + Assert.Throws(() => TlvObjects.UnpackValue(0x02, input)); + } + + [Fact] + public void UnpackValue_EmptyValue_ReturnsEmptyArray() + { + var input = new byte[] { 0x01, 0x00 }; + var result = TlvObjects.UnpackValue(0x01, input); + Assert.Empty(result.ToArray()); + } + } +} diff --git a/Yubico.YubiKey/docs/users-manual/sdk-programming-guide/secure-channel-protocol-3.md b/Yubico.YubiKey/docs/users-manual/sdk-programming-guide/secure-channel-protocol-3.md index d1e28e050..91ab0880f 100644 --- a/Yubico.YubiKey/docs/users-manual/sdk-programming-guide/secure-channel-protocol-3.md +++ b/Yubico.YubiKey/docs/users-manual/sdk-programming-guide/secure-channel-protocol-3.md @@ -32,7 +32,7 @@ as "SCP03"). This standard prescribes methods to encrypt and authenticate smart (CCID) messages. That is, APDUs and responses are encrypted and contain checksums. If executed properly, the only entities that can see the contents of the messages (and verify their correctness) will be the YubiKey itself and authorized applications. This -protocol is produced by GlobalPlatform, an industry consortium of hardware security +protocol is produced by GlobalPlatform, an industry consortium of hardware security vendors that produce standards. Think of SCP03 as wrapping or unwrapping commands and responses. Before sending the actual @@ -51,7 +51,7 @@ specifies no method for distributing keys securely. This added layer of protection makes the most sense when the communication channel between the host machine and the device could feasibly be compromised. -For example, if you tunnel YubiKey commands and resonses over the Internet, in +For example, if you tunnel YubiKey commands and responses over the Internet, in addition to standard web security protocols like TLS, it could makes sense to leverage SCP03 as an added layer of defense. Additionally, several 'card management systems' use SCP03 to securely remotely manage devices. diff --git a/Yubico.YubiKey/src/Resources/ExceptionMessages.Designer.cs b/Yubico.YubiKey/src/Resources/ExceptionMessages.Designer.cs index cdc9dd487..aa7863731 100644 --- a/Yubico.YubiKey/src/Resources/ExceptionMessages.Designer.cs +++ b/Yubico.YubiKey/src/Resources/ExceptionMessages.Designer.cs @@ -1283,6 +1283,15 @@ internal static string InvalidVerifyUvArguments { } } + /// + /// Looks up a localized string similar to Key agreement receipts do not match. + /// + internal static string KeyAgreementReceiptMissmatch { + get { + return ResourceManager.GetString("KeyAgreementReceiptMissmatch", resourceCulture); + } + } + /// /// Looks up a localized string similar to The keyboard connection does not support writing more than 64 bytes to the device.. /// @@ -1913,15 +1922,6 @@ internal static string PinTooShort { } } - /// - /// Looks up a localized string similar to Exception caught when disposing PivSession: {0}, {1}. - /// - internal static string PivSessionDisposeUnknownError { - get { - return ResourceManager.GetString("PivSessionDisposeUnknownError", resourceCulture); - } - } - /// /// Looks up a localized string similar to The private ID must be set either explicitly or by specifying that it should be generated before it can be read.. /// @@ -2003,6 +2003,15 @@ internal static string Scp03KeyMismatch { } } + /// + /// Looks up a localized string similar to Exception caught when disposing Session: {0}, {1}. + /// + internal static string SessionDisposeUnknownError { + get { + return ResourceManager.GetString("SessionDisposeUnknownError", resourceCulture); + } + } + /// /// Looks up a localized string similar to The HMAC-SHA512 algorithm is not supported on YubiKeys with firmware version less than 4.3.1.. /// @@ -2175,11 +2184,11 @@ internal static string UnknownFidoError { } /// - /// Looks up a localized string similar to An unknown SCP03 error has occurred.. + /// Looks up a localized string similar to An unknown SCP error has occurred. /// - internal static string UnknownScp03Error { + internal static string UnknownScpError { get { - return ResourceManager.GetString("UnknownScp03Error", resourceCulture); + return ResourceManager.GetString("UnknownScpError", resourceCulture); } } diff --git a/Yubico.YubiKey/src/Resources/ExceptionMessages.resx b/Yubico.YubiKey/src/Resources/ExceptionMessages.resx index 479c635b4..f73f2e6a9 100644 --- a/Yubico.YubiKey/src/Resources/ExceptionMessages.resx +++ b/Yubico.YubiKey/src/Resources/ExceptionMessages.resx @@ -266,7 +266,7 @@ The provided PIN or PUK value violates current complexity conditions. - + PIV management key mutual authentication failed because the YubiKey did not authenticate. @@ -296,7 +296,7 @@ The temporary PIN length is invalid. - + The length of input to a Put Data operation, {0}, was invalid, the maximum is {1}. @@ -352,8 +352,8 @@ An unknown error has occurred. Candidate for removal - - An unknown SCP03 error has occurred. + + An unknown SCP error has occurred Cannot convert the data to the requested type. Expected only {0} byte(s) of data, but {1} byte(s) were present. @@ -904,7 +904,10 @@ The URI query cannot be null or empty. - - Exception caught when disposing PivSession: {0}, {1} + + Exception caught when disposing Session: {0}, {1} + + + Key agreement receipts do not match \ No newline at end of file diff --git a/Yubico.YubiKey/src/Yubico.YubiKey.csproj b/Yubico.YubiKey/src/Yubico.YubiKey.csproj index e5558830c..edba741b4 100644 --- a/Yubico.YubiKey/src/Yubico.YubiKey.csproj +++ b/Yubico.YubiKey/src/Yubico.YubiKey.csproj @@ -1,4 +1,4 @@ - + diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Oath/OathSessionPasswordTests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Oath/OathSessionPasswordTests.cs index ce3043ab7..dc492b2d4 100644 --- a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Oath/OathSessionPasswordTests.cs +++ b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Oath/OathSessionPasswordTests.cs @@ -13,6 +13,7 @@ // limitations under the License. using Xunit; +using Yubico.YubiKey.Scp; using Yubico.YubiKey.TestUtilities; namespace Yubico.YubiKey.Oath @@ -21,101 +22,130 @@ namespace Yubico.YubiKey.Oath [Trait(TraitTypes.Category, TestCategories.Simple)] public sealed class OathSessionPasswordTests { - [Theory, TestPriority(0)] - [InlineData(StandardTestDevice.Fw5)] - public void SetPassword(StandardTestDevice testDeviceType) + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, false)] + [InlineData(StandardTestDevice.Fw5, true)] + [InlineData(StandardTestDevice.Fw5Fips, false)] + [InlineData(StandardTestDevice.Fw5Fips, true)] + public void SetPassword( + StandardTestDevice testDeviceType, bool useScp) { - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - using (var oathSession = new OathSession(testDevice)) + var keyParameters = useScp ? Scp03KeyParameters.DefaultKey : null; + using (var resetSession = new OathSession(testDevice, keyParameters)) { - var collectorObj = new SimpleOathKeyCollector(); - oathSession.KeyCollector = collectorObj.SimpleKeyCollectorDelegate; + resetSession.ResetApplication(); + } - oathSession.SetPassword(); + using var oathSession = new OathSession(testDevice, keyParameters); + var collectorObj = new SimpleOathKeyCollector(); + oathSession.KeyCollector = collectorObj.SimpleKeyCollectorDelegate; - Assert.False(oathSession._oathData.Challenge.IsEmpty); - } + oathSession.SetPassword(); + + Assert.False(oathSession._oathData.Challenge.IsEmpty); } - [Theory, TestPriority(1)] - [InlineData(StandardTestDevice.Fw5)] - public void VerifyCorrectPassword(StandardTestDevice testDeviceType) + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, false)] + [InlineData(StandardTestDevice.Fw5, true)] + [InlineData(StandardTestDevice.Fw5Fips, false)] + [InlineData(StandardTestDevice.Fw5Fips, true)] + public void VerifyCorrectPassword( + StandardTestDevice testDeviceType, bool useScp) { - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - using (var oathSession = new OathSession(testDevice)) - { - var collectorObj = new SimpleOathKeyCollector(); - oathSession.KeyCollector = collectorObj.SimpleKeyCollectorDelegate; + SetTestPassword(testDevice); - bool isVerified = oathSession.TryVerifyPassword(); - Assert.True(isVerified); - } + using var oathSession = new OathSession(testDevice, useScp ? Scp03KeyParameters.DefaultKey : null); + var collectorObj = new SimpleOathKeyCollector(); + oathSession.KeyCollector = collectorObj.SimpleKeyCollectorDelegate; + + var isVerified = oathSession.TryVerifyPassword(); + + Assert.True(isVerified); } - [Theory, TestPriority(2)] - [InlineData(StandardTestDevice.Fw5)] - public void VerifyWrongPassword(StandardTestDevice testDeviceType) + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, false)] + [InlineData(StandardTestDevice.Fw5, true)] + [InlineData(StandardTestDevice.Fw5Fips, false)] + [InlineData(StandardTestDevice.Fw5Fips, true)] + public void VerifyWrongPassword( + StandardTestDevice testDeviceType, bool useScp) { - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - using (var oathSession = new OathSession(testDevice)) - { - var collectorObj = new SimpleOathKeyCollector(); - oathSession.KeyCollector = collectorObj.SimpleKeyCollectorDelegate; + SetTestPassword(testDevice); - collectorObj.KeyFlag = 1; + using var oathSession = new OathSession(testDevice, useScp ? Scp03KeyParameters.DefaultKey : null); + var collectorObj = new SimpleOathKeyCollector(); + oathSession.KeyCollector = collectorObj.SimpleKeyCollectorDelegate; - bool isVerified = oathSession.TryVerifyPassword(); - Assert.False(isVerified); - } + collectorObj.KeyFlag = 1; + + var isVerified = oathSession.TryVerifyPassword(); + Assert.False(isVerified); } - [Theory, TestPriority(3)] - [InlineData(StandardTestDevice.Fw5)] - public void ChangePassword(StandardTestDevice testDeviceType) + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, false)] + [InlineData(StandardTestDevice.Fw5, true)] + [InlineData(StandardTestDevice.Fw5Fips, false)] + [InlineData(StandardTestDevice.Fw5Fips, true)] + public void ChangePassword( + StandardTestDevice testDeviceType, bool useScp) { - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - using (var oathSession = new OathSession(testDevice)) - { - var collectorObj = new SimpleOathKeyCollector(); - oathSession.KeyCollector = collectorObj.SimpleKeyCollectorDelegate; + SetTestPassword(testDevice); - collectorObj.KeyFlag = 1; - oathSession.SetPassword(); + using var oathSession = new OathSession(testDevice, useScp ? Scp03KeyParameters.DefaultKey : null); + var collectorObj = new SimpleOathKeyCollector(); + oathSession.KeyCollector = collectorObj.SimpleKeyCollectorDelegate; + collectorObj.KeyFlag = 1; - Assert.False(oathSession._oathData.Challenge.IsEmpty); - } + oathSession.SetPassword(); + + Assert.False(oathSession._oathData.Challenge.IsEmpty); } - [Theory, TestPriority(4)] - [InlineData(StandardTestDevice.Fw5)] - public void UnsetPassword(StandardTestDevice testDeviceType) + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, false)] + [InlineData(StandardTestDevice.Fw5, true)] + [InlineData(StandardTestDevice.Fw5Fips, false)] + [InlineData(StandardTestDevice.Fw5Fips, true)] + public void UnsetPassword( + StandardTestDevice testDeviceType, bool useScp) { - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - using (var oathSession = new OathSession(testDevice)) - { - oathSession.ResetApplication(); + SetTestPassword(testDevice); + using var oathSession = new OathSession(testDevice, useScp ? Scp03KeyParameters.DefaultKey : null); + var collectorObj = new SimpleOathKeyCollector(); + oathSession.KeyCollector = collectorObj.SimpleKeyCollectorDelegate; - var collectorObj = new SimpleOathKeyCollector(); - oathSession.KeyCollector = collectorObj.SimpleKeyCollectorDelegate; + oathSession.UnsetPassword(); - oathSession.SetPassword(); + Assert.False(oathSession.IsPasswordProtected); + } - Assert.True(oathSession.IsPasswordProtected); + private void SetTestPassword(IYubiKeyDevice testDevice, Scp03KeyParameters? keyParameters = null) + { + using (var resetSession = new OathSession(testDevice, keyParameters)) + { + resetSession.ResetApplication(); } - using (var oathSession = new OathSession(testDevice)) + using (var oathSession = new OathSession(testDevice, keyParameters)) { var collectorObj = new SimpleOathKeyCollector(); oathSession.KeyCollector = collectorObj.SimpleKeyCollectorDelegate; + oathSession.SetPassword(); - oathSession.UnsetPassword(); - - Assert.False(oathSession.IsPasswordProtected); + Assert.True(oathSession.IsPasswordProtected); } } } diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Otp/ConfigureStaticTests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Otp/ConfigureStaticTests.cs deleted file mode 100644 index 13db59558..000000000 --- a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Otp/ConfigureStaticTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -// 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 Xunit; -using Yubico.Core.Devices.Hid; -using Yubico.YubiKey.Otp.Operations; -using Yubico.YubiKey.TestUtilities; - -namespace Yubico.YubiKey.Otp -{ - public class ConfigureStaticTests - { - - [Trait(TraitTypes.Category, TestCategories.Simple)] - [SkippableTheory(typeof(DeviceNotFoundException))] - [InlineData(StandardTestDevice.Fw5)] - [InlineData(StandardTestDevice.Fw5Fips)] - public void ConfigureStaticPassword_Succeeds(StandardTestDevice testDeviceType) - { - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - - using (var otpSession = new OtpSession(testDevice)) - { - if (otpSession.IsLongPressConfigured) - { - otpSession.DeleteSlot(Slot.LongPress); - } - - ConfigureStaticPassword configObj = otpSession.ConfigureStaticPassword(Slot.LongPress); - Assert.NotNull(configObj); - - var generatedPassword = new Memory(new char[16]); - - configObj = configObj.WithKeyboard(KeyboardLayout.en_US); - configObj = configObj.AllowManualUpdate(false); - configObj = configObj.AppendCarriageReturn(false); - configObj = configObj.SendTabFirst(false); - configObj = configObj.SetAllowUpdate(); - configObj = configObj.GeneratePassword(generatedPassword); - configObj.Execute(); - } - } - } -} diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Otp/OtpSessionTests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Otp/OtpSessionTests.cs new file mode 100644 index 000000000..9e2859178 --- /dev/null +++ b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Otp/OtpSessionTests.cs @@ -0,0 +1,83 @@ +// 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 Xunit; +using Yubico.Core.Devices.Hid; +using Yubico.YubiKey.Otp.Operations; +using Yubico.YubiKey.Scp; +using Yubico.YubiKey.TestUtilities; + +namespace Yubico.YubiKey.Otp +{ + public class OtpSessionTests + { + + [Trait(TraitTypes.Category, TestCategories.Simple)] + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void ConfigureStaticPassword_Succeeds(StandardTestDevice testDeviceType) + { + IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + + using var otpSession = new OtpSession(testDevice); + if (otpSession.IsLongPressConfigured) + { + otpSession.DeleteSlot(Slot.LongPress); + } + + ConfigureStaticPassword configObj = otpSession.ConfigureStaticPassword(Slot.LongPress); + Assert.NotNull(configObj); + + var generatedPassword = new Memory(new char[16]); + + configObj = configObj.WithKeyboard(KeyboardLayout.en_US); + configObj = configObj.AllowManualUpdate(false); + configObj = configObj.AppendCarriageReturn(false); + configObj = configObj.SendTabFirst(false); + configObj = configObj.SetAllowUpdate(); + configObj = configObj.GeneratePassword(generatedPassword); + configObj.Execute(); + } + + [Trait(TraitTypes.Category, TestCategories.Simple)] + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void ConfigureStaticPassword_WithWScp_Succeeds(StandardTestDevice testDeviceType) + { + IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + + using var otpSession = new OtpSession(testDevice, Scp03KeyParameters.DefaultKey); + if (otpSession.IsLongPressConfigured) + { + otpSession.DeleteSlot(Slot.LongPress); + } + + ConfigureStaticPassword configObj = otpSession.ConfigureStaticPassword(Slot.LongPress); + Assert.NotNull(configObj); + + var generatedPassword = new Memory(new char[16]); + + configObj = configObj.WithKeyboard(KeyboardLayout.en_US); + configObj = configObj.AllowManualUpdate(false); + configObj = configObj.AppendCarriageReturn(false); + configObj = configObj.SendTabFirst(false); + configObj = configObj.SetAllowUpdate(); + configObj = configObj.GeneratePassword(generatedPassword); + configObj.Execute(); + } + } +} diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/GenerateTests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/GenerateTests.cs index cf3c6e7b0..4d1bb46cf 100644 --- a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/GenerateTests.cs +++ b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/GenerateTests.cs @@ -15,6 +15,7 @@ using System; using Xunit; using Yubico.YubiKey.Piv.Commands; +using Yubico.YubiKey.Scp; using Yubico.YubiKey.Scp03; using Yubico.YubiKey.TestUtilities; @@ -37,13 +38,14 @@ public void SimpleGenerate(PivAlgorithm expectedAlgorithm, bool useScp03 = false var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(Transport.SmartCard, FirmwareVersion.V5_3_0); Assert.True(testDevice.AvailableUsbCapabilities.HasFlag(YubiKeyCapabilities.Piv)); + using var pivSession = useScp03 + ? new PivSession(testDevice, Scp03KeyParameters.DefaultKey) + : new PivSession(testDevice); - using var pivSession = useScp03 ? new PivSession(testDevice, new StaticKeys()) : new PivSession(testDevice); var collectorObj = new Simple39KeyCollector(); pivSession.KeyCollector = collectorObj.Simple39KeyCollectorDelegate; var result = pivSession.GenerateKeyPair(PivSlot.Retired12, expectedAlgorithm); - Assert.Equal(expectedAlgorithm, result.Algorithm); } @@ -58,12 +60,6 @@ public void GenerateAndSign(PivAlgorithm algorithm) Assert.True(testDevice.AvailableUsbCapabilities.HasFlag(YubiKeyCapabilities.Piv)); Assert.True(testDevice is YubiKeyDevice); - // if (testDevice is YubiKeyDevice device) - // { - // #pragma warning disable CS0618 // Specifically testing this feature - // // testDevice = device.WithScp03(new StaticKeys()); - // #pragma warning restore CS0618 // - // } var isValid = DoGenerate(testDevice, 0x86, algorithm, PivPinPolicy.Once, PivTouchPolicy.Never); Assert.True(isValid); diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/GetPutDataTests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/GetPutDataTests.cs index 9ff52ba1a..35d6b94ed 100644 --- a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/GetPutDataTests.cs +++ b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/GetPutDataTests.cs @@ -17,6 +17,7 @@ using Xunit; using Yubico.Core.Tlv; using Yubico.YubiKey.Piv.Commands; +using Yubico.YubiKey.Scp; using Yubico.YubiKey.Scp03; using Yubico.YubiKey.TestUtilities; @@ -29,7 +30,7 @@ public class GetPutDataTests public void Cert_Auth_Req(StandardTestDevice testDeviceType) { var isValid = SampleKeyPairs.GetMatchingKeyAndCert(PivAlgorithm.Rsa2048, - out X509Certificate2 cert, out PivPrivateKey privateKey); + out var cert, out var privateKey); Assert.True(isValid); var certDer = cert.GetRawCertData(); @@ -45,7 +46,7 @@ public void Cert_Auth_Req(StandardTestDevice testDeviceType) var certData = tlvWriter.Encode(); tlvWriter.Clear(); - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); using (var pivSession = new PivSession(testDevice)) { @@ -61,13 +62,13 @@ public void Cert_Auth_Req(StandardTestDevice testDeviceType) { // There should be no data. var getDataCommand = new GetDataCommand((int)PivDataTag.Authentication); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.NoData, getDataResponse.Status); // Now put some data. // This should fail because the mgmt key is needed. var putDataCommand = new PutDataCommand((int)PivDataTag.Authentication, certData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, putDataResponse.Status); // Verify the PIN @@ -87,7 +88,7 @@ public void Cert_Auth_Req(StandardTestDevice testDeviceType) pivSession.AuthenticateManagementKey(); var putDataCommand = new PutDataCommand((int)PivDataTag.Authentication, certData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.Success, putDataResponse.Status); } @@ -95,10 +96,10 @@ public void Cert_Auth_Req(StandardTestDevice testDeviceType) { // There should be data this time. var getDataCommand = new GetDataCommand((int)PivDataTag.Authentication); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.Success, getDataResponse.Status); - ReadOnlyMemory getData = getDataResponse.GetData(); + var getData = getDataResponse.GetData(); Assert.Equal(certData.Length, getData.Length); } } @@ -114,21 +115,24 @@ public void Chuid_Auth_Req(StandardTestDevice testDeviceType) 0x08, 0x32, 0x30, 0x33, 0x30, 0x30, 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00 }; - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - using (var pivSession = new PivSession(testDevice, new StaticKeys())) + using (var pivSession = new PivSession(testDevice)) { pivSession.ResetApplication(); + } + using (var pivSession = new PivSession(testDevice, Scp03KeyParameters.DefaultKey)) + { // There should be no data. var getDataCommand = new GetDataCommand((int)PivDataTag.Chuid); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.NoData, getDataResponse.Status); // Now put some data. // This should fail because the mgmt key is needed. var putDataCommand = new PutDataCommand((int)PivDataTag.Chuid, chuidData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, putDataResponse.Status); // Verify the PIN @@ -148,7 +152,7 @@ public void Chuid_Auth_Req(StandardTestDevice testDeviceType) pivSession.AuthenticateManagementKey(); var putDataCommand = new PutDataCommand((int)PivDataTag.Chuid, chuidData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.Success, putDataResponse.Status); } @@ -157,10 +161,10 @@ public void Chuid_Auth_Req(StandardTestDevice testDeviceType) using (var pivSession = new PivSession(testDevice)) { var getDataCommand = new GetDataCommand((int)PivDataTag.Chuid); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.Success, getDataResponse.Status); - ReadOnlyMemory getData = getDataResponse.GetData(); + var getData = getDataResponse.GetData(); Assert.Equal(61, getData.Length); } } @@ -176,7 +180,7 @@ public void Capability_Auth_Req(StandardTestDevice testDeviceType) 0x00, 0xFD, 0x00, 0xFE, 0x00 }; - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); using (var pivSession = new PivSession(testDevice)) { @@ -186,13 +190,13 @@ public void Capability_Auth_Req(StandardTestDevice testDeviceType) #pragma warning disable CS0618 // Testing an obsolete feature var getDataCommand = new GetDataCommand(PivDataTag.Capability); #pragma warning restore CS0618 // Type or member is obsolete - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.NoData, getDataResponse.Status); // Now put some data. // This should fail because the mgmt key is needed. var putDataCommand = new PutDataCommand((int)PivDataTag.Capability, capabilityData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, putDataResponse.Status); // Verify the PIN @@ -214,7 +218,7 @@ public void Capability_Auth_Req(StandardTestDevice testDeviceType) #pragma warning disable CS0618 // Testing an obsolete feature var putDataCommand = new PutDataCommand(PivDataTag.Capability, capabilityData); #pragma warning restore CS0618 // Type or member is obsolete - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.Success, putDataResponse.Status); } @@ -225,10 +229,10 @@ public void Capability_Auth_Req(StandardTestDevice testDeviceType) #pragma warning disable CS0618 // Testing an obsolete feature var getDataCommand = new GetDataCommand(PivDataTag.Capability); #pragma warning restore CS0618 // Type or member is obsolete - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.Success, getDataResponse.Status); - ReadOnlyMemory getData = getDataResponse.GetData(); + var getData = getDataResponse.GetData(); Assert.Equal(53, getData.Length); } } @@ -242,7 +246,7 @@ public void Discovery_Auth_Req(StandardTestDevice testDeviceType) 0x2F, 0x02, 0x40, 0x00, }; - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); using (var pivSession = new PivSession(testDevice)) { @@ -250,10 +254,10 @@ public void Discovery_Auth_Req(StandardTestDevice testDeviceType) // There should be data. var getDataCommand = new GetDataCommand((int)PivDataTag.Discovery); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.Success, getDataResponse.Status); - ReadOnlyMemory getData = getDataResponse.GetData(); + var getData = getDataResponse.GetData(); Assert.Equal(20, getData.Length); // Now put some data. @@ -271,21 +275,8 @@ public void Printed_Auth_Req(StandardTestDevice testDeviceType) byte[] printedData = { 0x53, 0x04, 0x04, 0x02, 0xd4, 0xe7 }; - byte[] key1 = { - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff - }; - byte[] key2 = { - 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11 - }; - byte[] key3 = { - 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11, 0x22 - }; - var newKeys = new StaticKeys(key2, key1, key3) - { - KeyVersionNumber = 2 - }; - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); using (var pivSession = new PivSession(testDevice)) { @@ -293,7 +284,7 @@ public void Printed_Auth_Req(StandardTestDevice testDeviceType) // There should be no data, but even so, the error should be Auth Required. var getDataCommand = new GetDataCommand((int)PivDataTag.Printed); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, getDataResponse.Status); // Authenticate the mgmt key and try again. It should still @@ -304,8 +295,7 @@ public void Printed_Auth_Req(StandardTestDevice testDeviceType) getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, getDataResponse.Status); } - - using (var pivSession = new PivSession(testDevice, newKeys)) + using (var pivSession = new PivSession(testDevice)) { // Verify the PIN pivSession.KeyCollector = PinOnlyKeyCollectorDelegate; @@ -314,7 +304,7 @@ public void Printed_Auth_Req(StandardTestDevice testDeviceType) // Get the data. This time we should be able to see that there's // NoData. var getDataCommand = new GetDataCommand((int)PivDataTag.Printed); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.NoData, getDataResponse.Status); } @@ -325,7 +315,7 @@ public void Printed_Auth_Req(StandardTestDevice testDeviceType) // With no PIN nor mgmt key, or with only the PIN, this should // fail. var putDataCommand = new PutDataCommand(0x5FC109, printedData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, putDataResponse.Status); pivSession.KeyCollector = PinOnlyKeyCollectorDelegate; @@ -342,7 +332,7 @@ public void Printed_Auth_Req(StandardTestDevice testDeviceType) pivSession.AuthenticateManagementKey(); var putDataCommand = new PutDataCommand(0x5FC109, printedData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.Success, putDataResponse.Status); } @@ -351,7 +341,7 @@ public void Printed_Auth_Req(StandardTestDevice testDeviceType) using (var pivSession = new PivSession(testDevice)) { var getDataCommand = new GetDataCommand((int)PivDataTag.Printed); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, getDataResponse.Status); pivSession.KeyCollector = MgmtKeyOnlyKeyCollectorDelegate; @@ -369,10 +359,10 @@ public void Printed_Auth_Req(StandardTestDevice testDeviceType) pivSession.VerifyPin(); var getDataCommand = new GetDataCommand((int)PivDataTag.Printed); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.Success, getDataResponse.Status); - ReadOnlyMemory getData = getDataResponse.GetData(); + var getData = getDataResponse.GetData(); Assert.Equal(6, getData.Length); } } @@ -386,7 +376,7 @@ public void Security_Auth_Req(StandardTestDevice testDeviceType) 0x53, 0x08, 0xBA, 0x01, 0x11, 0xBB, 0x01, 0x22, 0xFE, 0x00 }; - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); using (var pivSession = new PivSession(testDevice)) { @@ -394,13 +384,13 @@ public void Security_Auth_Req(StandardTestDevice testDeviceType) // There should be no data. var getDataCommand = new GetDataCommand((int)PivDataTag.SecurityObject); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.NoData, getDataResponse.Status); // Now put some data. // This should fail because the mgmt key is needed. var putDataCommand = new PutDataCommand((int)PivDataTag.SecurityObject, securityData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, putDataResponse.Status); // Verify the PIN @@ -420,7 +410,7 @@ public void Security_Auth_Req(StandardTestDevice testDeviceType) pivSession.AuthenticateManagementKey(); var putDataCommand = new PutDataCommand((int)PivDataTag.SecurityObject, securityData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.Success, putDataResponse.Status); } @@ -429,10 +419,10 @@ public void Security_Auth_Req(StandardTestDevice testDeviceType) using (var pivSession = new PivSession(testDevice)) { var getDataCommand = new GetDataCommand((int)PivDataTag.SecurityObject); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.Success, getDataResponse.Status); - ReadOnlyMemory getData = getDataResponse.GetData(); + var getData = getDataResponse.GetData(); Assert.Equal(10, getData.Length); } } @@ -445,7 +435,7 @@ public void KeyHistory_Auth_Req(StandardTestDevice testDeviceType) 0x53, 0x0A, 0xC1, 0x01, 0x00, 0xC2, 0x01, 0x00, 0xF3, 0x00, 0xFE, 0x00 }; - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); using (var pivSession = new PivSession(testDevice)) { @@ -453,13 +443,13 @@ public void KeyHistory_Auth_Req(StandardTestDevice testDeviceType) // There should be no data. var getDataCommand = new GetDataCommand((int)PivDataTag.KeyHistory); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.NoData, getDataResponse.Status); // Now put some data. // This should fail because the mgmt key is needed. var putDataCommand = new PutDataCommand((int)PivDataTag.KeyHistory, keyHistoryData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, putDataResponse.Status); // Verify the PIN @@ -479,7 +469,7 @@ public void KeyHistory_Auth_Req(StandardTestDevice testDeviceType) pivSession.AuthenticateManagementKey(); var putDataCommand = new PutDataCommand((int)PivDataTag.KeyHistory, keyHistoryData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.Success, putDataResponse.Status); } @@ -488,10 +478,10 @@ public void KeyHistory_Auth_Req(StandardTestDevice testDeviceType) using (var pivSession = new PivSession(testDevice)) { var getDataCommand = new GetDataCommand((int)PivDataTag.KeyHistory); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.Success, getDataResponse.Status); - ReadOnlyMemory getData = getDataResponse.GetData(); + var getData = getDataResponse.GetData(); Assert.Equal(12, getData.Length); } } @@ -505,7 +495,7 @@ public void Iris_Auth_Req(StandardTestDevice testDeviceType) 0x53, 0x05, 0xBC, 0x01, 0x11, 0xFE, 0x00 }; - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); using (var pivSession = new PivSession(testDevice)) { @@ -513,7 +503,7 @@ public void Iris_Auth_Req(StandardTestDevice testDeviceType) // There should be no data, but even so, the error should be Auth Required. var getDataCommand = new GetDataCommand((int)PivDataTag.IrisImages); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, getDataResponse.Status); // Authenticate the mgmt key and try to get data again. It should still @@ -534,7 +524,7 @@ public void Iris_Auth_Req(StandardTestDevice testDeviceType) // Get the data. This time we should be able to see that there's // NoData. var getDataCommand = new GetDataCommand((int)PivDataTag.IrisImages); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.NoData, getDataResponse.Status); } @@ -544,7 +534,7 @@ public void Iris_Auth_Req(StandardTestDevice testDeviceType) // With no PIN nor mgmt key, or with only the PIN, this should // fail. var putDataCommand = new PutDataCommand((int)PivDataTag.IrisImages, irisData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, putDataResponse.Status); pivSession.KeyCollector = PinOnlyKeyCollectorDelegate; @@ -561,7 +551,7 @@ public void Iris_Auth_Req(StandardTestDevice testDeviceType) pivSession.AuthenticateManagementKey(); var putDataCommand = new PutDataCommand((int)PivDataTag.IrisImages, irisData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.Success, putDataResponse.Status); } @@ -570,7 +560,7 @@ public void Iris_Auth_Req(StandardTestDevice testDeviceType) using (var pivSession = new PivSession(testDevice)) { var getDataCommand = new GetDataCommand((int)PivDataTag.IrisImages); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, getDataResponse.Status); pivSession.KeyCollector = MgmtKeyOnlyKeyCollectorDelegate; @@ -588,10 +578,10 @@ public void Iris_Auth_Req(StandardTestDevice testDeviceType) pivSession.VerifyPin(); var getDataCommand = new GetDataCommand((int)PivDataTag.IrisImages); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.Success, getDataResponse.Status); - ReadOnlyMemory getData = getDataResponse.GetData(); + var getData = getDataResponse.GetData(); Assert.Equal(7, getData.Length); } } @@ -605,7 +595,7 @@ public void Facial_Auth_Req(StandardTestDevice testDeviceType) 0x53, 0x05, 0xBC, 0x01, 0x11, 0xFE, 0x00 }; - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); using (var pivSession = new PivSession(testDevice)) { @@ -613,7 +603,7 @@ public void Facial_Auth_Req(StandardTestDevice testDeviceType) // There should be no data, but even so, the error should be Auth Required. var getDataCommand = new GetDataCommand((int)PivDataTag.FacialImage); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, getDataResponse.Status); // Authenticate the mgmt key and try to get data again. It should still @@ -634,7 +624,7 @@ public void Facial_Auth_Req(StandardTestDevice testDeviceType) // Get the data. This time we should be able to see that there's // NoData. var getDataCommand = new GetDataCommand((int)PivDataTag.FacialImage); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.NoData, getDataResponse.Status); } @@ -644,7 +634,7 @@ public void Facial_Auth_Req(StandardTestDevice testDeviceType) // With no PIN nor mgmt key, or with only the PIN, this should // fail. var putDataCommand = new PutDataCommand((int)PivDataTag.FacialImage, facialData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, putDataResponse.Status); pivSession.KeyCollector = PinOnlyKeyCollectorDelegate; @@ -661,7 +651,7 @@ public void Facial_Auth_Req(StandardTestDevice testDeviceType) pivSession.AuthenticateManagementKey(); var putDataCommand = new PutDataCommand((int)PivDataTag.FacialImage, facialData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.Success, putDataResponse.Status); } @@ -670,7 +660,7 @@ public void Facial_Auth_Req(StandardTestDevice testDeviceType) using (var pivSession = new PivSession(testDevice)) { var getDataCommand = new GetDataCommand((int)PivDataTag.FacialImage); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, getDataResponse.Status); pivSession.KeyCollector = MgmtKeyOnlyKeyCollectorDelegate; @@ -688,10 +678,10 @@ public void Facial_Auth_Req(StandardTestDevice testDeviceType) pivSession.VerifyPin(); var getDataCommand = new GetDataCommand((int)PivDataTag.FacialImage); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.Success, getDataResponse.Status); - ReadOnlyMemory getData = getDataResponse.GetData(); + var getData = getDataResponse.GetData(); Assert.Equal(7, getData.Length); } } @@ -705,7 +695,7 @@ public void Fingerprint_Auth_Req(StandardTestDevice testDeviceType) 0x53, 0x05, 0xBC, 0x01, 0x11, 0xFE, 0x00 }; - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); using (var pivSession = new PivSession(testDevice)) { @@ -713,7 +703,7 @@ public void Fingerprint_Auth_Req(StandardTestDevice testDeviceType) // There should be no data, but even so, the error should be Auth Required. var getDataCommand = new GetDataCommand((int)PivDataTag.Fingerprints); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, getDataResponse.Status); // Authenticate the mgmt key and try to get data again. It should still @@ -734,7 +724,7 @@ public void Fingerprint_Auth_Req(StandardTestDevice testDeviceType) // Get the data. This time we should be able to see that there's // NoData. var getDataCommand = new GetDataCommand((int)PivDataTag.Fingerprints); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.NoData, getDataResponse.Status); } @@ -744,7 +734,7 @@ public void Fingerprint_Auth_Req(StandardTestDevice testDeviceType) // With no PIN nor mgmt key, or with only the PIN, this should // fail. var putDataCommand = new PutDataCommand((int)PivDataTag.Fingerprints, fingerprintData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, putDataResponse.Status); pivSession.KeyCollector = PinOnlyKeyCollectorDelegate; @@ -761,7 +751,7 @@ public void Fingerprint_Auth_Req(StandardTestDevice testDeviceType) pivSession.AuthenticateManagementKey(); var putDataCommand = new PutDataCommand((int)PivDataTag.Fingerprints, fingerprintData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.Success, putDataResponse.Status); } @@ -770,7 +760,7 @@ public void Fingerprint_Auth_Req(StandardTestDevice testDeviceType) using (var pivSession = new PivSession(testDevice)) { var getDataCommand = new GetDataCommand((int)PivDataTag.Fingerprints); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, getDataResponse.Status); pivSession.KeyCollector = MgmtKeyOnlyKeyCollectorDelegate; @@ -788,10 +778,10 @@ public void Fingerprint_Auth_Req(StandardTestDevice testDeviceType) pivSession.VerifyPin(); var getDataCommand = new GetDataCommand((int)PivDataTag.Fingerprints); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.Success, getDataResponse.Status); - ReadOnlyMemory getData = getDataResponse.GetData(); + var getData = getDataResponse.GetData(); Assert.Equal(7, getData.Length); } } @@ -805,7 +795,7 @@ public void Bitgt_Auth_Req(StandardTestDevice testDeviceType) 0x7F, 0x61, 0x07, 0x02, 0x01, 0x01, 0x7F, 0x60, 0x01, 0x01 }; - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); using (var pivSession = new PivSession(testDevice)) { @@ -815,7 +805,7 @@ public void Bitgt_Auth_Req(StandardTestDevice testDeviceType) #pragma warning disable CS0618 // Testing an obsolete feature var getDataCommand = new GetDataCommand(PivDataTag.BiometricGroupTemplate); #pragma warning restore CS0618 // Type or member is obsolete - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.NoData, getDataResponse.Status); // Now try to put some data. @@ -837,7 +827,7 @@ public void SMSigner_Auth_Req(StandardTestDevice testDeviceType) 0x53, 0x08, 0x70, 0x01, 0x11, 0x71, 0x01, 0x00, 0xFE, 0x00 }; - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); using (var pivSession = new PivSession(testDevice)) { @@ -846,7 +836,7 @@ public void SMSigner_Auth_Req(StandardTestDevice testDeviceType) // There is no auth required to get data, but there should be no // data at the moment. var getDataCommand = new GetDataCommand((int)PivDataTag.SecureMessageSigner); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.NoData, getDataResponse.Status); } @@ -856,7 +846,7 @@ public void SMSigner_Auth_Req(StandardTestDevice testDeviceType) // With no PIN nor mgmt key, or with only the PIN, this should // fail. var putDataCommand = new PutDataCommand((int)PivDataTag.SecureMessageSigner, smSignerData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, putDataResponse.Status); pivSession.KeyCollector = PinOnlyKeyCollectorDelegate; @@ -873,7 +863,7 @@ public void SMSigner_Auth_Req(StandardTestDevice testDeviceType) pivSession.AuthenticateManagementKey(); var putDataCommand = new PutDataCommand((int)PivDataTag.SecureMessageSigner, smSignerData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.Success, putDataResponse.Status); } @@ -882,10 +872,10 @@ public void SMSigner_Auth_Req(StandardTestDevice testDeviceType) using (var pivSession = new PivSession(testDevice)) { var getDataCommand = new GetDataCommand((int)PivDataTag.SecureMessageSigner); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.Success, getDataResponse.Status); - ReadOnlyMemory getData = getDataResponse.GetData(); + var getData = getDataResponse.GetData(); Assert.Equal(10, getData.Length); } } @@ -899,7 +889,7 @@ public void PCRef_Auth_Req(StandardTestDevice testDeviceType) 0x53, 0x0C, 0x99, 0x08, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xFE, 0x00 }; - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); using (var pivSession = new PivSession(testDevice)) { pivSession.ResetApplication(); @@ -907,7 +897,7 @@ public void PCRef_Auth_Req(StandardTestDevice testDeviceType) // There is no auth required to get data, but there should be no // data at the moment. var getDataCommand = new GetDataCommand((int)PivDataTag.PairingCodeReferenceData); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.NoData, getDataResponse.Status); } @@ -917,7 +907,7 @@ public void PCRef_Auth_Req(StandardTestDevice testDeviceType) // With no PIN nor mgmt key, or with only the PIN, this should // fail. var putDataCommand = new PutDataCommand((int)PivDataTag.PairingCodeReferenceData, pcRefData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, putDataResponse.Status); pivSession.KeyCollector = PinOnlyKeyCollectorDelegate; @@ -934,7 +924,7 @@ public void PCRef_Auth_Req(StandardTestDevice testDeviceType) pivSession.AuthenticateManagementKey(); var putDataCommand = new PutDataCommand((int)PivDataTag.PairingCodeReferenceData, pcRefData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.Success, putDataResponse.Status); } @@ -943,10 +933,10 @@ public void PCRef_Auth_Req(StandardTestDevice testDeviceType) using (var pivSession = new PivSession(testDevice)) { var getDataCommand = new GetDataCommand((int)PivDataTag.PairingCodeReferenceData); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.Success, getDataResponse.Status); - ReadOnlyMemory getData = getDataResponse.GetData(); + var getData = getDataResponse.GetData(); Assert.Equal(14, getData.Length); } } @@ -959,19 +949,19 @@ public void AdminData_Auth_Req(StandardTestDevice testDeviceType) 0x53, 0x09, 0x80, 0x07, 0x81, 0x01, 0x00, 0x03, 0x02, 0x5C, 0x29 }; - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); using (var pivSession = new PivSession(testDevice)) { // There should be no data. var getDataCommand = new GetDataCommand(0x5FFF00); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.NoData, getDataResponse.Status); // Now put some data. // This should fail because the mgmt key is needed. var putDataCommand = new PutDataCommand(0x5FFF00, adminData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.AuthenticationRequired, putDataResponse.Status); // Verify the PIN @@ -991,7 +981,7 @@ public void AdminData_Auth_Req(StandardTestDevice testDeviceType) pivSession.AuthenticateManagementKey(); var putDataCommand = new PutDataCommand(0x5FFF00, adminData); - PutDataResponse putDataResponse = pivSession.Connection.SendCommand(putDataCommand); + var putDataResponse = pivSession.Connection.SendCommand(putDataCommand); Assert.Equal(ResponseStatus.Success, putDataResponse.Status); } @@ -999,10 +989,10 @@ public void AdminData_Auth_Req(StandardTestDevice testDeviceType) { // There should be data this time. var getDataCommand = new GetDataCommand(0x5FFF00); - GetDataResponse getDataResponse = pivSession.Connection.SendCommand(getDataCommand); + var getDataResponse = pivSession.Connection.SendCommand(getDataCommand); Assert.Equal(ResponseStatus.Success, getDataResponse.Status); - ReadOnlyMemory getData = getDataResponse.GetData(); + var getData = getDataResponse.GetData(); Assert.Equal(11, getData.Length); } } diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/SignTests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/SignTests.cs index cded8cb9f..08fa81cc4 100644 --- a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/SignTests.cs +++ b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/SignTests.cs @@ -18,6 +18,7 @@ using Yubico.Core.Tlv; using Yubico.YubiKey.Cryptography; using Yubico.YubiKey.Piv.Commands; +using Yubico.YubiKey.Scp; using Yubico.YubiKey.Scp03; using Yubico.YubiKey.TestUtilities; @@ -47,9 +48,8 @@ public void Sign_EccP256_Succeeds(bool useScp03, StandardTestDevice device, PivP bool isValid = LoadKey(PivAlgorithm.EccP256, 0x89, pinPolicy, PivTouchPolicy.Never, testDevice); Assert.True(isValid); Assert.True(testDevice.AvailableUsbCapabilities.HasFlag(YubiKeyCapabilities.Piv)); - using var pivSession = useScp03 - ? new PivSession(testDevice, new StaticKeys()) + ? new PivSession(testDevice, Scp03KeyParameters.DefaultKey) : new PivSession(testDevice); var collectorObj = new Simple39KeyCollector(); diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp/Scp03Tests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp/Scp03Tests.cs new file mode 100644 index 000000000..e67c657e2 --- /dev/null +++ b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp/Scp03Tests.cs @@ -0,0 +1,469 @@ +// 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 System.Linq; +using System.Security.Cryptography; +using Xunit; +using Yubico.Core.Tlv; +using Yubico.YubiKey.Cryptography; +using Yubico.YubiKey.Piv; +using Yubico.YubiKey.Piv.Commands; +using Yubico.YubiKey.Scp03; +using Yubico.YubiKey.TestUtilities; +using GetDataCommand = Yubico.YubiKey.Scp.Commands.GetDataCommand; + +namespace Yubico.YubiKey.Scp +{ + [Trait(TraitTypes.Category, TestCategories.Simple)] + public class Scp03Tests + { + private readonly ReadOnlyMemory _defaultPin = new byte[] { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }; + + public Scp03Tests() + { + ResetAllowedDevices(); + } + + private IYubiKeyDevice GetDevice( + StandardTestDevice desiredDeviceType, + Transport transport = Transport.All, + FirmwareVersion? minimumFirmwareVersion = null) + { + var testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(desiredDeviceType, transport, minimumFirmwareVersion); + + // Since we are in testing, we assume that the keys the default keys are present on the device and haven't been changed + // Therefore we will throw an exception if the device is FIPS and the transport is NFC + // This can be changed in the future if needed + Assert.False( + desiredDeviceType == StandardTestDevice.Fw5Fips && + transport == Transport.NfcSmartCard && + testDevice.IsFipsSeries, + "SCP03 with the default static keys is not allowed over NFC on FIPS capable devices"); + + return testDevice; + } + + private static void ResetAllowedDevices() + { + // Reset all attached allowed devices + foreach (var availableDevice in IntegrationTestDeviceEnumeration.GetTestDevices()) + { + using var session = new SecurityDomainSession(availableDevice); + session.Reset(); + } + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_PutKey_with_StaticKey_Imports_Key(StandardTestDevice desiredDeviceType, Transport transport) + { + byte[] sk = + { + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + }; + + var testDevice = GetDevice(desiredDeviceType, transport); + var newKeyParams = Scp03KeyParameters.FromStaticKeys(new StaticKeys(sk, sk, sk)); + + // Authenticate with default key, then replace default key + using (var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey)) + { + session.PutKey(newKeyParams.KeyReference, newKeyParams.StaticKeys, 0); + } + + using (_ = new SecurityDomainSession(testDevice, newKeyParams)) + { + } + + // Default key should not work now and throw an exception + Assert.Throws(() => + { + using (_ = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey)) + { + } + }); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + [Obsolete("Use Scp03_PutKey_with_StaticKey_Imports_Key instead")] + public void Obsolete_Scp03_PutKey_with_StaticKey_Imports_Key(StandardTestDevice desiredDeviceType, Transport transport) + { + byte[] sk = + { + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + }; + + var testDevice = GetDevice(desiredDeviceType, transport); + var defaultStaticKeys = new Scp03.StaticKeys(); + var newStaticKeys = new Scp03.StaticKeys(sk, sk, sk); + + // Authenticate with default key, then replace default key + using (var session = new Scp03Session(testDevice, defaultStaticKeys)) + { + session.PutKeySet(newStaticKeys); + } + + using (_ = new Scp03Session(testDevice, newStaticKeys)) + { + } + + // Default key should not work now and throw an exception + Assert.Throws(() => + { + using (_ = new Scp03Session(testDevice, defaultStaticKeys)) + { + } + }); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_PutKey_with_PublicKey_Imports_Key(StandardTestDevice desiredDeviceType, Transport transport) + { + var keyReference = new KeyReference(ScpKeyIds.ScpCaPublicKey, 0x3); + var testDevice = GetDevice(desiredDeviceType, transport, FirmwareVersion.V5_7_2); + + using var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey); + using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256); + + var publicKey = new ECPublicKeyParameters(ecdsa); + session.PutKey(keyReference, publicKey, 0); + + // Verify the generated key was stored + var keyInformation = session.GetKeyInformation(); + Assert.True(keyInformation.ContainsKey(keyReference)); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_AuthenticateWithWrongKey_Should_ThrowException(StandardTestDevice desiredDeviceType, Transport transport) + { + var testDevice = GetDevice(desiredDeviceType, transport); + var incorrectKeys = RandomStaticKeys(); + var keyRef = Scp03KeyParameters.FromStaticKeys(incorrectKeys); + + // Authentication with incorrect key should throw + Assert.Throws(() => + { + using (var session = new SecurityDomainSession(testDevice, keyRef)) { } + }); + + // Authentication with default key should succeed + using (_ = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey)) + { + } + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_GetInformation_WithDefaultKey_Returns_DefaultKey(StandardTestDevice desiredDeviceType, Transport transport) + { + var testDevice = GetDevice(desiredDeviceType, transport); + using var session = new SecurityDomainSession(testDevice); + + var result = session.GetKeyInformation(); + if (testDevice.FirmwareVersion < FirmwareVersion.V5_7_2) + { + Assert.Equal(3, result.Count); + } + else + { + Assert.Equal(4, result.Count); + } + Assert.Equal(0xFF, result.Keys.First().VersionNumber); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_Connect_GetKeyInformation_WithDefaultKey_Returns_DefaultKey(StandardTestDevice desiredDeviceType, Transport transport) + { + var testDevice = GetDevice(desiredDeviceType, transport); + using var connection = testDevice.Connect(YubiKeyApplication.SecurityDomain); + + const byte TAG_KEY_INFORMATION = 0xE0; + var response = connection.SendCommand(new GetDataCommand(TAG_KEY_INFORMATION)); + var result = response.GetData(); + + var keyInformation = new Dictionary>(); + foreach (var tlvObject in TlvObjects.DecodeList(result.Span)) + { + var value = TlvObjects.UnpackValue(0xC0, tlvObject.GetBytes().Span); + var keyRef = new KeyReference(value.Span[0], value.Span[1]); + var keyComponents = new Dictionary(); + + // Iterate while there are more key components, each component is 2 bytes, so take 2 bytes at a time + while (!(value = value[2..]).IsEmpty) + { + keyComponents.Add(value.Span[0], value.Span[1]); + } + + keyInformation.Add(keyRef, keyComponents); + } + + Assert.NotEmpty(keyInformation); + Assert.Equal(0xFF, keyInformation.Keys.First().VersionNumber); // 0xff, Default kvn + + if (testDevice.FirmwareVersion < FirmwareVersion.V5_7_2) + { + Assert.Equal(3, keyInformation.Keys.Count); + } + else + { + Assert.Equal(4, keyInformation.Keys.Count); + } + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_GetCertificates_ReturnsCerts(StandardTestDevice desiredDeviceType, Transport transport) + { + var testDevice = GetDevice(desiredDeviceType, transport, FirmwareVersion.V5_7_2); + + using var session = new SecurityDomainSession(testDevice); + + var keyReference = new KeyReference(ScpKeyIds.Scp11B, 0x1); + var certificateList = session.GetCertificates(keyReference); + + Assert.NotEmpty(certificateList); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_Reset_Restores_SecurityDomainKeys_To_FactoryKeys(StandardTestDevice desiredDeviceType, Transport transport) + { + var testDevice = GetDevice(desiredDeviceType, transport); + byte[] sk = + { + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + }; + + var newKeyParams = new Scp03KeyParameters( + ScpKeyIds.Scp03, + 0x01, + new StaticKeys(sk, sk, sk)); + + using (var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey)) + { + session.PutKey(newKeyParams.KeyReference, newKeyParams.StaticKeys, 0); + } + + // Authentication with new key should succeed + using (var session = new SecurityDomainSession(testDevice, newKeyParams)) + { + session.GetKeyInformation(); + } + + // Default key should not work now and throw an exception + Assert.Throws(() => + { + using (_ = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey)) + { + } + }); + + using (var session = new SecurityDomainSession(testDevice)) + { + session.Reset(); + } + + // Successful authentication with default key means key has been restored to factory settings + using (var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey)) + { + _ = session.GetKeyInformation(); + } + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_GetSupportedCaIdentifiers_Succeeds(StandardTestDevice desiredDeviceType, Transport transport) + { + var testDevice = GetDevice(desiredDeviceType, transport, FirmwareVersion.V5_7_2); + using var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey); + + var result = session.GetSupportedCaIdentifiers(true, true); + Assert.NotEmpty(result); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_GetCardRecognitionData_Succeeds(StandardTestDevice desiredDeviceType, Transport transport) + { + var testDevice = GetDevice(desiredDeviceType, transport); + + using var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey); + var result = session.GetCardRecognitionData(); + + Assert.True(result.Length > 0); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_GetData_Succeeds(StandardTestDevice desiredDeviceType, Transport transport) + { + var testDevice = GetDevice(desiredDeviceType, transport); + + using var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey); + var result = session.GetData(0x66); // Card Data + + Assert.True(result.Length > 0); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_PivSession_TryVerifyPinAndGetMetaData_Succeeds(StandardTestDevice desiredDeviceType, Transport transport) + { + var testDevice = GetDevice(desiredDeviceType, transport); + Assert.True(testDevice.FirmwareVersion >= FirmwareVersion.V5_3_0); + Assert.True(testDevice.HasFeature(YubiKeyFeature.Scp03)); + + using var pivSession = new PivSession(testDevice, Scp03KeyParameters.DefaultKey); + var result = pivSession.TryVerifyPin(_defaultPin, out _); + + Assert.True(result); + + var metadata = pivSession.GetMetadata(PivSlot.Pin)!; + Assert.Equal(3, metadata.RetryCount); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_Device_Connect_With_Application_Succeeds(StandardTestDevice desiredDeviceType, Transport transport) + { + var testDevice = GetDevice(desiredDeviceType, transport); + Assert.True(testDevice.FirmwareVersion >= FirmwareVersion.V5_3_0); + + using var connection = testDevice.Connect(YubiKeyApplication.Piv, Scp03KeyParameters.DefaultKey); + Assert.NotNull(connection); + + var cmd = new VerifyPinCommand(_defaultPin); + var rsp = connection!.SendCommand(cmd); + Assert.Equal(ResponseStatus.Success, rsp.Status); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_Device_Connect_ApplicationId_Succeeds(StandardTestDevice desiredDeviceType, Transport transport) + { + var testDevice = GetDevice(desiredDeviceType, transport); + Assert.True(testDevice.FirmwareVersion >= FirmwareVersion.V5_3_0); + + using IYubiKeyConnection connection = testDevice.Connect( + YubiKeyApplication.Piv.GetIso7816ApplicationId(), Scp03KeyParameters.DefaultKey); + + Assert.NotNull(connection); + + var cmd = new VerifyPinCommand(_defaultPin); + var rsp = connection!.SendCommand(cmd); + Assert.Equal(ResponseStatus.Success, rsp.Status); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_Device_TryConnect_With_Application_Succeeds(StandardTestDevice desiredDeviceType, Transport transport) + { + var testDevice = GetDevice(desiredDeviceType, transport); + Assert.True(testDevice.FirmwareVersion >= FirmwareVersion.V5_3_0); + + var isValid = testDevice.TryConnect( + YubiKeyApplication.Piv, + Scp03KeyParameters.DefaultKey, + out var connection); + + using (connection) + { + Assert.NotNull(connection); + Assert.True(isValid); + var cmd = new VerifyPinCommand(_defaultPin); + var rsp = connection!.SendCommand(cmd); + Assert.Equal(ResponseStatus.Success, rsp.Status); + } + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.UsbSmartCard)] + [InlineData(StandardTestDevice.Fw5Fips, Transport.NfcSmartCard)] + public void Scp03_Device_TryConnect_With_ApplicationId_Succeeds(StandardTestDevice desiredDeviceType, Transport transport) + { + var testDevice = GetDevice(desiredDeviceType, transport); + Assert.True(testDevice.FirmwareVersion >= FirmwareVersion.V5_3_0); + + var isValid = testDevice.TryConnect(YubiKeyApplication.Piv, Scp03KeyParameters.DefaultKey, + out var connection); + + using (connection) + { + Assert.True(isValid); + Assert.NotNull(connection); + var cmd = new VerifyPinCommand(_defaultPin); + var rsp = connection!.SendCommand(cmd); + Assert.Equal(ResponseStatus.Success, rsp.Status); + } + } + + #region Helpers + + private static StaticKeys RandomStaticKeys() => + new StaticKeys( + GetRandom16Bytes(), + GetRandom16Bytes(), + GetRandom16Bytes() + ); + + private static ReadOnlyMemory GetRandom16Bytes() + { + var buffer = new byte[16]; + Random.Shared.NextBytes(buffer); + return buffer; + } + + #endregion + } +} diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp/Scp11TestData.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp/Scp11TestData.cs new file mode 100644 index 000000000..e54d026ec --- /dev/null +++ b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp/Scp11TestData.cs @@ -0,0 +1,79 @@ +using System; +using System.Text; + +namespace Yubico.YubiKey.Scp +{ + public static class Scp11TestData + { + public readonly static ReadOnlyMemory OceCerts = Encoding.UTF8.GetBytes( + "-----BEGIN CERTIFICATE-----\n" + + "MIIB8DCCAZegAwIBAgIUf0lxsK1R+EydqZKLLV/vXhaykgowCgYIKoZIzj0EAwIw\n" + + "KjEoMCYGA1UEAwwfRXhhbXBsZSBPQ0UgUm9vdCBDQSBDZXJ0aWZpY2F0ZTAeFw0y\n" + + "NDA1MjgwOTIyMDlaFw0yNDA4MjYwOTIyMDlaMC8xLTArBgNVBAMMJEV4YW1wbGUg\n" + + "T0NFIEludGVybWVkaWF0ZSBDZXJ0aWZpY2F0ZTBZMBMGByqGSM49AgEGCCqGSM49\n" + + "AwEHA0IABMXbjb+Y33+GP8qUznrdZSJX9b2qC0VUS1WDhuTlQUfg/RBNFXb2/qWt\n" + + "h/a+Ag406fV7wZW2e4PPH+Le7EwS1nyjgZUwgZIwHQYDVR0OBBYEFJzdQCINVBES\n" + + "R4yZBN2l5CXyzlWsMB8GA1UdIwQYMBaAFDGqVWafYGfoHzPc/QT+3nPlcZ89MBIG\n" + + "A1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgIEMCwGA1UdIAEB/wQiMCAw\n" + + "DgYMKoZIhvxrZAAKAgEoMA4GDCqGSIb8a2QACgIBADAKBggqhkjOPQQDAgNHADBE\n" + + "AiBE5SpNEKDW3OehDhvTKT9g1cuuIyPdaXGLZ3iX0x0VcwIgdnIirhlKocOKGXf9\n" + + "ijkE8e+9dTazSPLf24lSIf0IGC8=\n" + + "-----END CERTIFICATE-----\n" + + "-----BEGIN CERTIFICATE-----\n" + + "MIIB2zCCAYGgAwIBAgIUSf59wIpCKOrNGNc5FMPTD9zDGVAwCgYIKoZIzj0EAwIw\n" + + "KjEoMCYGA1UEAwwfRXhhbXBsZSBPQ0UgUm9vdCBDQSBDZXJ0aWZpY2F0ZTAeFw0y\n" + + "NDA1MjgwOTIyMDlaFw0yNDA2MjcwOTIyMDlaMCoxKDAmBgNVBAMMH0V4YW1wbGUg\n" + + "T0NFIFJvb3QgQ0EgQ2VydGlmaWNhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n" + + "AASPrxfpSB/AvuvLKaCz1YTx68Xbtx8S9xAMfRGwzp5cXMdF8c7AWpUfeM3BQ26M\n" + + "h0WPvyBJKhCdeK8iVCaHyr5Jo4GEMIGBMB0GA1UdDgQWBBQxqlVmn2Bn6B8z3P0E\n" + + "/t5z5XGfPTASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBBjA8BgNV\n" + + "HSABAf8EMjAwMA4GDCqGSIb8a2QACgIBFDAOBgwqhkiG/GtkAAoCASgwDgYMKoZI\n" + + "hvxrZAAKAgEAMAoGCCqGSM49BAMCA0gAMEUCIHv8cgOzxq2n1uZktL9gCXSR85mk\n" + + "TieYeSoKZn6MM4rOAiEA1S/+7ez/gxDl01ztKeoHiUiW4FbEG4JUCzIITaGxVvM=\n" + + "-----END CERTIFICATE-----").AsMemory(); + + // PKCS12 certificate with a private key and full certificate chain + public static readonly Memory Oce = Convert.FromBase64String( + "MIIIfAIBAzCCCDIGCSqGSIb3DQEHAaCCCCMEgggfMIIIGzCCBtIGCSqGSIb3DQEHBqCCBsMwgga/" + + "AgEAMIIGuAYJKoZIhvcNAQcBMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAg8IcJO44iS" + + "gAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEAllIHdoQx/USA3jmRMeciiAggZQAHCP" + + "J5lzPV0Z5tnssXZZ1AWm8AcKEq28gWUTVqVxc+0EcbKQHig1Jx7rqC3q4G4sboIRw1vDH6q5O8eG" + + "sbkeNuYBim8fZ08JrsjeJABJoEiJrPqplMWA7H6a7athg3YSu1v4OR3UKN5Gyzn3s0Yx5yMm/xzw" + + "204TEK5/1LpK8AMcUliFSq7jw3Xl1RY0zjMSWyQjX0KmB9IdubqQCfhy8zkKluAQADtHsEYAn0F3" + + "LoMETQytyUSkIvGMZoFemkCWV7zZ5n5IPhXL7gvnTu0WS8UxEnz/+FYdF43cjmwGfSb3OpaxOND4" + + "PBCpwzbFfVCLa6mUBlwq1KQWRm1+PFm4LnL+3s2mxfjJAsVYP4U722/FHpW8rdTsyvdift9lsQja" + + "s2jIjCu8PFClFZJLQldu5FxOhKzx2gsjYS/aeTdefwjlRiGtEFSrE1snKBbnBeRYFocBjhTD/sy3" + + "Vj0i5sbWwTx7iq67joWydWAMp/lGSZ6akWRsyku/282jlwYsc3pR05qCHkbV0TzJcZofhXBwRgH5" + + "NKfulnJ1gH+i3e3RT3TauAKlqCeAfvDvA3+jxEDy/puPncod7WH0m9P4OmXjZ0s5EI4U+v6bKPgL" + + "7LlTCEI6yj15P7kxmruoxZlDAmhixVmlwJ8ZbVxD6Q+AOhXYPg+il3AYaRAS+VyJla0K+ac6hpYV" + + "AnbZCPzgHVkKC6iq4a/azf2b4uq9ks109jjnryAChdBsGdmStpZaPW4koMSAIJf12vGRp5jNjSax" + + "aIL5QxTn0WCO8FHi1oqTmlTSWvR8wwZLiBmqQtnNTpewiLL7C22lerUT7pYvKLCq/nnPYtb5UrST" + + "HrmTNOUzEGVOSAGUWV293S4yiPGIwxT3dPE5/UaU/yKq1RonMRaPhOZEESZEwLKVCqyDVEbAt7Hd" + + "ahp+Ex0FVrC5JQhpVQ0Wn6uCptF2Jup70u+P2kVWjxrGBuRrlgEkKuHcohWoO9EMX/bLK9KcY4s1" + + "ofnfgSNagsAyX7N51Bmahgz1MCFOEcuFa375QYQhqkyLO2ZkNTpFQtjHjX0izZWO55LN3rNpcD9+" + + "fZt6ldoZCpg+t6y5xqHy+7soH0BpxF1oGIHAUkYSuXpLY0M7Pt3qqvsJ4/ycmFUEyoGv8Ib/ieUB" + + "bebPz0Uhn+jaTpjgtKCyym7nBxVCuUv39vZ31nhNr4WaFsjdB/FOJh1s4KI6kQgzCSObrIVXBcLC" + + "TXPfZ3jWxspKIREHn+zNuW7jIkbugSRiNFfVArcc7cmU4av9JPSmFiZzeyA0gkrkESTg8DVPT16u" + + "7W5HREX4CwmKu+12R6iYQ/po9Hcy6NJ8ShLdAzU0+q/BzgH7Cb8qimjgfGBA3Mesc+P98FlCzAjB" + + "2EgucRuXuehM/FemmZyNl0qI1Mj9qOgx/HeYaJaYD+yXwojApmetFGtDtMJsDxwL0zK7eGXeHHa7" + + "pd7OybKdSjDq25CCTOZvfR0DD55FDIGCy0FsJTcferzPFlkz/Q45vEwuGfEBnXXS9IhH4ySvJmDm" + + "yfLMGiHW6t+9gjyEEg+dwSOq9yXYScfCsefRl7+o/9nDoNQ8s/XS7LKlJ72ZEBaKeAxcm6q4wVwU" + + "WITNNl1R3EYAsFBWzYt4Ka9Ob3igVaNfeG9K4pfQqMWcPpqVp4FuIsEpDWZYuv71s+WMYCs1JMfH" + + "bHDUczdRet1Ir2vLDGeWwvci70AzeKvvQ9OwBVESRec6cVrgt3EJWLey5sXY01WpMm526fwtLolS" + + "MpCf+dNePT97nXemQCcr3QXimagHTSGPngG3577FPrSQJl+lCJDYxBFFtnd6hq4OcVr5HiNAbLnS" + + "jBWbzqxhHMmgoojy4rwtHmrfyVYKXyl+98r+Lobitv2tpnBqmjL6dMPRBOJvQl8+Wp4MGBsi1gvT" + + "gW/+pLlMXT++1iYyxBeK9/AN5hfjtrivewE3JY531jwkrl3rUl50MKwBJMMAtQQIYrDg7DAg/+Qc" + + "Oi+2mgo9zJPzR2jIXF0wP+9FA4+MITa2v78QVXcesh63agcFJCayGAL1StnbSBvvDqK5vEei3uGZ" + + "beJEpU1hikQx57w3UzS9O7OSQMFvRBOrFBQsYC4JzfF0soIweGNpJxpm+UNYz+hB9vCb8+3OHA06" + + "9M0CAlJVOTF9uEpLVRzK+1kwggFBBgkqhkiG9w0BBwGgggEyBIIBLjCCASowggEmBgsqhkiG9w0B" + + "DAoBAqCB7zCB7DBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIexxrwNlHM34CAggAMAwG" + + "CCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBAkK96h6gHJglyJl1/yEylvBIGQh62z7u5RoQ9y5wIX" + + "bE3/oMQTKVfCSrtqGUmj38sxDY7yIoTVQq7sw0MPNeYHROgGUAzawU0DlXMGuOWrbgzYeURZs0/H" + + "Z2Cqk8qhVnD8TgpB2n0U0NB7aJRHlkzTl5MLFAwn3NE49CSzb891lGwfLYXYCfNfqltD7xZ7uvz6" + + "JAo/y6UtY8892wrRv4UdejyfMSUwIwYJKoZIhvcNAQkVMRYEFJBU0s1/6SLbIRbyeq65gLWqClWN" + + "MEEwMTANBglghkgBZQMEAgEFAAQgqkOJRTcBlnx5yn57k23PH+qUXUGPEuYkrGy+DzEQiikECB0B" + + "XjHOZZhuAgIIAA=="); + + public static readonly ReadOnlyMemory OcePassword = "password".AsMemory(); + } +} diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp/Scp11Tests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp/Scp11Tests.cs new file mode 100644 index 000000000..d4a48db8e --- /dev/null +++ b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp/Scp11Tests.cs @@ -0,0 +1,616 @@ +// Copyright 2024 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 System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Security; +using Xunit; +using Yubico.Core.Devices.Hid; +using Yubico.Core.Tlv; +using Yubico.YubiKey.Cryptography; +using Yubico.YubiKey.Oath; +using Yubico.YubiKey.Otp; +using Yubico.YubiKey.Piv; +using Yubico.YubiKey.TestUtilities; +using Yubico.YubiKey.YubiHsmAuth; +using ECCurve = System.Security.Cryptography.ECCurve; +using ECPoint = System.Security.Cryptography.ECPoint; + + +namespace Yubico.YubiKey.Scp +{ + [Trait(TraitTypes.Category, TestCategories.Simple)] + public class Scp11Tests + { + private const byte OceKid = 0x010; + + public Scp11Tests() + { + ResetAllowedDevices(); + } + + private IYubiKeyDevice GetDevice( + StandardTestDevice desiredDeviceType, + Transport transport = Transport.SmartCard, + FirmwareVersion? minimumFirmwareVersion = null) => + IntegrationTestDeviceEnumeration.GetTestDevice(desiredDeviceType, transport, + minimumFirmwareVersion ?? FirmwareVersion.V5_7_2); + + private static void ResetAllowedDevices() + { + // Reset all attached allowed devices + foreach (var availableDevice in IntegrationTestDeviceEnumeration.GetTestDevices()) + { + using var session = new SecurityDomainSession(availableDevice); + session.Reset(); + } + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void Scp11b_PivSession_Operations_Succeeds( + StandardTestDevice desiredDeviceType) + { + var testDevice = GetDevice(desiredDeviceType); + var keyReference = new KeyReference(ScpKeyIds.Scp11B, 0x1); + var keyParams = Get_Scp11b_EncryptedChannel_Parameters(testDevice, keyReference); + + using var session = new PivSession(testDevice, keyParams); + session.ResetApplication(); + + session.KeyCollector = new Simple39KeyCollector().Simple39KeyCollectorDelegate; + var isVerified = session.TryVerifyPin(); + Assert.True(isVerified); + + var result = session.GenerateKeyPair(PivSlot.Retired12, PivAlgorithm.EccP256); + Assert.Equal(PivAlgorithm.EccP256, result.Algorithm); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void Scp11b_OathSession_Operations_Succeeds( + StandardTestDevice desiredDeviceType) + { + var testDevice = GetDevice(desiredDeviceType); + var keyReference = new KeyReference(ScpKeyIds.Scp11B, 0x1); + var keyParams = Get_Scp11b_EncryptedChannel_Parameters(testDevice, keyReference); + + using (var resetSession = new OathSession(testDevice, keyParams)) + { + resetSession.ResetApplication(); + } + + using var session = new OathSession(testDevice, keyParams); + session.KeyCollector = new SimpleOathKeyCollector().SimpleKeyCollectorDelegate; + + session.SetPassword(); + Assert.True(session.IsPasswordProtected); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void Scp11b_OtpSession_Operations_Succeeds( + StandardTestDevice desiredDeviceType) + { + var testDevice = GetDevice(desiredDeviceType); + var keyReference = new KeyReference(ScpKeyIds.Scp11B, 0x1); + var keyParams = Get_Scp11b_EncryptedChannel_Parameters(testDevice, keyReference); + + using var session = new OtpSession(testDevice, keyParams); + if (session.IsLongPressConfigured) + { + session.DeleteSlot(Slot.LongPress); + } + + var configObj = session.ConfigureStaticPassword(Slot.LongPress); + var generatedPassword = new Memory(new char[16]); + configObj = configObj.WithKeyboard(KeyboardLayout.en_US); + configObj = configObj.GeneratePassword(generatedPassword); + + configObj.Execute(); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void Scp11b_YubiHsmSession_Operations_Succeeds( + StandardTestDevice desiredDeviceType) + { + var testDevice = YhaTestUtilities.GetCleanDevice(desiredDeviceType); + var keyReference = new KeyReference(ScpKeyIds.Scp11B, 0x1); + var keyParams = Get_Scp11b_EncryptedChannel_Parameters(testDevice, keyReference); + + using var session = new YubiHsmAuthSession(testDevice, keyParams); + session.AddCredential(YhaTestUtilities.DefaultMgmtKey, YhaTestUtilities.DefaultAes128Cred); + + var result = session.ListCredentials(); + Assert.Single(result); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void Scp11b_Establish_Connection_Succeeds( + StandardTestDevice desiredDeviceType) + { + var testDevice = GetDevice(desiredDeviceType); + var keyReference = new KeyReference(ScpKeyIds.Scp11B, 0x1); + + IReadOnlyCollection certificateList; + using (var session = new SecurityDomainSession(testDevice)) + { + certificateList = session.GetCertificates(keyReference); + } + + var leaf = certificateList.Last(); + var ecDsaPublicKey = leaf.PublicKey.GetECDsaPublicKey()!.ExportParameters(false); + var keyParams = new Scp11KeyParameters(keyReference, new ECPublicKeyParameters(ecDsaPublicKey)); + + using (var session = new SecurityDomainSession(testDevice, keyParams)) + { + var result = session.GetKeyInformation(); + Assert.NotEmpty(result); + } + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void Scp11b_Import_Succeeds( + StandardTestDevice desiredDeviceType) + { + var testDevice = GetDevice(desiredDeviceType); + var keyReference = new KeyReference(ScpKeyIds.Scp11B, 0x2); + + // Start authenticated session with default key + using var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey); + + // Import private key + var ecDsa = ECDsa.Create(ECCurve.NamedCurves.nistP256); + var privateKey = new ECPrivateKeyParameters(ecDsa); + session.PutKey(keyReference, privateKey, 0); + + var result = session.GetKeyInformation(); + Assert.NotEmpty(result); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void Scp11b_GetCertificates_IsNotEmpty( + StandardTestDevice desiredDeviceType) + { + var testDevice = GetDevice(desiredDeviceType); + using var session = new SecurityDomainSession(testDevice); + + var keyReference = new KeyReference(ScpKeyIds.Scp11B, 0x1); + var certificateList = session.GetCertificates(keyReference); + + Assert.NotEmpty(certificateList); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void Scp11b_StoreCertificates_CanBeRetrieved( + StandardTestDevice desiredDeviceType) + { + var testDevice = GetDevice(desiredDeviceType); + var keyReference = new KeyReference(ScpKeyIds.Scp11B, 0x1); + + using var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey); + var oceCertificates = GetOceCertificates(Scp11TestData.OceCerts.Span); + + session.StoreCertificates(keyReference, oceCertificates.Bundle); + var result = session.GetCertificates(keyReference); + + // Assert that we can store and retrieve the off card entity certificate + var oceThumbprint = oceCertificates.Bundle.Single().Thumbprint; + Assert.Single(result); + Assert.Equal(oceThumbprint, result[0].Thumbprint); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void Scp11a_GenerateEcKey_Succeeds( + StandardTestDevice desiredDeviceType) + { + var testDevice = GetDevice(desiredDeviceType); + var keyReference = new KeyReference(ScpKeyIds.Scp11A, 0x3); + + // Start authenticated session + using var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey); + + // Generate a new EC key + var generatedKey = session.GenerateEcKey(keyReference, 0); + + // Verify the generated key + Assert.NotNull(generatedKey.Parameters.Q.X); + Assert.NotNull(generatedKey.Parameters.Q.Y); + Assert.Equal(32, generatedKey.Parameters.Q.X.Length); + Assert.Equal(32, generatedKey.Parameters.Q.Y.Length); + Assert.Equal(ECCurve.NamedCurves.nistP256.Oid.Value, generatedKey.Parameters.Curve.Oid.Value); + + using var ecdsa = ECDsa.Create(generatedKey.Parameters); + Assert.NotNull(ecdsa); + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void Scp11a_WithAllowList_AllowsApprovedSerials( + StandardTestDevice desiredDeviceType) + { + var testDevice = GetDevice(desiredDeviceType); + const byte kvn = 0x05; + var oceKeyRef = new KeyReference(OceKid, kvn); + + Scp11KeyParameters keyParams; + using (var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey)) + { + keyParams = LoadKeys(session, ScpKeyIds.Scp11A, kvn); + } + + using (var session = new SecurityDomainSession(testDevice, keyParams)) + { + var serials = new List + { + // Serial numbers from oce certs + "7F4971B0AD51F84C9DA9928B2D5FEF5E16B2920A", + "6B90028800909F9FFCD641346933242748FBE9AD" + }; + + // Only the above serials shall work. + session.StoreAllowlist(oceKeyRef, serials); + } + + using (var session = new SecurityDomainSession(testDevice, keyParams)) + { + session.DeleteKey(new KeyReference(ScpKeyIds.Scp11A, kvn)); + } + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void Scp11a_WithAllowList_BlocksUnapprovedSerials( + StandardTestDevice desiredDeviceType) + { + var testDevice = GetDevice(desiredDeviceType); + const byte kvn = 0x03; + var oceKeyRef = new KeyReference(OceKid, kvn); + + Scp03KeyParameters scp03KeyParams; + using (var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey)) + { + // Import SCP03 key and get key parameters + scp03KeyParams = ImportScp03Key(session); + } + + Scp11KeyParameters scp11KeyParams; + using (var session = new SecurityDomainSession(testDevice, scp03KeyParams)) + { + // Make space for new key + session.DeleteKey(new KeyReference(ScpKeyIds.Scp11B, 0x01), false); + + // Load SCP11a keys + scp11KeyParams = LoadKeys(session, ScpKeyIds.Scp11A, kvn); + + // Create list of serial numbers + var serials = new List + { + "01", + "02", + "03" + }; + + // Store the allow list + session.StoreAllowlist(oceKeyRef, serials); + } + + // This is the test. Authenticate with SCP11a should throw. + Assert.Throws(() => + { + using (var session = new SecurityDomainSession(testDevice, scp11KeyParams)) + { + // ... Authenticated + } + }); + + // Reset the allow list + using (var session = new SecurityDomainSession(testDevice, scp03KeyParams)) + { + session.ClearAllowList(oceKeyRef); + } + + // Now, with the allowlist removed, authenticate with SCP11a should now succeed + using (var session = new SecurityDomainSession(testDevice, scp11KeyParams)) + { + // ... Authenticated + } + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void Scp11a_Authenticate_Succeeds( + StandardTestDevice desiredDeviceType) + { + var testDevice = GetDevice(desiredDeviceType); + const byte kvn = 0x03; + var keyRef = new KeyReference(ScpKeyIds.Scp11A, kvn); + + // Start authenticated session with default key + Scp11KeyParameters keyParams; + using (var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey)) + { + keyParams = LoadKeys(session, ScpKeyIds.Scp11A, kvn); + } + + // Start authenticated session using new key params and public key from yubikey + using (var session = new SecurityDomainSession(testDevice, keyParams)) + { + session.DeleteKey(keyRef); + } + } + + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + public void Scp11c_Authenticate_Succeeds( + StandardTestDevice desiredDeviceType) + { + var testDevice = GetDevice(desiredDeviceType); + const byte kvn = 0x03; + var keyReference = new KeyReference(ScpKeyIds.Scp11C, kvn); + + Scp11KeyParameters keyParams; + using (var session = new SecurityDomainSession(testDevice, Scp03KeyParameters.DefaultKey)) + { + keyParams = LoadKeys(session, ScpKeyIds.Scp11C, kvn); + } + + Assert.Throws(() => + { + using var session = new SecurityDomainSession(testDevice, keyParams); + session.DeleteKey(keyReference); + }); + } + + private Scp11KeyParameters LoadKeys( + SecurityDomainSession session, + byte scpKid, + byte kvn) + { + var sessionRef = new KeyReference(scpKid, kvn); + var oceRef = new KeyReference(OceKid, kvn); + + // Generate new key pair on YubiKey and store public key for later use + var newPublicKey = session.GenerateEcKey(sessionRef, 0); + + var oceCerts = GetOceCertificates(Scp11TestData.OceCerts.Span); + if (oceCerts.Ca == null) + { + throw new InvalidOperationException("Missing CA certificate"); + } + + // Put Oce Keys + var ocePublicKey = new ECPublicKeyParameters(oceCerts.Ca.PublicKey.GetECDsaPublicKey()!); + session.PutKey(oceRef, ocePublicKey, 0); + + // Get Oce subject key identifier + var ski = GetSki(oceCerts.Ca); + if (ski.IsEmpty) + { + throw new InvalidOperationException("CA certificate missing Subject Key Identifier"); + } + + // Store the key identifier with the referenced off card entity on the Yubikey + session.StoreCaIssuer(oceRef, ski); + + var (certChain, privateKey) = + GetOceCertificateChainAndPrivateKey(Scp11TestData.Oce, Scp11TestData.OcePassword); + + // Now we have the EC private key parameters and cert chain + return new Scp11KeyParameters( + sessionRef, + new ECPublicKeyParameters(newPublicKey.Parameters), + oceRef, + new ECPrivateKeyParameters(privateKey), + certChain + ); + } + + private static (List certChain, ECParameters privateKey) GetOceCertificateChainAndPrivateKey( + ReadOnlyMemory ocePkcs12, + ReadOnlyMemory ocePassword) + { + // Load the OCE PKCS12 using Bouncy Castle Pkcs12 Store + using var pkcsStream = new MemoryStream(ocePkcs12.ToArray()); + var pkcs12Store = new Pkcs12Store(pkcsStream, ocePassword.ToArray()); + + // Get the first alias (usually there's only one) + var alias = pkcs12Store.Aliases.Cast().FirstOrDefault(); + if (alias == null || !pkcs12Store.IsKeyEntry(alias)) + { + throw new InvalidOperationException("No private key entry found in PKCS12"); + } + + // Get the certificate chain + var x509CertificateEntries = pkcs12Store.GetCertificateChain(alias); + var x509Certs = x509CertificateEntries + .Select(certEntry => + { + var cert = DotNetUtilities.ToX509Certificate(certEntry.Certificate); + return new X509Certificate2( + cert.Export(X509ContentType.Cert) + ); + }); + + var certs = ScpCertificates.From(x509Certs); + var certChain = new List(certs.Bundle); + if (certs.Leaf != null) + { + certChain.Add(certs.Leaf); + } + + // Get the private key + var privateKeyEntry = pkcs12Store.GetKey(alias); + if (!(privateKeyEntry.Key is Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters ecPrivateKey)) + { + throw new InvalidOperationException("Private key is not an EC key"); + } + + return (certChain, ConvertToECParameters(ecPrivateKey)); + } + + static ECParameters ConvertToECParameters( + Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters bcPrivateKey) + { + // Convert the BigInteger D to byte array + var dBytes = bcPrivateKey.D.ToByteArrayUnsigned(); + + // Calculate public key point Q = d*G + var Q = bcPrivateKey.Parameters.G.Multiply(bcPrivateKey.D); + + // Get X and Y coordinates as byte arrays + var xBytes = Q.XCoord.ToBigInteger().ToByteArrayUnsigned(); + var yBytes = Q.YCoord.ToBigInteger().ToByteArrayUnsigned(); + + // Create ECParameters with P-256 curve + return new ECParameters + { + Curve = ECCurve.NamedCurves.nistP256, + D = dBytes, + Q = new ECPoint + { + X = xBytes, + Y = yBytes + } + }; + } + + private ScpCertificates GetOceCertificates( + ReadOnlySpan pem) + { + try + { + var certificates = new List(); + + // Convert PEM to a string + var pemString = Encoding.UTF8.GetString(pem); + + // Split the PEM string into individual certificates + var pemCerts = pemString.Split( + new[] { "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----" }, + StringSplitOptions.RemoveEmptyEntries + ); + + foreach (var certString in pemCerts) + { + if (!string.IsNullOrWhiteSpace(certString)) + { + // Remove any whitespace and convert to byte array + var certData = Convert.FromBase64String(certString.Trim()); + var cert = new X509Certificate2(certData); + certificates.Add(cert); + } + } + + return ScpCertificates.From(certificates); + } + catch (Exception ex) + { + throw new InvalidOperationException("Failed to parse PEM certificates", ex); + } + } + + private Memory GetSki( + X509Certificate2 certificate) + { + var extension = certificate.Extensions["2.5.29.14"]; + if (!(extension is X509SubjectKeyIdentifierExtension skiExtension)) + { + throw new InvalidOperationException("Invalid Subject Key Identifier extension"); + } + + var rawData = skiExtension.RawData; + if (rawData == null || rawData.Length == 0) + { + throw new InvalidOperationException("Missing Subject Key Identifier"); + } + + var tlv = TlvObject.Parse(skiExtension.RawData); + return tlv.Value; + } + + private static Scp03KeyParameters ImportScp03Key( + SecurityDomainSession session) + { + var scp03Ref = new KeyReference(0x01, 0x01); + var staticKeys = new StaticKeys( + GetRandomBytes(16), + GetRandomBytes(16), + GetRandomBytes(16) + ); + + session.PutKey(scp03Ref, staticKeys, 0); + + return new Scp03KeyParameters(scp03Ref, staticKeys); + } + + private static Memory GetRandomBytes( + byte length) + { + using var rng = CryptographyProviders.RngCreator(); + Span hostChallenge = stackalloc byte[length]; + rng.GetBytes(hostChallenge); + + return hostChallenge.ToArray(); + } + + /// + /// This is a copy of Scp11b_Authenticate_Succeeds test + /// + /// + /// + /// + private static Scp11KeyParameters Get_Scp11b_EncryptedChannel_Parameters( + IYubiKeyDevice testDevice, + KeyReference keyReference) + { + IReadOnlyCollection certificateList; + using (var session = new SecurityDomainSession(testDevice)) + { + certificateList = session.GetCertificates(keyReference); + } + + var leaf = certificateList.Last(); + var ecDsaPublicKey = leaf.PublicKey.GetECDsaPublicKey()!.ExportParameters(false); + var keyParams = new Scp11KeyParameters(keyReference, new ECPublicKeyParameters(ecDsaPublicKey)); + + return keyParams; + } + } +} diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp/ScpCertificates.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp/ScpCertificates.cs new file mode 100644 index 000000000..87197bab2 --- /dev/null +++ b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp/ScpCertificates.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; + +namespace Yubico.YubiKey.Scp +{ + public class ScpCertificates + { + public X509Certificate2? Ca { get; } + public IReadOnlyList Bundle { get; } + public X509Certificate2? Leaf { get; } + + private ScpCertificates(X509Certificate2? ca, IReadOnlyList bundle, X509Certificate2? leaf) + { + Ca = ca; + Bundle = bundle ?? throw new ArgumentNullException(nameof(bundle)); + Leaf = leaf; + } + + public static ScpCertificates From(IEnumerable? certificates) + { + if (certificates == null || !certificates.Any()) + { + return new ScpCertificates(null, Array.Empty(), null); + } + + var certList = certificates.ToList(); + X509Certificate2? ca = null; + byte[]? seenSerial = null; + + // Order certificates with the Root CA on top + var ordered = new List { certList[0] }; + certList.RemoveAt(0); + + while (certList.Count > 0) + { + var head = ordered[0]; + var tail = ordered[^1]; + var cert = certList[0]; + certList.RemoveAt(0); + + if (IsIssuedBy(cert, cert)) + { + ordered.Insert(0, cert); + ca = ordered[0]; + continue; + } + + if (IsIssuedBy(cert, tail)) + { + ordered.Add(cert); + continue; + } + + if (IsIssuedBy(head, cert)) + { + ordered.Insert(0, cert); + continue; + } + + if (seenSerial != null && cert.GetSerialNumber().SequenceEqual(seenSerial)) + { + throw new InvalidOperationException($"Cannot decide the order of {cert} in {string.Join(", ", ordered)}"); + } + + // This cert could not be ordered, try to process rest of certificates + // but if you see this cert again fail because the cert chain is not complete + certList.Add(cert); + seenSerial = cert.GetSerialNumber(); + } + + // Find ca and leaf + if (ca != null) + { + ordered.RemoveAt(0); + } + + X509Certificate2? leaf = null; + if (ordered.Count > 0) + { + var lastCert = ordered[^1]; + var keyUsage = lastCert.Extensions.OfType().FirstOrDefault()?.KeyUsages ?? default; + if (keyUsage.HasFlag(X509KeyUsageFlags.DigitalSignature)) + { + leaf = lastCert; + ordered.RemoveAt(ordered.Count - 1); + } + } + + return new ScpCertificates(ca, ordered, leaf); + } + + private static bool IsIssuedBy(X509Certificate2 subjectCert, X509Certificate2 issuerCert) + { + return subjectCert.IssuerName.RawData.SequenceEqual(issuerCert.SubjectName.RawData); + } + } +} diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp03/Commands/DeleteKeyCommandTests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp03/Commands/DeleteKeyCommandTests.cs deleted file mode 100644 index 46b3245aa..000000000 --- a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp03/Commands/DeleteKeyCommandTests.cs +++ /dev/null @@ -1,113 +0,0 @@ -// 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 Xunit; -using Yubico.YubiKey.TestUtilities; - -namespace Yubico.YubiKey.Scp03.Commands -{ - [Trait(TraitTypes.Category, TestCategories.Simple)] - public class DeleteKeyCommandTests - { - [SkippableTheory(typeof(DeviceNotFoundException))] - [InlineData(StandardTestDevice.Fw5Fips)] - [InlineData(StandardTestDevice.Fw5)] - public void DeleteKey_One_Succeeds(StandardTestDevice testDeviceType) - { - byte[] key1 = { - 0x33, 0xff, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, - }; - byte[] key2 = { - 0x33, 0xee, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xff, - }; - byte[] key3 = { - 0x33, 0xdd, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xee, 0xff, 0x11, - }; - var currentKeys = new StaticKeys(key2, key1, key3) - { - KeyVersionNumber = 3 - }; - - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - var isValid = testDevice.TryConnectScp03(YubiKeyApplication.Scp03, currentKeys, out IScp03YubiKeyConnection? connection); - - Assert.True(isValid); - Assert.NotNull(connection); - - var cmd = new DeleteKeyCommand(1, false); - Scp03Response rsp = connection!.SendCommand(cmd); - Assert.Equal(ResponseStatus.Success, rsp.Status); - } - - [SkippableTheory(typeof(DeviceNotFoundException))] - [InlineData(StandardTestDevice.Fw5Fips)] - [InlineData(StandardTestDevice.Fw5)] - public void DeleteKey_Two_Succeeds(StandardTestDevice testDeviceType) - { - byte[] key1 = { - 0x33, 0xff, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, - }; - byte[] key2 = { - 0x33, 0xee, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xff, - }; - byte[] key3 = { - 0x33, 0xdd, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xee, 0xff, 0x11, - }; - var currentKeys = new StaticKeys(key2, key1, key3) - { - KeyVersionNumber = 3 - }; - - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - var isValid = testDevice.TryConnectScp03(YubiKeyApplication.Scp03, currentKeys, out IScp03YubiKeyConnection? connection); - - Assert.True(isValid); - Assert.NotNull(connection); - - var cmd = new DeleteKeyCommand(2, false); - Scp03Response rsp = connection!.SendCommand(cmd); - Assert.Equal(ResponseStatus.Success, rsp.Status); - } - - [SkippableTheory(typeof(DeviceNotFoundException))] - [InlineData(StandardTestDevice.Fw5Fips)] - [InlineData(StandardTestDevice.Fw5)] - public void DeleteKey_Three_Succeeds(StandardTestDevice testDeviceType) - { - byte[] key1 = { - 0x33, 0xff, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, - }; - byte[] key2 = { - 0x33, 0xee, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xff, - }; - byte[] key3 = { - 0x33, 0xdd, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xee, 0xff, 0x11, - }; - var currentKeys = new StaticKeys(key2, key1, key3) - { - KeyVersionNumber = 3 - }; - - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - var isValid = testDevice.TryConnectScp03(YubiKeyApplication.Scp03, currentKeys, out IScp03YubiKeyConnection? connection); - - Assert.True(isValid); - Assert.NotNull(connection); - - var cmd = new DeleteKeyCommand(3, true); - Scp03Response rsp = connection!.SendCommand(cmd); - Assert.Equal(ResponseStatus.Success, rsp.Status); - } - } -} diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp03/Commands/PutKeyCommandTests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp03/Commands/PutKeyCommandTests.cs deleted file mode 100644 index 52c67645c..000000000 --- a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp03/Commands/PutKeyCommandTests.cs +++ /dev/null @@ -1,147 +0,0 @@ -// 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 Xunit; -using Yubico.YubiKey.TestUtilities; - -namespace Yubico.YubiKey.Scp03.Commands -{ - public class PutKeyCommandTests - { - // These may require that DeleteKeyCommandTests have been run first. - [SkippableTheory(typeof(DeviceNotFoundException))] - [InlineData(StandardTestDevice.Fw5Fips)] - [InlineData(StandardTestDevice.Fw5)] - public void ChangeDefaultKey_Succeeds(StandardTestDevice testDeviceType) - { - byte[] key1 = { - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff - }; - byte[] key2 = { - 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11 - }; - byte[] key3 = { - 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11, 0x22 - }; - - var currentKeys = new StaticKeys(); - var newKeys = new StaticKeys(key2, key1, key3); - - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - var isValid = testDevice.TryConnectScp03(YubiKeyApplication.Scp03, currentKeys, out IScp03YubiKeyConnection? connection); - - Assert.True(isValid); - Assert.NotNull(connection); - - var cmd = new PutKeyCommand(currentKeys, newKeys); - PutKeyResponse rsp = connection!.SendCommand(cmd); - Assert.Equal(ResponseStatus.Success, rsp.Status); - ReadOnlyMemory checksum = rsp.GetData(); - bool isEqual = checksum.Span.SequenceEqual(cmd.ExpectedChecksum.Span); - Assert.True(isEqual); - } - - [SkippableTheory(typeof(DeviceNotFoundException))] - [InlineData(StandardTestDevice.Fw5Fips)] - [InlineData(StandardTestDevice.Fw5)] - public void AddNewKeySet_Succeeds(StandardTestDevice testDeviceType) - { - byte[] key1 = { - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff - }; - byte[] key2 = { - 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11 - }; - byte[] key3 = { - 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11, 0x22 - }; - byte[] newKey1 = { - 0xff, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, - }; - byte[] newKey2 = { - 0xee, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xff, 0x11 - }; - byte[] newKey3 = { - 0xdd, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xee, 0xff, 0x11, 0x22 - }; - - var currentKeys = new StaticKeys(key2, key1, key3); - var newKeys = new StaticKeys(newKey2, newKey1, newKey3) - { - KeyVersionNumber = 2 - }; - - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - var isValid = testDevice.TryConnectScp03(YubiKeyApplication.Scp03, currentKeys, out IScp03YubiKeyConnection? connection); - - Assert.True(isValid); - Assert.NotNull(connection); - - var cmd = new PutKeyCommand(currentKeys, newKeys); - PutKeyResponse rsp = connection!.SendCommand(cmd); - Assert.Equal(ResponseStatus.Success, rsp.Status); - ReadOnlyMemory checksum = rsp.GetData(); - bool isEqual = checksum.Span.SequenceEqual(cmd.ExpectedChecksum.Span); - Assert.True(isEqual); - } - - [SkippableTheory(typeof(DeviceNotFoundException))] - [InlineData(StandardTestDevice.Fw5Fips)] - [InlineData(StandardTestDevice.Fw5)] - public void AddThirdKeySet_Succeeds(StandardTestDevice testDeviceType) - { - byte[] key1 = { - 0xff, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, - }; - byte[] key2 = { - 0xee, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xff, 0x11 - }; - byte[] key3 = { - 0xdd, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xee, 0xff, 0x11, 0x22 - }; - byte[] newKey1 = { - 0x33, 0xff, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, - }; - byte[] newKey2 = { - 0x33, 0xee, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xff, - }; - byte[] newKey3 = { - 0x33, 0xdd, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xee, 0xff, 0x11, - }; - - var currentKeys = new StaticKeys(key2, key1, key3) - { - KeyVersionNumber = 2 - }; - var newKeys = new StaticKeys(newKey2, newKey1, newKey3) - { - KeyVersionNumber = 3 - }; - - IYubiKeyDevice testDevice = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - var isValid = testDevice.TryConnectScp03(YubiKeyApplication.Scp03, currentKeys, out IScp03YubiKeyConnection? connection); - - Assert.True(isValid); - Assert.NotNull(connection); - - var cmd = new PutKeyCommand(currentKeys, newKeys); - PutKeyResponse rsp = connection!.SendCommand(cmd); - Assert.Equal(ResponseStatus.Success, rsp.Status); - ReadOnlyMemory checksum = rsp.GetData(); - bool isEqual = checksum.Span.SequenceEqual(cmd.ExpectedChecksum.Span); - Assert.True(isEqual); - } - } -} diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp03/PutDeleteKeyTests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp03/PutDeleteKeyTests.cs deleted file mode 100644 index 97839d4b3..000000000 --- a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp03/PutDeleteKeyTests.cs +++ /dev/null @@ -1,153 +0,0 @@ -// 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 Xunit; -using Yubico.YubiKey.TestUtilities; - -namespace Yubico.YubiKey.Scp03 -{ - // These may require that DeleteKeyCommandTests have been run first. - [TestCaseOrderer(PriorityOrderer.TypeName, PriorityOrderer.AssembyName)] - public class PutDeleteTests - { - [Fact] - [TestPriority(3)] - public void PutKey_Succeeds() - { - using var staticKeys = new StaticKeys(); - IYubiKeyDevice device = IntegrationTestDeviceEnumeration.GetTestDevice( - Transport.SmartCard, - minimumFirmwareVersion: FirmwareVersion.V5_3_0); - - using (var scp03Session = new Scp03Session(device, staticKeys)) - { - using StaticKeys newKeys = GetKeySet(1); - scp03Session.PutKeySet(newKeys); - - using StaticKeys nextKeys = GetKeySet(2); - scp03Session.PutKeySet(nextKeys); - } - - using StaticKeys keySet2 = GetKeySet(2); - - using (var scp03Session = new Scp03Session(device, keySet2)) - { - using StaticKeys keySet3 = GetKeySet(3); - scp03Session.PutKeySet(keySet3); - } - } - - [Fact] - [TestPriority(3)] - public void ReplaceKey_Succeeds() - { - using StaticKeys staticKeys = GetKeySet(2); - IYubiKeyDevice device = IntegrationTestDeviceEnumeration.GetTestDevice( - Transport.SmartCard, - FirmwareVersion.V5_3_0); - - using (var scp03Session = new Scp03Session(device, staticKeys)) - { - using StaticKeys newKeys = GetKeySet(1); - newKeys.KeyVersionNumber = 2; - scp03Session.PutKeySet(newKeys); - } - } - - [Fact] - [TestPriority(0)] - public void DeleteKey_Succeeds() - { - using StaticKeys staticKeys = GetKeySet(3); - IYubiKeyDevice device = IntegrationTestDeviceEnumeration.GetTestDevice( - Transport.SmartCard, - FirmwareVersion.V5_3_0); - - using var scp03Session = new Scp03Session(device, staticKeys); - scp03Session.DeleteKeySet(1); - scp03Session.DeleteKeySet(2); - - scp03Session.DeleteKeySet(3, true); - } - - // The setNumber is to be 1, 2, or 3 - private StaticKeys GetKeySet(int setNumber) => setNumber switch - { - 1 => GetKeySet1(), - 2 => GetKeySet2(), - _ => GetKeySet3() - }; - - private StaticKeys GetKeySet1() - { - var key1 = new ReadOnlyMemory(new byte[] - { - 0x11, 0x11, 0x11, 0x11, 0x49, 0x2f, 0x4d, 0x09, 0x22, 0xec, 0x3d, 0xb4, 0x6b, 0x20, 0x94, 0x7a - }); - var key2 = new ReadOnlyMemory(new byte[] - { - 0x12, 0x12, 0x12, 0x12, 0x53, 0xB3, 0xE3, 0x78, 0x2A, 0x1D, 0xE5, 0xDC, 0x5A, 0xF4, 0xa6, 0x41 - }); - var key3 = new ReadOnlyMemory(new byte[] - { - 0x13, 0x13, 0x13, 0x13, 0x68, 0xDE, 0x7A, 0xB7, 0x74, 0x19, 0xBB, 0x7F, 0xB0, 0x55, 0x7d, 0x40 - }); - - return new StaticKeys(key2, key1, key3); - } - - private StaticKeys GetKeySet2() - { - var key1 = new ReadOnlyMemory(new byte[] - { - 0x21, 0x21, 0x21, 0x21, 0x20, 0x94, 0x7a, 0x49, 0x2f, 0x4d, 0x09, 0x22, 0xec, 0x3d, 0xb4, 0x6b - }); - var key2 = new ReadOnlyMemory(new byte[] - { - 0x22, 0x22, 0x22, 0x22, 0xDC, 0x5A, 0xF4, 0xa6, 0x41, 0x53, 0xB3, 0xE3, 0x78, 0x2A, 0x1D, 0xE5 - }); - var key3 = new ReadOnlyMemory(new byte[] - { - 0x23, 0x23, 0x23, 0x23, 0x7d, 0x40, 0x68, 0xDE, 0x7A, 0xB7, 0x74, 0x19, 0xBB, 0x7F, 0xB0, 0x55 - }); - - return new StaticKeys(key2, key1, key3) - { - KeyVersionNumber = 2 - }; - } - - private StaticKeys GetKeySet3() - { - var key1 = new ReadOnlyMemory(new byte[] - { - 0x21, 0x21, 0x21, 0x21, 0x20, 0xDC, 0x5A, 0xF4, 0xa6, 0x41, 0x94, 0x7a, 0x49, 0x2f, 0x4d, 0x09 - }); - var key2 = new ReadOnlyMemory(new byte[] - { - 0x22, 0x22, 0x22, 0x22, 0x22, 0xec, 0x3d, 0xb4, 0x6b, 0x53, 0xB3, 0xE3, 0x78, 0x2A, 0x1D, 0xE5 - }); - var key3 = new ReadOnlyMemory(new byte[] - { - 0x23, 0x23, 0x23, 0x23, 0x7A, 0xB7, 0x74, 0x19, 0x7d, 0x40, 0x68, 0xDE, 0xBB, 0x7F, 0xB0, 0x55 - }); - - return new StaticKeys(key2, key1, key3) - { - KeyVersionNumber = 3 - }; - } - } -} diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp03/SimpleSessionTests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp03/SimpleSessionTests.cs deleted file mode 100644 index 9adcf96df..000000000 --- a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Scp03/SimpleSessionTests.cs +++ /dev/null @@ -1,119 +0,0 @@ -// 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 Xunit; -using Yubico.YubiKey.Piv; -using Yubico.YubiKey.Piv.Commands; -using Yubico.YubiKey.TestUtilities; - -namespace Yubico.YubiKey.Scp03 -{ - public class SimpleSessionTests - { - private readonly byte[] _pin = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }; - - [Theory] - [InlineData(StandardTestDevice.Fw5)] - public void SessionSetupAndUse_Succeeds(StandardTestDevice testDeviceType) - { - IYubiKeyDevice device = IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType); - Assert.True(device.FirmwareVersion >= FirmwareVersion.V5_3_0); - Assert.True(device.HasFeature(YubiKeyFeature.Scp03)); - -#pragma warning disable CS0618 // Specifically testing this soon-to-be-deprecated feature - IYubiKeyDevice scp03Device = (device as YubiKeyDevice)!.WithScp03(new StaticKeys()); -#pragma warning restore CS0618 - - using var piv = new PivSession(scp03Device); - bool result = piv.TryVerifyPin(new ReadOnlyMemory(new byte[] { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }), out _); - Assert.True(result); - - PivMetadata metadata = piv.GetMetadata(PivSlot.Pin)!; - Assert.Equal(3, metadata.RetryCount); - } - - [Fact] - public void ConnectScp03_Application_Succeeds() - { - IYubiKeyDevice device = IntegrationTestDeviceEnumeration.GetTestDevice( - Transport.SmartCard, FirmwareVersion.V5_3_0); - - using var scp03Keys = new StaticKeys(); - - using IYubiKeyConnection connection = device.ConnectScp03(YubiKeyApplication.Piv, scp03Keys); - Assert.NotNull(connection); - - var cmd = new VerifyPinCommand(_pin); - VerifyPinResponse rsp = connection!.SendCommand(cmd); - Assert.Equal(ResponseStatus.Success, rsp.Status); - } - - [Fact] - public void ConnectScp03_AlgorithmId_Succeeds() - { - IYubiKeyDevice device = IntegrationTestDeviceEnumeration.GetTestDevice( - Transport.SmartCard, FirmwareVersion.V5_3_0); - - using var scp03Keys = new StaticKeys(); - - using IYubiKeyConnection connection = device.ConnectScp03( - YubiKeyApplication.Piv.GetIso7816ApplicationId(), scp03Keys); - Assert.NotNull(connection); - - var cmd = new VerifyPinCommand(_pin); - VerifyPinResponse rsp = connection!.SendCommand(cmd); - Assert.Equal(ResponseStatus.Success, rsp.Status); - } - - [Fact] - public void TryConnectScp03_Application_Succeeds() - { - IYubiKeyDevice device = IntegrationTestDeviceEnumeration.GetTestDevice( - Transport.SmartCard, FirmwareVersion.V5_3_0); - - using var scp03Keys = new StaticKeys(); - - bool isValid = device.TryConnectScp03(YubiKeyApplication.Piv, scp03Keys, out IScp03YubiKeyConnection? connection); - using (connection) - { - Assert.NotNull(connection); - Assert.True(isValid); - var cmd = new VerifyPinCommand(_pin); - VerifyPinResponse rsp = connection!.SendCommand(cmd); - Assert.Equal(ResponseStatus.Success, rsp.Status); - } - } - - [Fact] - public void TryConnectScp03_AlgorithmId_Succeeds() - { - IYubiKeyDevice device = IntegrationTestDeviceEnumeration.GetTestDevice( - Transport.SmartCard, FirmwareVersion.V5_3_0); - - using var scp03Keys = new StaticKeys(); - - bool isValid = device.TryConnectScp03( - YubiKeyApplication.Piv.GetIso7816ApplicationId(), scp03Keys, out IScp03YubiKeyConnection? connection); - using (connection) - { - Assert.NotNull(connection); - Assert.True(isValid); - var cmd = new VerifyPinCommand(_pin); - VerifyPinResponse rsp = connection!.SendCommand(cmd); - Assert.Equal(ResponseStatus.Success, rsp.Status); - } - } - } -} diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/YubiHsmAuth/SessionCredentialTests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/YubiHsmAuth/SessionCredentialTests.cs index 5249cc4e9..451b6767a 100644 --- a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/YubiHsmAuth/SessionCredentialTests.cs +++ b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/YubiHsmAuth/SessionCredentialTests.cs @@ -17,22 +17,28 @@ using System.Linq; using System.Security; using Xunit; +using Yubico.YubiKey.Scp; +using Yubico.YubiKey.TestUtilities; namespace Yubico.YubiKey.YubiHsmAuth { public class SessionCredentialTests { #region DeleteCredential - [Fact] - public void AddCredential_DefaultTestCred_AppContainsOneCred() + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + [InlineData(StandardTestDevice.Fw5, true)] + [InlineData(StandardTestDevice.Fw5Fips, true)] + public void AddCredential_DefaultTestCred_AppContainsOneCred(StandardTestDevice selectedDevice, bool useScp = false) { // Preconditions - IYubiKeyDevice testDevice = YhaTestUtilities.GetCleanDevice(); + IYubiKeyDevice testDevice = YhaTestUtilities.GetCleanDevice(selectedDevice); IReadOnlyList credentialList; // Test - using (var yubiHsmAuthSession = new YubiHsmAuthSession(testDevice)) + using (var yubiHsmAuthSession = new YubiHsmAuthSession(testDevice, useScp ? Scp03KeyParameters.DefaultKey : null)) { yubiHsmAuthSession.AddCredential(YhaTestUtilities.DefaultMgmtKey, YhaTestUtilities.DefaultAes128Cred); @@ -173,15 +179,19 @@ public void TryDeleteCredentialKeyCollector_NoKeyCollector_ThrowsInvalidOpEx() #endregion #region AddCredential - [Fact] - public void TryAddCredentialKeyCollector_CorrectMgmtKey_AppContainsNewCred() + [SkippableTheory(typeof(DeviceNotFoundException))] + [InlineData(StandardTestDevice.Fw5)] + [InlineData(StandardTestDevice.Fw5Fips)] + [InlineData(StandardTestDevice.Fw5, true)] + [InlineData(StandardTestDevice.Fw5Fips, true)] + public void TryAddCredentialKeyCollector_CorrectMgmtKey_AppContainsNewCred(StandardTestDevice selectedDevice, bool useScp = false) { // Preconditions - IYubiKeyDevice testDevice = YhaTestUtilities.GetCleanDevice(); + IYubiKeyDevice testDevice = YhaTestUtilities.GetCleanDevice(selectedDevice); IReadOnlyList credentialList; - using (var yubiHsmAuthSession = new YubiHsmAuthSession(testDevice)) + using (var yubiHsmAuthSession = new YubiHsmAuthSession(testDevice, useScp ? Scp03KeyParameters.DefaultKey : null)) { yubiHsmAuthSession.KeyCollector = SimpleKeyCollector.DefaultValueCollectorDelegate; diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/YubiHsmAuth/YhaTestUtilities.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/YubiHsmAuth/YhaTestUtilities.cs index 7e5a51905..9a3ea6a8d 100644 --- a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/YubiHsmAuth/YhaTestUtilities.cs +++ b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/YubiHsmAuth/YhaTestUtilities.cs @@ -90,9 +90,9 @@ public class YhaTestUtilities /// into a known "control" state for performing integration /// tests with the YubiHSM Auth application. /// - public static IYubiKeyDevice GetCleanDevice() + public static IYubiKeyDevice GetCleanDevice(StandardTestDevice testDeviceType = StandardTestDevice.Fw5) { - var testDevice = DeviceReset.EnableAllCapabilities(IntegrationTestDeviceEnumeration.GetTestDevice()); + var testDevice = DeviceReset.EnableAllCapabilities(IntegrationTestDeviceEnumeration.GetTestDevice(testDeviceType)); return DeviceReset.ResetYubiHsmAuth(testDevice); } diff --git a/Yubico.YubiKey/tests/sandbox/Plugins/GregPlugin.cs b/Yubico.YubiKey/tests/sandbox/Plugins/GregPlugin.cs index 01505a9b5..c57e77e19 100644 --- a/Yubico.YubiKey/tests/sandbox/Plugins/GregPlugin.cs +++ b/Yubico.YubiKey/tests/sandbox/Plugins/GregPlugin.cs @@ -14,6 +14,9 @@ using System; using System.Linq; +using Microsoft.Extensions.Logging; +using Yubico.Core.Logging; +using Yubico.YubiKey.Piv; namespace Yubico.YubiKey.TestApp.Plugins { @@ -23,6 +26,7 @@ internal class GregPlugin : PluginBase public override string Description => "A place for Greg's test code"; public GregPlugin(IOutput output) : base(output) { } + public static ILogger Logger => Log.GetLogger(); public override bool Execute() { @@ -33,8 +37,13 @@ public override bool Execute() Console.Error.WriteLine($"YubiKey Version: {yubiKey.FirmwareVersion}"); Console.Error.WriteLine("NFC Before Value: " + yubiKey.IsNfcRestricted); - yubiKey.SetIsNfcRestricted(true); - + using (var session = new PivSession(yubiKey)) + { + Logger.LogDebug("Disconnect Yubikey"); + Console.ReadLine(); + } + //Dispose + Console.ReadLine(); return true; } diff --git a/Yubico.YubiKey/tests/sandbox/Plugins/Scp03Plugin.cs b/Yubico.YubiKey/tests/sandbox/Plugins/Scp03Plugin.cs index 1bc529047..f6e091423 100644 --- a/Yubico.YubiKey/tests/sandbox/Plugins/Scp03Plugin.cs +++ b/Yubico.YubiKey/tests/sandbox/Plugins/Scp03Plugin.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Linq; using Yubico.YubiKey.Piv; +using Yubico.YubiKey.Scp; using Yubico.YubiKey.Scp03; namespace Yubico.YubiKey.TestApp.Plugins @@ -45,10 +46,7 @@ private bool BasicE2ETest() IEnumerable keys = YubiKeyDevice.FindByTransport(Transport.UsbSmartCard); IYubiKeyDevice device = keys.Single()!; -#pragma warning disable CS0618 // Specifically testing this soon-to-be-deprecated feature - IYubiKeyDevice scp03Device = (device as YubiKeyDevice)!.WithScp03(new StaticKeys()); -#pragma warning restore CS0618 - using var piv = new PivSession(scp03Device); + using var piv = new PivSession(device, Scp03KeyParameters.DefaultKey); bool result = piv.TryVerifyPin(new ReadOnlyMemory(new byte[] { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }), out _); Output.WriteLine($"pin 123456: {result}"); diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/AesUtilitiesTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/AesUtilitiesTests.cs index ed654dad4..84ea502d4 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/AesUtilitiesTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/AesUtilitiesTests.cs @@ -56,7 +56,7 @@ public void AesBlockCipher_GivenKeyPlaintext_EncryptsCorrectly() byte[] plaintext = GetPlaintext(); // Act - byte[] result = AesUtilities.BlockCipher(key, plaintext); + var result = AesUtilities.BlockCipher(key, plaintext); // Assert Assert.Equal(result, Hex.HexToBytes("dcc0c378ec111cb23048486ef9d9a6b7")); @@ -116,7 +116,7 @@ public void AesCbcEncrypt_GivenKeyIVPlaintext_EncryptsCorrectly() byte[] iv = GetIV(); // Act - byte[] result = AesUtilities.AesCbcEncrypt(key, iv, plaintext); + ReadOnlyMemory result = AesUtilities.AesCbcEncrypt(key, iv, plaintext); // Assert Assert.Equal(result, Hex.HexToBytes("da19df061b1bcba151d692a4a9e63901")); @@ -175,7 +175,7 @@ public void AesCbcDecrypt_GivenKeyIVCiphertext_EncryptsCorrectly() byte[] iv = GetIV(); // Act - byte[] result = AesUtilities.AesCbcDecrypt(key, iv, ciphertext); + ReadOnlyMemory result = AesUtilities.AesCbcDecrypt(key, iv, ciphertext); // Assert Assert.Equal(result, Hex.HexToBytes("d1af13631ea24595793ddf5bf6f9c42c")); diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Fido2/Fido2SessionTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Fido2/Fido2SessionTests.cs index 5e5ecd4f3..38bac23f1 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Fido2/Fido2SessionTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Fido2/Fido2SessionTests.cs @@ -92,8 +92,6 @@ void GetAuthenticatorInfo_SendsGetInfoCommand() var session = new Fido2Session(mockYubiKey.Object); - //session.AuthenticatorInfo; - mockConnection.Verify(c => c.SendCommand(It.IsAny())); } } diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Pipelines/Scp03ApduTransformTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Pipelines/Scp03ApduTransformTests.cs deleted file mode 100644 index 287cf0135..000000000 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Pipelines/Scp03ApduTransformTests.cs +++ /dev/null @@ -1,123 +0,0 @@ -// 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.Linq; -using System.Security.Cryptography; -using Xunit; -using Yubico.Core.Buffers; -using Yubico.Core.Iso7816; -using Yubico.YubiKey.InterIndustry.Commands; -using Yubico.YubiKey.Piv.Commands; -using Yubico.YubiKey.Scp03; - -namespace Yubico.YubiKey.Pipelines -{ - public class PipelineFixture : IApduTransform - { - public ResponseApdu Invoke(CommandApdu command, Type commandType, Type responseType) - { - if (command is null) - { - throw new ArgumentNullException(nameof(command)); - } - - if (command.AsByteArray().SequenceEqual(new SelectApplicationCommand(YubiKeyApplication.Piv).CreateCommandApdu().AsByteArray())) - { - return new ResponseApdu(Hex.HexToBytes("9000")); - } - else if (command.AsByteArray().SequenceEqual(Hex.HexToBytes("8050FF0008360CB43F4301B894"))) - { - return new ResponseApdu(Hex.HexToBytes("010B001F002500000000FF0360CAAFA4DAC615236ADD5607216F3E115C9000")); - } - else if (command.AsByteArray().SequenceEqual(Hex.HexToBytes("848233001045330AB30BB1A079A8E7F77376DB9F2C"))) - { - return new ResponseApdu(Hex.HexToBytes("9000")); - } - else if (command.AsByteArray().SequenceEqual(Hex.HexToBytes("84FD0000181CE4E3D8F32D986A886DDBC90C8DB22553C2C04391250CCE"))) - { - return new ResponseApdu(Hex.HexToBytes("5F67E9E059DF3C52809DC9F6DDFBEF3E4C45691B2C8CDDD89000")); - } - else - { - string apduHex = Hex.BytesToHex(command.AsByteArray()); - throw new SecureChannelException($"Error: received unexpected APDU {apduHex}"); - // return new ResponseApdu(Hex.HexToBytes("6a80")); - } - } - public void Setup() - { - - } - public void Cleanup() - { - - } - } - - public class RandomNumberGeneratorFixture : RandomNumberGenerator - { - private readonly byte[] bytesToGenerate = Hex.HexToBytes("360CB43F4301B894"); // host challenge - public override void GetBytes(byte[] arr) - { - if (arr is null) - { - throw new ArgumentNullException(nameof(arr)); - } - for (int i = 0; i < bytesToGenerate.Length; i++) - { - arr[i] = bytesToGenerate[i]; - } - } - } - - public class Scp03ApduTransformTests - { - private static IApduTransform GetPipeline() => new PipelineFixture(); - private static StaticKeys GetStaticKeys() => new StaticKeys(); - - [Fact] - public void Constructor_GivenNullPipeline_ThrowsArgumentNullException() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _ = Assert.Throws(() => new Scp03ApduTransform(null, GetStaticKeys())); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void Constructor_GivenNullStaticKeys_ThrowsArgumentNullException() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _ = Assert.Throws(() => new Scp03ApduTransform(GetPipeline(), null)); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void Invoke_GivenPriorSetup_CorrectlyEncodesCommand() - { - // Arrange - var pipeline = new Scp03ApduTransform(GetPipeline(), GetStaticKeys()); - using var fakeRng = new RandomNumberGeneratorFixture(); - pipeline.Setup(fakeRng); - - // Act - ResponseApdu responseApdu = pipeline.Invoke(new VersionCommand().CreateCommandApdu(), typeof(object), typeof(object)); - var versionResponse = new VersionResponse(responseApdu); - FirmwareVersion fwv = versionResponse.GetData(); - - // Assert - Assert.Equal(5, fwv.Major); - } - } -} diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Pipelines/ScpApduTransformTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Pipelines/ScpApduTransformTests.cs new file mode 100644 index 000000000..f3059a43d --- /dev/null +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Pipelines/ScpApduTransformTests.cs @@ -0,0 +1,144 @@ +// 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 Moq; +using Xunit; +using Yubico.Core.Iso7816; +using Yubico.YubiKey.Scp; +using Yubico.YubiKey.InterIndustry.Commands; + +namespace Yubico.YubiKey.Pipelines +{ + public class ScpApduTransformTests + { + private readonly Mock _previous = new Mock(); + private readonly Scp03KeyParameters _scp03KeyParams = Scp03KeyParameters.DefaultKey; + + [Fact] + public void Constructor_NullPipeline_ThrowsArgumentNullException() + { + // Act & Assert + Assert.Throws(() => new ScpApduTransform(null!, _scp03KeyParams)); + } + + [Fact] + public void Constructor_NullKeyParameters_ThrowsArgumentNullException() + { + // Act & Assert + Assert.Throws(() => new ScpApduTransform(_previous.Object, null!)); + } + + [Fact] + public void Constructor_ValidParameters_CreatesInstance() + { + // Act + var transform = new ScpApduTransform(_previous.Object, _scp03KeyParams); + + // Assert + Assert.NotNull(transform); + Assert.Same(_scp03KeyParams, transform.KeyParameters); + } + + [Fact] + public void EncryptDataFunc_BeforeSetup_ThrowsInvalidOperationException() + { + // Arrange + var transform = new ScpApduTransform(_previous.Object, _scp03KeyParams); + + // Act & Assert + Assert.Throws(() => transform.EncryptDataFunc); + } + + [Fact] + public void Invoke_ExemptedCommands_BypassEncoding() + { + // Arrange + var transform = new ScpApduTransform(_previous.Object, _scp03KeyParams); + + var testCases = new[] + { + new CommandTestCase{ + Command = new SelectApplicationCommand(YubiKeyApplication.SecurityDomain), + CommandType = typeof(SelectApplicationCommand), + ResponseType = typeof(ISelectApplicationResponse) + }, + new CommandTestCase{ + Command = new Oath.Commands.SelectOathCommand(), + CommandType = typeof(Oath.Commands.SelectOathCommand), + ResponseType = typeof(Oath.Commands.SelectOathResponse) + }, + new CommandTestCase{ + Command = new Scp.Commands.ResetCommand(0x84, 0x01, 0x01, new byte[] { 0x00 }), + CommandType = typeof(Scp.Commands.ResetCommand), + ResponseType = typeof(YubiKeyResponse) + } + }; + + foreach (var testCase in testCases) + { + _previous.Setup(p => p.Invoke( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(new ResponseApdu(new byte[] { 0x90, 0x00 })); + + // Act + _ = transform.Invoke( + testCase.Command.CreateCommandApdu(), + testCase.CommandType, + testCase.ResponseType); + + // Assert that the previous pipeline was called (which is the one that doesn't do encoding) + _previous.Verify(p => p.Invoke( + It.IsAny(), + testCase.CommandType, + testCase.ResponseType), Times.Once); + + _previous.Reset(); + } + } + + private struct CommandTestCase + { + public IYubiKeyCommand Command; + public Type CommandType; + public Type ResponseType; + } + + [Fact] + public void Dispose_MultipleCalls_DoesNotThrow() + { + // Arrange + var transform = new ScpApduTransform(_previous.Object, _scp03KeyParams); + + // Act & Assert + transform.Dispose(); + transform.Dispose(); // Should not throw + } + + [Fact] + public void Cleanup_CallsUnderlyingPipelineCleanup() + { + // Arrange + var transform = new ScpApduTransform(_previous.Object, _scp03KeyParams); + + // Act + transform.Cleanup(); + + // Assert + _previous.Verify(p => p.Cleanup(), Times.Once); + } + } +} diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/ChannelEncryptionTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/ChannelEncryptionTests.cs similarity index 92% rename from Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/ChannelEncryptionTests.cs rename to Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/ChannelEncryptionTests.cs index 1e4742718..1ce7fd438 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/ChannelEncryptionTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/ChannelEncryptionTests.cs @@ -15,8 +15,9 @@ using System; using Xunit; using Yubico.Core.Buffers; +using Yubico.YubiKey.Scp.Helpers; -namespace Yubico.YubiKey.Scp03 +namespace Yubico.YubiKey.Scp { public class ChannelEncryptionTests { @@ -46,7 +47,7 @@ public void EncryptData_GivenCorrectKeyPayload_ReturnsCorrectly() int ec = GetEncryptionCounter(); // Act - byte[] output = ChannelEncryption.EncryptData(payload, key, ec); + ReadOnlyMemory output = ChannelEncryption.EncryptData(payload, key, ec); // Assert Assert.Equal(GetCorrectEncryptOutput(), output); @@ -77,7 +78,7 @@ public void DecryptData_GivenCorrectKeyPayload_ReturnsCorrectly() int ec = 1; // Act - byte[] output = ChannelEncryption.DecryptData(payload, key, ec); + ReadOnlyMemory output = ChannelEncryption.DecryptData(payload, key, ec); // Assert Assert.Equal(GetCorrectDecryptedOutput(), output); diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/ChannelMacTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/ChannelMacTests.cs similarity index 98% rename from Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/ChannelMacTests.cs rename to Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/ChannelMacTests.cs index ad92329f5..7efd3e9ac 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/ChannelMacTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/ChannelMacTests.cs @@ -16,8 +16,9 @@ using Xunit; using Yubico.Core.Buffers; using Yubico.Core.Iso7816; +using Yubico.YubiKey.Scp.Helpers; -namespace Yubico.YubiKey.Scp03 +namespace Yubico.YubiKey.Scp { public class ChannelMacTests { diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/Scp03ResponseTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/Commands/ScpResponseTests.cs similarity index 86% rename from Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/Scp03ResponseTests.cs rename to Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/Commands/ScpResponseTests.cs index 2036ab9b8..4c011c35d 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/Scp03ResponseTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/Commands/ScpResponseTests.cs @@ -15,16 +15,17 @@ using System; using Xunit; using Yubico.Core.Iso7816; +using Yubico.YubiKey.Scp.Commands; -namespace Yubico.YubiKey.Scp03.Commands +namespace Yubico.YubiKey.Scp.Commands { - public class Scp03ResponseTests + public class ScpResponseTests { [Fact] public void Constructor_GivenNullResponseApdu_ThrowsArgumentNullException() { #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _ = Assert.Throws(() => new Scp03Response(null)); + _ = Assert.Throws(() => new ScpResponse(null)); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. } @@ -35,7 +36,7 @@ public void StatusWord_GivenResponseApdu_EqualsSWField() var responseApdu = new ResponseApdu(new byte[] { 24, 73 }); // Act - var scp03Response = new Scp03Response(responseApdu); + var scp03Response = new ScpResponse(responseApdu); // Assert Assert.Equal(responseApdu.SW, scp03Response.StatusWord); @@ -48,7 +49,7 @@ public void Status_GivenSuccessfulResponseApdu_ReturnsSuccess() var responseApdu = new ResponseApdu(new byte[] { SW1Constants.Success, 0x00 }); // Act - var scp03Response = new Scp03Response(responseApdu); + var scp03Response = new ScpResponse(responseApdu); // Assert Assert.Equal(ResponseStatus.Success, scp03Response.Status); @@ -61,7 +62,7 @@ public void Status_GivenFailedResponseApdu_ReturnsFailed() var responseApdu = new ResponseApdu(new byte[] { SW1Constants.CommandNotAllowed, 0x00 }); // Act - var scp03Response = new Scp03Response(responseApdu); + var scp03Response = new ScpResponse(responseApdu); // Assert Assert.Equal(ResponseStatus.Failed, scp03Response.Status); diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/DerivationTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/DerivationTests.cs similarity index 92% rename from Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/DerivationTests.cs rename to Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/DerivationTests.cs index ed571e511..45aff3055 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/DerivationTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/DerivationTests.cs @@ -15,8 +15,10 @@ using System; using Xunit; using Yubico.Core.Buffers; +using Yubico.YubiKey.Scp.Helpers; + +namespace Yubico.YubiKey.Scp -namespace Yubico.YubiKey.Scp03 { public class DerivationTests { @@ -49,7 +51,7 @@ public void Derive_GivenBadKey_ThrowsArgumentException() [Fact] public void Derive_GivenCorrectVals_ReturnsCorrectHostCryptogram() { - byte[] hostCryptogram = Derivation.Derive(Derivation.DDC_HOST_CRYPTOGRAM, 0x40, GetKey(), GetHostChallenge(), GetCardChallenge()); + var hostCryptogram = Derivation.Derive(Derivation.DDC_HOST_CRYPTOGRAM, 0x40, GetKey(), GetHostChallenge(), GetCardChallenge()); Assert.Equal(GetCorrectDeriveOutput(), hostCryptogram); } } diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/PaddingTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/PaddingTests.cs similarity index 71% rename from Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/PaddingTests.cs rename to Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/PaddingTests.cs index 4cbb95920..77b659f19 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/PaddingTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/PaddingTests.cs @@ -15,8 +15,9 @@ using System; using Xunit; using Yubico.Core.Buffers; +using Yubico.YubiKey.Scp.Helpers; -namespace Yubico.YubiKey.Scp03 +namespace Yubico.YubiKey.Scp { public class PaddingTests { @@ -27,22 +28,6 @@ public class PaddingTests private static byte[] Get16BytePayload() => Hex.HexToBytes("000102038005060708090A0B0C0D0E0F"); private static byte[] GetPadded16BytePayload() => Hex.HexToBytes("000102038005060708090A0B0C0D0E0F80000000000000000000000000000000"); - [Fact] - public void PadToBlockSize_GivenNullPayload_ThrowsArgumentNullException() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _ = Assert.Throws(() => Padding.PadToBlockSize(null)); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void RemovePadding_GivenNullPayload_ThrowsArgumentNullException() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _ = Assert.Throws(() => Padding.RemovePadding(null)); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - [Fact] public void PadToBlockSize_Given1BytePayload_ReturnsCorrectlyPaddedBlock() { @@ -50,7 +35,7 @@ public void PadToBlockSize_Given1BytePayload_ReturnsCorrectlyPaddedBlock() byte[] payload = Get1BytePayload(); // Act - byte[] paddedPayload = Padding.PadToBlockSize(payload); + Memory paddedPayload = Padding.PadToBlockSize(payload); // Assert Assert.Equal(paddedPayload, GetPadded1BytePayload()); @@ -63,7 +48,7 @@ public void PadToBlockSize_Given8BytePayload_ReturnsCorrectlyPaddedBlock() byte[] payload = Get8BytePayload(); // Act - byte[] paddedPayload = Padding.PadToBlockSize(payload); + Memory paddedPayload = Padding.PadToBlockSize(payload); // Assert Assert.Equal(paddedPayload, GetPadded8BytePayload()); @@ -76,7 +61,7 @@ public void PadToBlockSize_Given16BytePayload_ReturnsCorrectlyPaddedBlock() byte[] payload = Get16BytePayload(); // Act - byte[] paddedPayload = Padding.PadToBlockSize(payload); + Memory paddedPayload = Padding.PadToBlockSize(payload); // Assert Assert.Equal(paddedPayload, GetPadded16BytePayload()); @@ -89,7 +74,7 @@ public void RemovePadding_GivenPadded1BytePayload_ReturnsPayloadWithPaddingRemov byte[] paddedPayload = GetPadded1BytePayload(); // Act - byte[] payload = Padding.RemovePadding(paddedPayload); + Memory payload = Padding.RemovePadding(paddedPayload); // Assert Assert.Equal(payload, Get1BytePayload()); @@ -102,7 +87,7 @@ public void RemovePadding_GivenPadded8BytePayload_ReturnsPayloadWithPaddingRemov byte[] paddedPayload = GetPadded8BytePayload(); // Act - byte[] payload = Padding.RemovePadding(paddedPayload); + Memory payload = Padding.RemovePadding(paddedPayload); // Assert Assert.Equal(payload, Get8BytePayload()); @@ -115,7 +100,7 @@ public void RemovePadding_GivenPadded16BytePayload_ReturnsPayloadWithPaddingRemo byte[] paddedPayload = GetPadded16BytePayload(); // Act - byte[] payload = Padding.RemovePadding(paddedPayload); + Memory payload = Padding.RemovePadding(paddedPayload); // Assert Assert.Equal(payload, Get16BytePayload()); diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/Scp03KeyParametersTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/Scp03KeyParametersTests.cs new file mode 100644 index 000000000..f3e962312 --- /dev/null +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/Scp03KeyParametersTests.cs @@ -0,0 +1,106 @@ +// Copyright 2024 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.Linq; +using Xunit; + +namespace Yubico.YubiKey.Scp +{ + public class Scp03KeyParametersTests + { + [Fact] + public void DefaultKey_ReturnsValidInstance() + { + // Act + var keyParams = Scp03KeyParameters.DefaultKey; + + // Assert + Assert.NotNull(keyParams); + Assert.NotNull(keyParams.StaticKeys); + Assert.Equal(ScpKeyIds.Scp03, keyParams.KeyReference.Id); + Assert.Equal(0xFF, keyParams.KeyReference.VersionNumber); + } + + [Fact] + public void FromStaticKeys_ValidKeys_ReturnsValidInstance() + { + // Arrange + var staticKeys = new StaticKeys( + new byte[16], // enc + new byte[16], // mac + new byte[16] // dek + ); + + // Act + var keyParams = Scp03KeyParameters.FromStaticKeys(staticKeys); + + // Assert + Assert.NotNull(keyParams); + Assert.True(staticKeys.AreKeysSame(keyParams.StaticKeys)); + Assert.Equal(ScpKeyIds.Scp03, keyParams.KeyReference.Id); + Assert.Equal(0x01, keyParams.KeyReference.VersionNumber); + } + + [Fact] + public void Constructor_ValidParameters_SetsProperties() + { + // Arrange + var staticKeys = new StaticKeys( + new byte[16], // enc + new byte[16], // mac + new byte[16] // dek + ); + const int keyId = ScpKeyIds.Scp03; + const int kvn = 0x02; + + // Act + var keyParams = new Scp03KeyParameters(keyId, kvn, staticKeys); + + // Assert + Assert.NotNull(keyParams); + Assert.True(staticKeys.AreKeysSame(keyParams.StaticKeys)); + Assert.Equal(keyId, keyParams.KeyReference.Id); + Assert.Equal(kvn, keyParams.KeyReference.VersionNumber); + } + + [Fact] + public void Dispose_DisposesStaticKeys() + { + // Arrange + var staticKeys = new StaticKeys( + new byte[16], // enc + new byte[16], // mac + new byte[16] // dek + ); + + var keyParams = new Scp03KeyParameters(ScpKeyIds.Scp03, 0x01, staticKeys); + + // Act + keyParams.Dispose(); + Assert.True(keyParams.StaticKeys.DataEncryptionKey.ToArray().All(b => b == 0)); + } + + [Fact] + public void Dispose_MultipleCalls_DoesNotThrow() + { + // Arrange + var keyParams = Scp03KeyParameters.DefaultKey; + + // Act & Assert - Should not throw + keyParams.Dispose(); + keyParams.Dispose(); + } + } +} diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/Scp03StateTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/Scp03StateTests.cs new file mode 100644 index 000000000..f582f6922 --- /dev/null +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/Scp03StateTests.cs @@ -0,0 +1,146 @@ +using System; +using Moq; +using Xunit; +using Yubico.Core.Iso7816; +using Yubico.YubiKey.Cryptography; +using Yubico.YubiKey.Scp.Commands; +using Yubico.YubiKey.Scp.Helpers; + +namespace Yubico.YubiKey.Scp +{ + public class Scp03StateTests + { + readonly byte[] ResponseData; + + internal Scp03State State { get; set; } + + public Scp03StateTests() + { + var parent = new Mock(); + var keyParams = Scp03KeyParameters.DefaultKey; + var hostChallenge = new byte[8]; + var cardChallenge = new byte[8]; + + ResponseData = GetFakeResponseApduData(hostChallenge, cardChallenge); + + parent.Setup(p => p.Invoke( + It.IsAny(), + typeof(InitializeUpdateCommand), + typeof(InitializeUpdateResponse))) + .Returns(new ResponseApdu(ResponseData)); + + + parent.Setup(p => p.Invoke( + It.IsAny(), + typeof(ExternalAuthenticateCommand), + typeof(ExternalAuthenticateResponse))) + .Returns(new ResponseApdu(ResponseData)); + + // Act + State = Scp03State.CreateScpState(parent.Object, keyParams, hostChallenge); + } + + [Fact] + public void CreateScpState_ValidParameters_InitializesCorrectly() + { + // Arrange + var parent = new Mock(); + var keyParams = Scp03KeyParameters.DefaultKey; + var hostChallenge = new byte[8]; + var cardChallenge = new byte[8]; + + var responseApduData = GetFakeResponseApduData(hostChallenge, cardChallenge); + + parent.Setup(p => p.Invoke( + It.IsAny(), + typeof(InitializeUpdateCommand), + typeof(InitializeUpdateResponse))) + .Returns(new ResponseApdu(responseApduData)); + + + parent.Setup(p => p.Invoke( + It.IsAny(), + typeof(ExternalAuthenticateCommand), + typeof(ExternalAuthenticateResponse))) + .Returns(new ResponseApdu(responseApduData)); + + // Act + var state = Scp03State.CreateScpState(parent.Object, keyParams, hostChallenge); + + // Assert + Assert.NotNull(state); + Assert.NotNull(state.GetDataEncryptor()); + } + + [Fact] + public void CreateScpState_NullPipeline_ThrowsArgumentNullException() + { + // Arrange + var keyParams = Scp03KeyParameters.DefaultKey; + byte[] hostChallenge = new byte[8]; + + // Act & Assert + Assert.Throws(() => + Scp03State.CreateScpState(null!, keyParams, hostChallenge)); + } + + [Fact] + public void CreateScpState_NullKeyParams_ThrowsArgumentNullException() + { + // Arrange + var pipeline = new Mock(); + byte[] hostChallenge = new byte[8]; + + // Act & Assert + Assert.Throws(() => + Scp03State.CreateScpState(pipeline.Object, null!, hostChallenge)); + } + + [Fact] + public void EncodeCommand_ValidCommand_ReturnsEncodedApdu() + { + // Arrange + using var rng = CryptographyProviders.RngCreator(); + Span putKeyData = stackalloc byte[256]; + rng.GetBytes(putKeyData); + var originalCommand = new PutKeyCommand(1, 2, putKeyData.ToArray()).CreateCommandApdu(); + + // Act + var encodedCommand = State.EncodeCommand(originalCommand); + + // Assert + Assert.NotNull(encodedCommand); + Assert.NotEqual(originalCommand.Data, encodedCommand.Data); + } + + private static byte[] GetFakeResponseApduData( + byte[] hostChallenge, + byte[] cardChallenge) + { + Array.Fill(hostChallenge, (byte)1); + Array.Fill(cardChallenge, (byte)1); + + // Derive session keys + var sessionKeys = Derivation.DeriveSessionKeysFromStaticKeys( + Scp03KeyParameters.DefaultKey.StaticKeys, + hostChallenge, + cardChallenge); + + // Check supplied card cryptogram + var calculatedCardCryptogram = Derivation.DeriveCryptogram( + Derivation.DDC_CARD_CRYPTOGRAM, + sessionKeys.MacKey.Span, + hostChallenge, + cardChallenge); + + var responseApduData = new byte[31]; + Array.Fill(responseApduData, (byte)1); + responseApduData[^2] = 0x90; + responseApduData[^1] = 0x00; + + // Add fake Card Crypto response + calculatedCardCryptogram.Span.CopyTo(responseApduData.AsSpan(21..29)); + return responseApduData; + } + } +} diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/Scp11KeyParametersTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/Scp11KeyParametersTests.cs new file mode 100644 index 000000000..d63c5fcd1 --- /dev/null +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/Scp11KeyParametersTests.cs @@ -0,0 +1,204 @@ +// Copyright 2024 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.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Xunit; +using Yubico.YubiKey.Cryptography; + +namespace Yubico.YubiKey.Scp +{ + public class Scp11KeyParametersTests : IDisposable + { + private readonly ECCurve _curve = ECCurve.NamedCurves.nistP256; + private readonly ECParameters _sdParams; + private readonly ECParameters _oceParams; + private readonly X509Certificate2[] _certificates; + + public Scp11KeyParametersTests() + { + using var sdKey = ECDsa.Create(_curve); + _sdParams = sdKey.ExportParameters(false); + + using var oceKey = ECDsa.Create(_curve); + _oceParams = oceKey.ExportParameters(true); + + // Create a self-signed cert for testing + var req = new CertificateRequest( + "CN=Test", + oceKey, + HashAlgorithmName.SHA256); + + _certificates = new[] + { + req.CreateSelfSigned( + DateTimeOffset.UtcNow, + DateTimeOffset.UtcNow.AddYears(1)) + }; + } + + [Fact] + public void Constructor_Scp11b_ValidParameters_Succeeds() + { + // Arrange + var keyRef = new KeyReference(ScpKeyIds.Scp11B, 0x01); + var pkSdEcka = new ECPublicKeyParameters(_sdParams); + + // Act + var keyParams = new Scp11KeyParameters(keyRef, pkSdEcka); + + // Assert + Assert.NotNull(keyParams); + Assert.Equal(ScpKeyIds.Scp11B, keyParams.KeyReference.Id); + Assert.Equal(0x01, keyParams.KeyReference.VersionNumber); + Assert.NotNull(keyParams.PkSdEcka); + Assert.Null(keyParams.OceKeyReference); + Assert.Null(keyParams.SkOceEcka); + Assert.Null(keyParams.OceCertificates); + } + + [Theory] + [InlineData(ScpKeyIds.Scp11A)] + [InlineData(ScpKeyIds.Scp11C)] + public void Constructor_Scp11ac_ValidParameters_Succeeds(byte keyId) + { + // Arrange + var keyRef = new KeyReference(keyId, 0x01); + var oceKeyRef = new KeyReference(0x01, 0x01); + var pkSdEcka = new ECPublicKeyParameters(_sdParams); + var skOceEcka = new ECPrivateKeyParameters(_oceParams); + + // Act + var keyParams = new Scp11KeyParameters( + keyRef, + pkSdEcka, + oceKeyRef, + skOceEcka, + _certificates); + + // Assert + Assert.NotNull(keyParams); + Assert.Equal(keyId, keyParams.KeyReference.Id); + Assert.Equal(0x01, keyParams.KeyReference.VersionNumber); + Assert.NotNull(keyParams.PkSdEcka); + Assert.NotNull(keyParams.OceKeyReference); + Assert.NotNull(keyParams.SkOceEcka); + Assert.NotNull(keyParams.OceCertificates); + Assert.Single(keyParams.OceCertificates); + } + + [Fact] + public void Constructor_Scp11b_WithOptionalParams_ThrowsArgumentException() + { + // Arrange + var keyRef = new KeyReference(ScpKeyIds.Scp11B, 0x01); + var oceKeyRef = new KeyReference(0x01, 0x01); + var pkSdEcka = new ECPublicKeyParameters(_sdParams); + var skOceEcka = new ECPrivateKeyParameters(_oceParams); + + // Act & Assert + Assert.Throws(() => new Scp11KeyParameters( + keyRef, + pkSdEcka, + oceKeyRef, + skOceEcka, + _certificates)); + } + + [Theory] + [InlineData(ScpKeyIds.Scp11A)] + [InlineData(ScpKeyIds.Scp11C)] + public void Constructor_Scp11ac_MissingParams_ThrowsArgumentException(byte keyId) + { + // Arrange + var keyRef = new KeyReference(keyId, 0x01); + var pkSdEcka = new ECPublicKeyParameters(_sdParams); + + // Act & Assert + Assert.Throws(() => new Scp11KeyParameters( + keyRef, + pkSdEcka)); + } + + [Theory] + [InlineData(0x00)] // Invalid key ID + [InlineData(0x10)] // Invalid key ID + [InlineData(0xFF)] // Invalid key ID + public void Constructor_InvalidKeyId_ThrowsArgumentException(byte keyId) + { + // Arrange + var keyRef = new KeyReference(keyId, 0x01); + var pkSdEcka = new ECPublicKeyParameters(_sdParams); + + // Act & Assert + Assert.Throws(() => new Scp11KeyParameters( + keyRef, + pkSdEcka)); + } + + [Fact] + public void Dispose_ClearsPrivateKey() + { + // Arrange + var keyRef = new KeyReference(ScpKeyIds.Scp11A, 0x01); + var oceKeyRef = new KeyReference(0x01, 0x01); + var pkSdEcka = new ECPublicKeyParameters(_sdParams); + var skOceEcka = new ECPrivateKeyParameters(_oceParams); + + Scp11KeyParameters keyParams = new Scp11KeyParameters( + keyRef, + pkSdEcka, + oceKeyRef, + skOceEcka, + _certificates); + + // Act + keyParams.Dispose(); + + // Assert + Assert.Null(keyParams.SkOceEcka); + Assert.Null(keyParams.OceKeyReference); + } + + [Fact] + public void Dispose_MultipleCalls_DoesNotThrow() + { + // Arrange + var keyRef = new KeyReference(ScpKeyIds.Scp11A, 0x01); + var oceKeyRef = new KeyReference(0x01, 0x01); + var pkSdEcka = new ECPublicKeyParameters(_sdParams); + var skOceEcka = new ECPrivateKeyParameters(_oceParams); + + var keyParams = new Scp11KeyParameters( + keyRef, + pkSdEcka, + oceKeyRef, + skOceEcka, + _certificates); + + // Act & Assert - Should not throw + keyParams.Dispose(); + keyParams.Dispose(); + } + + public void Dispose() + { + foreach (var cert in _certificates) + { + cert.Dispose(); + } + } + } +} diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/StaticKeysTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/StaticKeysTests.cs similarity index 98% rename from Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/StaticKeysTests.cs rename to Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/StaticKeysTests.cs index 10a4c0b2b..d2d668545 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/StaticKeysTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp/StaticKeysTests.cs @@ -15,7 +15,7 @@ using System; using Xunit; -namespace Yubico.YubiKey.Scp03 +namespace Yubico.YubiKey.Scp { public class StaticKeysTests { diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/ExternalAuthenticateCommandTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/ExternalAuthenticateCommandTests.cs deleted file mode 100644 index 9209b227f..000000000 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/ExternalAuthenticateCommandTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -// 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 Xunit; -using Yubico.Core.Iso7816; - -namespace Yubico.YubiKey.Scp03.Commands -{ - public class ExternalAuthenticateCommandTests - { - [Fact] - public void CreateCommandApdu_GetClaProperty_ReturnsHex84() - { - Assert.Equal(0x84, GetExternalAuthenticateCommandApdu().Cla); - } - - [Fact] - public void CreateCommandApdu_GetInsProperty_ReturnsHex82() - { - Assert.Equal(0x82, GetExternalAuthenticateCommandApdu().Ins); - } - - [Fact] - public void CreateCommandApdu_GetP1Property_ReturnsHex33() - { - Assert.Equal(0x33, GetExternalAuthenticateCommandApdu().P1); - } - - [Fact] - public void CreateCommandApdu_GetP2Property_ReturnsZero() - { - Assert.Equal(0, GetExternalAuthenticateCommandApdu().P2); - } - - [Fact] - public void CreateCommandApdu_GetData_ReturnsData() - { - ReadOnlyMemory data = GetExternalAuthenticateCommandApdu().Data; - Assert.True(data.Span.SequenceEqual(GetData())); - } - - [Fact] - public void CreateCommandApdu_GetNc_ReturnsDataLength() - { - Assert.Equal(GetData().Length, GetExternalAuthenticateCommandApdu().Nc); - } - - [Fact] - public void CreateCommandApdu_GetNe_ReturnsZero() - { - Assert.Equal(0, GetExternalAuthenticateCommandApdu().Ne); - } - - private static byte[] GetData() - { - return new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; - } - private static ExternalAuthenticateCommand GetExternalAuthenticateCommand() - { - return new ExternalAuthenticateCommand(GetData()); - } - private static CommandApdu GetExternalAuthenticateCommandApdu() - { - return GetExternalAuthenticateCommand().CreateCommandApdu(); - } - } -} diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/ExternalAuthenticateResponseTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/ExternalAuthenticateResponseTests.cs deleted file mode 100644 index 67d8b5d67..000000000 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/ExternalAuthenticateResponseTests.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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 Xunit; -using Yubico.Core.Iso7816; - -namespace Yubico.YubiKey.Scp03.Commands -{ - public class ExternalAuthenticateResponseTests - { - public static ResponseApdu GetResponseApdu() - { - return new ResponseApdu(new byte[] { 0x90, 0x00 }); - } - - [Fact] - public void Constructor_GivenNullResponseApdu_ThrowsArgumentNullExceptionFromBase() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _ = Assert.Throws(() => new ExternalAuthenticateResponse(null)); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void Constructor_GivenResponseApdu_SetsStatusWordCorrectly() - { - // Arrange - ResponseApdu responseApdu = GetResponseApdu(); - - // Act - var externalAuthenticateResponse = new ExternalAuthenticateResponse(responseApdu); - - // Assert - Assert.Equal(SWConstants.Success, externalAuthenticateResponse.StatusWord); - } - - [Fact] - public void ExternalAuthenticateResponse_GivenResponseApduWithData_ThrowsArgumentException() - { - var badResponseApdu = new ResponseApdu(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x90, 0x00 }); - - _ = Assert.Throws(() => new ExternalAuthenticateResponse(badResponseApdu)); - } - } -} diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/InitializeUpdateCommandTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/InitializeUpdateCommandTests.cs deleted file mode 100644 index 04e3b31fc..000000000 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/InitializeUpdateCommandTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// 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 Xunit; -using Yubico.Core.Iso7816; - -namespace Yubico.YubiKey.Scp03.Commands -{ - public class InitializeUpdateCommandTests - { - [Fact] - public void CreateCommandApdu_GetClaProperty_ReturnsHex80() - { - Assert.Equal(0x80, GetInitializeUpdateCommandApdu().Cla); - } - - [Fact] - public void CreateCommandApdu_GetInsProperty_ReturnsHex50() - { - Assert.Equal(0x50, GetInitializeUpdateCommandApdu().Ins); - } - - [Fact] - public void CreateCommandApdu_GetP1Property_ReturnsZero() - { - Assert.Equal(0, GetInitializeUpdateCommandApdu().P1); - } - - [Fact] - public void CreateCommandApdu_GetP2Property_ReturnsZero() - { - Assert.Equal(0, GetInitializeUpdateCommandApdu().P2); - } - - [Fact] - public void CreateCommandApdu_GetData_ReturnsChallenge() - { - CommandApdu commandApdu = GetInitializeUpdateCommandApdu(); - byte[] challenge = GetChallenge(); - - Assert.False(commandApdu.Data.IsEmpty); - Assert.True(commandApdu.Data.Span.SequenceEqual(challenge)); - } - - [Fact] - public void CreateCommandApdu_GetNc_Returns8() - { - Assert.Equal(8, GetInitializeUpdateCommandApdu().Nc); - } - - [Fact] - public void CreateCommandApdu_GetNe_ReturnsZero() - { - Assert.Equal(0, GetInitializeUpdateCommandApdu().Ne); - } - - private static byte[] GetChallenge() - { - return new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; - } - private static InitializeUpdateCommand GetInitializeUpdateCommand() - { - return new InitializeUpdateCommand(0, GetChallenge()); - } - private static CommandApdu GetInitializeUpdateCommandApdu() - { - return GetInitializeUpdateCommand().CreateCommandApdu(); - } - } -} diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/InitializeUpdateResponseTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/InitializeUpdateResponseTests.cs deleted file mode 100644 index d85a9f18f..000000000 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/Commands/InitializeUpdateResponseTests.cs +++ /dev/null @@ -1,111 +0,0 @@ -// 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 Xunit; -using Yubico.Core.Iso7816; - -namespace Yubico.YubiKey.Scp03.Commands -{ - public class InitializeUpdateResponseTests - { - public static ResponseApdu GetResponseApdu() - { - return new ResponseApdu(new byte[] { - 1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, - 0x90, 0x00 - }); - } - - private static IReadOnlyCollection GetDiversificationData() => GetResponseApdu().Data.Slice(0, 10).ToArray(); - private static IReadOnlyCollection GetKeyInfo() => GetResponseApdu().Data.Slice(10, 3).ToArray(); - private static IReadOnlyCollection GetCardChallenge() => GetResponseApdu().Data.Slice(13, 8).ToArray(); - private static IReadOnlyCollection GetCardCryptogram() => GetResponseApdu().Data.Slice(21, 8).ToArray(); - - [Fact] - public void Constructor_GivenNullResponseApdu_ThrowsArgumentNullExceptionFromBase() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _ = Assert.Throws(() => new InitializeUpdateResponse(null)); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void Constructor_GivenResponseApdu_SetsStatusWordCorrectly() - { - // Arrange - ResponseApdu responseApdu = GetResponseApdu(); - - // Act - var initializeUpdateResponse = new InitializeUpdateResponse(responseApdu); - - // Assert - Assert.Equal(SWConstants.Success, initializeUpdateResponse.StatusWord); - } - - [Fact] - public void InitializeUpdateResponse_GivenResponseApdu_DiversificationDataEqualsBytes0To10() - { - // Arrange - ResponseApdu responseApdu = GetResponseApdu(); - - // Act - var initializeUpdateResponse = new InitializeUpdateResponse(responseApdu); - - // Assert - Assert.Equal(GetDiversificationData(), initializeUpdateResponse.DiversificationData); - } - - [Fact] - public void InitializeUpdateResponse_GivenResponseApdu_KeyInfoEqualsBytes10To13() - { - // Arrange - ResponseApdu responseApdu = GetResponseApdu(); - - // Act - var initializeUpdateResponse = new InitializeUpdateResponse(responseApdu); - - // Assert - Assert.Equal(GetKeyInfo(), initializeUpdateResponse.KeyInfo); - } - [Fact] - public void InitializeUpdateResponse_GivenResponseApdu_CardChallengeEqualsBytes13To21() - { - // Arrange - ResponseApdu responseApdu = GetResponseApdu(); - - // Act - var initializeUpdateResponse = new InitializeUpdateResponse(responseApdu); - - // Assert - Assert.Equal(GetCardChallenge(), initializeUpdateResponse.CardChallenge); - } - [Fact] - public void InitializeUpdateResponse_GivenResponseApdu_CardCryptogramEqualsBytes21To29() - { - // Arrange - ResponseApdu responseApdu = GetResponseApdu(); - - // Act - var initializeUpdateResponse = new InitializeUpdateResponse(responseApdu); - - // Assert - Assert.Equal(GetCardCryptogram(), initializeUpdateResponse.CardCryptogram); - } - } -} diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/SessionTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/SessionTests.cs deleted file mode 100644 index 2f131cf47..000000000 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Scp03/SessionTests.cs +++ /dev/null @@ -1,124 +0,0 @@ -// 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 Xunit; -using Yubico.Core.Buffers; -using Yubico.Core.Iso7816; -using Yubico.YubiKey.Scp03.Commands; - -namespace Yubico.YubiKey.Scp03 -{ - public class SessionTests - { - private static byte[] GetChallenge() => Hex.HexToBytes("360CB43F4301B894"); - private static byte[] GetCorrectInitializeUpdate() => Hex.HexToBytes("8050000008360CB43F4301B894"); - private static byte[] GetInitializeUpdateResponse() => Hex.HexToBytes("010B001F002500000000FF0360CAAFA4DAC615236ADD5607216F3E115C9000"); - private static byte[] GetCorrectExternalAuthenticate() => Hex.HexToBytes("848233001045330AB30BB1A079A8E7F77376DB9F2C"); - - private static StaticKeys GetStaticKeys() - { - return new StaticKeys(); - } - - private static Session GetSession() - { - return new Session(); - } - - [Fact] - public void Constructor_GivenStaticKeys_Succeeds() - { - _ = GetSession(); - } - - [Fact] - public void BuildInitializeUpdate_GivenNullHostChallenge_ThrowsArgumentNullException() - { - Session sess = GetSession(); -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _ = Assert.Throws(() => sess.BuildInitializeUpdate(0, null)); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void BuildInitializeUpdate_GivenHostChallengeWrongLength_ThrowsArgumentException() - { - byte[] hostChallengeWrongLength = new byte[9]; - Session sess = GetSession(); - _ = Assert.Throws(() => sess.BuildInitializeUpdate(0, hostChallengeWrongLength)); - } - - [Fact] - public void BuildInitializeUpdate_GivenHostChallenge_BuildsCorrectInitializeUpdate() - { - // Arrange - Session sess = GetSession(); - - // Act - InitializeUpdateCommand initializeUpdateCommand = sess.BuildInitializeUpdate(0, GetChallenge()); - byte[] initializeUpdateCommandBytes = initializeUpdateCommand.CreateCommandApdu().AsByteArray(); - - // Assert - Assert.Equal(initializeUpdateCommandBytes, GetCorrectInitializeUpdate()); - } - - [Fact] - public void LoadInitializeUpdate_GivenNullInitializeUpdateResponse_ThrowsArgumentNullException() - { - Session sess = GetSession(); - InitializeUpdateCommand initializeUpdateCommand = sess.BuildInitializeUpdate(0, GetChallenge()); -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _ = Assert.Throws(() => sess.LoadInitializeUpdateResponse(null, GetStaticKeys())); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void LoadInitializeUpdate_GivenNullStaticKeys_ThrowsArgumentNullException() - { - Session sess = GetSession(); - InitializeUpdateCommand initializeUpdateCommand = sess.BuildInitializeUpdate(0, GetChallenge()); - var correctResponse = new InitializeUpdateResponse(new ResponseApdu(GetInitializeUpdateResponse())); -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _ = Assert.Throws(() => sess.LoadInitializeUpdateResponse(correctResponse, null)); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void LoadInitializeUpdate_CalledBeforeBuildInitializeUpdate_ThrowsInvalidOperationException() - { - Session sess = GetSession(); - var correctResponse = new InitializeUpdateResponse(new ResponseApdu(GetInitializeUpdateResponse())); -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _ = Assert.Throws(() => sess.LoadInitializeUpdateResponse(correctResponse, GetStaticKeys())); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void Session_GivenInitializeUpdateResponse_BuildsCorrectExternalAuthenticate() - { - // Arrange - Session sess = GetSession(); - InitializeUpdateCommand initializeUpdateCommand = sess.BuildInitializeUpdate(0, GetChallenge()); - sess.LoadInitializeUpdateResponse(initializeUpdateCommand.CreateResponseForApdu(new ResponseApdu(GetInitializeUpdateResponse())), GetStaticKeys()); - - // Act - ExternalAuthenticateCommand externalAuthenticateCommand = sess.BuildExternalAuthenticate(); - byte[] externalAuthenticateCommandBytes = externalAuthenticateCommand.CreateCommandApdu().AsByteArray(); - - // Assert - Assert.Equal(externalAuthenticateCommandBytes, GetCorrectExternalAuthenticate()); - } - } -} diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/YubiKeyDeviceExtensionsTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/YubiKeyDeviceExtensionsTests.cs deleted file mode 100644 index 544dc9680..000000000 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/YubiKeyDeviceExtensionsTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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 Moq; -using Xunit; -using Yubico.Core.Devices.SmartCard; -using Yubico.YubiKey.Scp03; - -namespace Yubico.YubiKey -{ -#pragma warning disable CS0618 // Specifically testing this soon-to-be-deprecated feature - public class YubiKeyDeviceExtensionsTests - { - [Fact] - public void WithScp03_WhenDeviceIsNull_Throws() - { - YubiKeyDevice? ykDevice = null; - _ = Assert.Throws(() => ykDevice!.WithScp03(new StaticKeys())); - } - - [Fact] - public void WithScp03_WhenDeviceDoesNotHaveSmartCard_ThrowsException() - { - var ykDeviceInfo = new YubiKeyDeviceInfo() - { - AvailableUsbCapabilities = YubiKeyCapabilities.All, - EnabledUsbCapabilities = YubiKeyCapabilities.All, - }; - - var ykDevice = new YubiKeyDevice(null, null, null, ykDeviceInfo); - _ = Assert.Throws(() => ykDevice.WithScp03(new StaticKeys())); - } - - [Fact] - public void WithScp03_WhenDeviceCorrect_Succeeds() - { - var mockSmartCard = new Mock(); - var ykDeviceInfo = new YubiKeyDeviceInfo() - { - AvailableUsbCapabilities = YubiKeyCapabilities.All, - EnabledUsbCapabilities = YubiKeyCapabilities.All, - }; - - var ykDevice = new YubiKeyDevice(mockSmartCard.Object, null, null, ykDeviceInfo); - IYubiKeyDevice scp03Device = ykDevice.WithScp03(new StaticKeys()); - _ = Assert.IsAssignableFrom(scp03Device); - } - } -#pragma warning restore CS0618 -} diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/YubikeyApplicationTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/YubikeyApplicationTests.cs new file mode 100644 index 000000000..f71e42120 --- /dev/null +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/YubikeyApplicationTests.cs @@ -0,0 +1,68 @@ +// Copyright 2024 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 Xunit; + +namespace Yubico.YubiKey +{ + public class YubiKeyApplicationExtensionsTests + { + [Theory] + [InlineData(YubiKeyApplication.Management, new byte[] { 0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 })] + [InlineData(YubiKeyApplication.Otp, new byte[] { 0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01 })] + [InlineData(YubiKeyApplication.FidoU2f, new byte[] { 0xa0, 0x00, 0x00, 0x06, 0x47, 0x2f, 0x00, 0x01 })] + [InlineData(YubiKeyApplication.Piv, new byte[] { 0xa0, 0x00, 0x00, 0x03, 0x08 })] + public void GetIso7816ApplicationId_ReturnsCorrectId(YubiKeyApplication application, byte[] expectedId) + { + byte[] result = application.GetIso7816ApplicationId(); + Assert.Equal(expectedId, result); + } + + [Fact] + public void GetIso7816ApplicationId_ThrowsForUnsupportedApplication() + { + var unsupportedApp = (YubiKeyApplication)999; + Assert.Throws(() => unsupportedApp.GetIso7816ApplicationId()); + } + + [Fact] + public void ApplicationIds_ContainsAllApplications() + { + var result = YubiKeyApplicationExtensions.Iso7816ApplicationIds; + Assert.Equal(11, result.Count); + Assert.Contains(YubiKeyApplication.Management, result.Keys); + Assert.Contains(YubiKeyApplication.Otp, result.Keys); + Assert.Contains(YubiKeyApplication.FidoU2f, result.Keys); + // Add assertions for other applications + } + + [Theory] + [InlineData(new byte[] { 0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }, YubiKeyApplication.Management)] + [InlineData(new byte[] { 0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01 }, YubiKeyApplication.Otp)] + [InlineData(new byte[] { 0xa0, 0x00, 0x00, 0x06, 0x47, 0x2f, 0x00, 0x01 }, YubiKeyApplication.FidoU2f)] + public void GetById_ReturnsCorrectApplication(byte[] applicationId, YubiKeyApplication expectedApplication) + { + var result = YubiKeyApplicationExtensions.GetYubiKeyApplication(applicationId); + Assert.Equal(expectedApplication, result); + } + + [Fact] + public void GetById_ThrowsForUnknownApplicationId() + { + byte[] unknownId = new byte[] { 0x00, 0x11, 0x22, 0x33 }; + Assert.Throws(() => YubiKeyApplicationExtensions.GetYubiKeyApplication(unknownId)); + } + } +} diff --git a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/HollowYubiKeyDevice.cs b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/HollowYubiKeyDevice.cs index 6dfb33f44..e3c8f741e 100644 --- a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/HollowYubiKeyDevice.cs +++ b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/HollowYubiKeyDevice.cs @@ -14,7 +14,8 @@ using System; using Yubico.Core.Devices; -using Yubico.YubiKey.Scp03; +using Yubico.YubiKey.Scp; + namespace Yubico.YubiKey.TestUtilities { @@ -141,9 +142,9 @@ public HollowYubiKeyDevice(bool alwaysAuthenticatePiv = false) EnabledUsbCapabilities = 0; } - public IYubiKeyConnection Connect(YubiKeyApplication yubikeyApplication) + public IYubiKeyConnection Connect(YubiKeyApplication yubiKeyApplication) { - var connection = new HollowConnection(yubikeyApplication, FirmwareVersion) + var connection = new HollowConnection(yubiKeyApplication, FirmwareVersion) { AlwaysAuthenticatePiv = _alwaysAuthenticatePiv, }; @@ -151,57 +152,87 @@ public IYubiKeyConnection Connect(YubiKeyApplication yubikeyApplication) return connection; } - public IScp03YubiKeyConnection ConnectScp03(YubiKeyApplication yubikeyApplication, StaticKeys scp03Keys) + public IYubiKeyConnection Connect(byte[] applicationId) { throw new NotImplementedException(); } - public IYubiKeyConnection Connect(byte[] applicationId) + [Obsolete("Obsolete")] + public IScp03YubiKeyConnection ConnectScp03(YubiKeyApplication yubikeyApplication, Yubico.YubiKey.Scp03.StaticKeys scp03Keys) { throw new NotImplementedException(); } - public IScp03YubiKeyConnection ConnectScp03(byte[] applicationId, StaticKeys scp03Keys) + [Obsolete("Obsolete")] + public IScp03YubiKeyConnection ConnectScp03(byte[] applicationId, Yubico.YubiKey.Scp03.StaticKeys scp03Keys) { throw new NotImplementedException(); } - /// - bool IYubiKeyDevice.HasSameParentDevice(IDevice other) + public IScpYubiKeyConnection Connect(YubiKeyApplication yubikeyApplication, ScpKeyParameters scp03Keys) { - return false; + throw new NotImplementedException(); + } + + public IScpYubiKeyConnection Connect(byte[] applicationId, ScpKeyParameters scp03Keys) + { + throw new NotImplementedException(); } - bool IYubiKeyDevice.TryConnect( + public bool TryConnect( YubiKeyApplication application, out IYubiKeyConnection connection) { throw new NotImplementedException(); } - bool IYubiKeyDevice.TryConnectScp03( + public bool TryConnect( + byte[] applicationId, + out IYubiKeyConnection connection) + { + throw new NotImplementedException(); + } + + [Obsolete("Obsolete")] + public bool TryConnectScp03( YubiKeyApplication application, - StaticKeys scp03Keys, + Yubico.YubiKey.Scp03.StaticKeys scp03Keys, out IScp03YubiKeyConnection connection) { throw new NotImplementedException(); } - bool IYubiKeyDevice.TryConnect( + [Obsolete("Obsolete")] + public bool TryConnectScp03( byte[] applicationId, - out IYubiKeyConnection connection) + Yubico.YubiKey.Scp03.StaticKeys scp03Keys, + out IScp03YubiKeyConnection connection) { throw new NotImplementedException(); } - bool IYubiKeyDevice.TryConnectScp03( + public bool TryConnect( + YubiKeyApplication application, + ScpKeyParameters keyParameters, + out IScpYubiKeyConnection connection) + { + throw new NotImplementedException(); + } + + public bool TryConnect( byte[] applicationId, - StaticKeys scp03Keys, - out IScp03YubiKeyConnection connection) + ScpKeyParameters keyParameters, + out IScpYubiKeyConnection connection) { throw new NotImplementedException(); } + /// + bool IYubiKeyDevice.HasSameParentDevice(IDevice other) + { + return false; + } + public void SetEnabledNfcCapabilities(YubiKeyCapabilities yubiKeyCapabilities) => throw new NotImplementedException(); @@ -246,6 +277,16 @@ public void UnlockConfiguration(ReadOnlySpan lockCode) throw new NotImplementedException(); } + public void SetLegacyDeviceConfiguration( + YubiKeyCapabilities yubiKeyInterfaces, + byte challengeResponseTimeout, + bool touchEjectEnabled, + int autoEjectTimeout = 0, + ScpKeyParameters? keyParameters = null) + { + throw new NotImplementedException(); + } + public void SetLegacyDeviceConfiguration( YubiKeyCapabilities yubiKeyInterfaces, byte challengeResponseTimeout, bool touchEjectEnabled, int autoEjectTimeout = 0) diff --git a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/IntegrationTestDeviceEnumeration.cs b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/IntegrationTestDeviceEnumeration.cs index 8da5e7d69..29a070a0d 100644 --- a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/IntegrationTestDeviceEnumeration.cs +++ b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/IntegrationTestDeviceEnumeration.cs @@ -50,7 +50,8 @@ private static readonly Lazy SingleInstance private const string YubikeyIntegrationtestAllowedKeysName = "YUBIKEY_INTEGRATIONTEST_ALLOWEDKEYS"; private readonly string _allowlistFileName = $"{YubikeyIntegrationtestAllowedKeysName}.txt"; - public IntegrationTestDeviceEnumeration(string? configDirectory = null) + public IntegrationTestDeviceEnumeration( + string? configDirectory = null) { var defaultDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Yubico"); @@ -89,21 +90,24 @@ public IntegrationTestDeviceEnumeration(string? configDirectory = null) /// /// /// - public static IYubiKeyDevice GetBySerial(int serialNumber) + public static IYubiKeyDevice GetBySerial( + int serialNumber) => GetTestDevices().Single(d => d.SerialNumber == serialNumber); /// /// Enumerates all YubiKey test devices on a system. /// /// The allow-list filtered list of available Yubikeys - public static IList GetTestDevices(Transport transport = Transport.All) + public static IList GetTestDevices( + Transport transport = Transport.All) { return YubiKeyDevice .FindByTransport(transport) .Where(IsAllowedKey) .ToList(); - static bool IsAllowedKey(IYubiKeyDevice key) + static bool IsAllowedKey( + IYubiKeyDevice key) => key.SerialNumber == null || Instance.AllowedSerialNumbers.Contains(key.SerialNumber.Value.ToString()); } @@ -113,12 +117,15 @@ static bool IsAllowedKey(IYubiKeyDevice key) /// /// The type of the device. /// The transport the device must support. + /// The earliest version number the + /// caller is willing to accept. Defaults to the minimum version for the given device. /// The allow-list filtered YubiKey that was found. public static IYubiKeyDevice GetTestDevice( StandardTestDevice testDeviceType = StandardTestDevice.Fw5, - Transport transport = Transport.All) + Transport transport = Transport.All, + FirmwareVersion? minimumFirmwareVersion = null) => GetTestDevices(transport) - .SelectByStandardTestDevice(testDeviceType); + .SelectByStandardTestDevice(testDeviceType, minimumFirmwareVersion, transport); /// /// Get YubiKey test device of specified transport and for which the @@ -128,12 +135,14 @@ public static IYubiKeyDevice GetTestDevice( /// The earliest version number the /// caller is willing to accept. /// The allow-list filtered YubiKey that was found. - public static IYubiKeyDevice GetTestDevice(Transport transport, FirmwareVersion minimumFirmwareVersion) + public static IYubiKeyDevice GetTestDevice( + Transport transport, + FirmwareVersion minimumFirmwareVersion) => GetTestDevices(transport) .SelectByMinimumVersion(minimumFirmwareVersion); - - private static void CreateAllowListFileIfMissing(string allowListFilePath) + private static void CreateAllowListFileIfMissing( + string allowListFilePath) { if (File.Exists(allowListFilePath)) { diff --git a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/StandardTestDevice.cs b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/StandardTestDevice.cs index c8fb1e939..7253f9e1b 100644 --- a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/StandardTestDevice.cs +++ b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/StandardTestDevice.cs @@ -36,16 +36,6 @@ public enum StandardTestDevice /// Fw5Fips, - /// - /// Major version 5, USB C Lightning, not FIPS - /// - Fw5Ci, - - /// - /// Major version 5, USB C Keychain - /// - Fw5C, - /// /// Major version 5, USB A biometric keychain, not FIPS /// diff --git a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/TestDeviceSelection.cs b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/TestDeviceSelection.cs index 09662c0ea..5d5954423 100644 --- a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/TestDeviceSelection.cs +++ b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/TestDeviceSelection.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using TestDev = Yubico.YubiKey.TestUtilities.IntegrationTestDeviceEnumeration; @@ -27,7 +28,8 @@ public static class TestDeviceSelection /// /// Thrown if the test device could not be found. /// - public static IYubiKeyDevice RenewDeviceEnumeration(int serialNumber) + public static IYubiKeyDevice RenewDeviceEnumeration( + int serialNumber) { const int maxReconnectAttempts = 40; const int sleepDuration = 100; //ms @@ -54,52 +56,85 @@ public static IYubiKeyDevice RenewDeviceEnumeration(int serialNumber) /// Retrieves a single based on test device requirements. /// /// - /// Thrown when is not a recognized value. + /// Thrown when is not a recognized value. /// /// /// Thrown when the input sequence did not contain a valid test device. /// + /// The type of the device. + /// The earliest version number the + /// caller is willing to accept. Defaults to the minimum version for the given device. + /// The list of yubikeys to select from + /// The desired transport + /// The allow-list filtered YubiKey that was found. public static IYubiKeyDevice SelectByStandardTestDevice( this IEnumerable yubiKeys, - StandardTestDevice testDevice) + StandardTestDevice testDeviceType, + FirmwareVersion? minimumFirmwareVersion = null, + Transport transport = Transport.All + ) { var devices = yubiKeys as IYubiKeyDevice[] ?? yubiKeys.ToArray(); if (!devices.Any()) { - throw new InvalidOperationException("Could not find any connected Yubikeys"); + ThrowDeviceNotFoundException("Could not find any connected Yubikeys (Transport: {transport})", devices); } - return testDevice switch + var devicesVersionFiltered = + devices.Where(d => d.FirmwareVersion >= MatchVersion(testDeviceType, minimumFirmwareVersion)); + + return testDeviceType switch { StandardTestDevice.Fw3 => SelectDevice(3), StandardTestDevice.Fw4Fips => SelectDevice(4, isFipsSeries: true), StandardTestDevice.Fw5 => SelectDevice(5), StandardTestDevice.Fw5Fips => SelectDevice(5, formFactor: FormFactor.UsbAKeychain, isFipsSeries: true), StandardTestDevice.Fw5Bio => SelectDevice(5, formFactor: FormFactor.UsbABiometricKeychain), - _ => throw new ArgumentException("Invalid test device value.", nameof(testDevice)), + _ => throw new ArgumentException("Invalid test device value.", nameof(testDeviceType)), }; - IYubiKeyDevice SelectDevice(int majorVersion, FormFactor? formFactor = null, bool isFipsSeries = false) + IYubiKeyDevice SelectDevice( + int majorVersion, + FormFactor? formFactor = null, + bool isFipsSeries = false) { IYubiKeyDevice device = null!; try { - bool MatchingDeviceSelector(IYubiKeyDevice d) => + bool MatchingDeviceSelector( + IYubiKeyDevice d) => d.FirmwareVersion.Major == majorVersion && (formFactor is null || d.FormFactor == formFactor) && d.IsFipsSeries == isFipsSeries; - device = devices.First(MatchingDeviceSelector); + device = devicesVersionFiltered.First(MatchingDeviceSelector); } catch (InvalidOperationException) { - ThrowDeviceNotFoundException($"Target test device not found ({testDevice})", devices); + ThrowDeviceNotFoundException($"Target test device not found ({testDeviceType}, Transport: {transport})", devices); } return device; } } + private static FirmwareVersion MatchVersion( + StandardTestDevice testDeviceType, + FirmwareVersion? minimumFirmwareVersion) + { + if (minimumFirmwareVersion is { }) + { + return minimumFirmwareVersion; + } + + return testDeviceType switch + { + StandardTestDevice.Fw3 => FirmwareVersion.V3_1_0, + StandardTestDevice.Fw4Fips => FirmwareVersion.V4_0_0, + _ => FirmwareVersion.V5_0_0, + }; + } + public static IYubiKeyDevice SelectByMinimumVersion( this IEnumerable yubiKeys, FirmwareVersion minimumFirmwareVersion) @@ -116,16 +151,20 @@ public static IYubiKeyDevice SelectByMinimumVersion( ThrowDeviceNotFoundException("No matching YubiKey found", devices); } - return device!; + return device; } - private static void ThrowDeviceNotFoundException(string errorMessage, IYubiKeyDevice[] devices) + [DoesNotReturn] + private static void ThrowDeviceNotFoundException( + string errorMessage, + IYubiKeyDevice[] devices) { var connectedDevicesText = FormatConnectedDevices(devices); throw new DeviceNotFoundException($"{errorMessage}. {connectedDevicesText}"); } - private static string FormatConnectedDevices(IReadOnlyCollection devices) + private static string FormatConnectedDevices( + IReadOnlyCollection devices) { var deviceText = devices.Select(y => $"{{{y.FirmwareVersion}, {y.FormFactor}, IsFipsSeries: {y.IsFipsSeries}}}"); @@ -139,6 +178,9 @@ private static string FormatConnectedDevices(IReadOnlyCollection // Custom test exception inheriting from InvalidOperationException as some test code depends on InvalidOperationExceptions public class DeviceNotFoundException : InvalidOperationException { - public DeviceNotFoundException(string message) : base(message) { } + public DeviceNotFoundException( + string message) : base(message) + { + } } } diff --git a/build/CompilerSettings.props b/build/CompilerSettings.props index db506cd74..36737af72 100644 --- a/build/CompilerSettings.props +++ b/build/CompilerSettings.props @@ -61,6 +61,8 @@ on things like conditions in this file. --> @(NoWarn);NU5105 + en-US + false - - - en-US - + + false + + + \ No newline at end of file diff --git a/build/ProjectTypes.props b/build/ProjectTypes.props index ea57b8051..75c2e5400 100644 --- a/build/ProjectTypes.props +++ b/build/ProjectTypes.props @@ -44,6 +44,14 @@ limitations under the License. --> true false + + true + false +