Skip to content

Commit dc117e3

Browse files
authored
Merge pull request #210 from Yubico/feature/new-piv-keys-2
Feature: Added Ed25519 and X25519 keys to the PIV application
2 parents 2b9328d + e339ff3 commit dc117e3

File tree

159 files changed

+10394
-2778
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

159 files changed

+10394
-2778
lines changed

Yubico.Core/src/Yubico/Core/Tlv/TlvObject.cs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,37 @@
1313
// limitations under the License.
1414

1515
using System;
16+
using System.Diagnostics.CodeAnalysis;
1617
using System.IO;
1718
using System.Linq;
19+
using System.Security.Cryptography;
1820

1921
namespace Yubico.Core.Tlv
2022
{
2123
/// <summary>
2224
/// Tag, length, Value structure that helps to parse APDU response data.
2325
/// This class handles BER-TLV encoded data with determinate length.
2426
/// </summary>
25-
public class TlvObject
27+
public sealed class TlvObject : IDisposable
2628
{
2729
/// <summary>
2830
/// Returns the tag.
2931
/// </summary>
30-
public int Tag { get; }
32+
public int Tag { get; private set; }
3133

3234
/// <summary>
33-
/// Returns the value.
35+
/// Returns a copy of the value.
3436
/// </summary>
3537
public Memory<byte> Value => _bytes.Skip(_offset).Take(Length).ToArray();
3638

3739
/// <summary>
3840
/// Returns the length of the value.
3941
/// </summary>
40-
public int Length { get; }
42+
public int Length { get; private set; }
4143

4244
private readonly byte[] _bytes;
4345
private readonly int _offset;
46+
private bool _disposed;
4447

4548
/// <summary>
4649
/// Creates a new TLV (Tag-Length-Value) object with the specified tag and value.
@@ -63,6 +66,7 @@ public TlvObject(int tag, ReadOnlySpan<byte> value)
6366
}
6467

6568
Tag = tag;
69+
6670
// Create a copy of the input value
6771
byte[] valueBuffer = value.ToArray();
6872
using var ms = new MemoryStream();
@@ -113,6 +117,22 @@ public static TlvObject Parse(ReadOnlySpan<byte> data)
113117
ReadOnlySpan<byte> buffer = data;
114118
return ParseFrom(ref buffer);
115119
}
120+
121+
/// <inheritdoc cref="TlvObject.Parse(ReadOnlySpan{byte})"/>
122+
public static bool TryParse(ReadOnlySpan<byte> data, [NotNullWhen(true)] out TlvObject? tlvObject)
123+
{
124+
// Poor man's TryParse
125+
tlvObject = null;
126+
try
127+
{
128+
tlvObject = ParseFrom(ref data);
129+
return true;
130+
}
131+
catch
132+
{
133+
return false;
134+
}
135+
}
116136

117137
/// <summary>
118138
/// Parses a TLV from a BER-TLV encoded byte array.
@@ -126,6 +146,11 @@ public static TlvObject Parse(ReadOnlySpan<byte> data)
126146
/// <exception cref="ArgumentException">Thrown if the buffer does not contain a valid TLV.</exception>
127147
internal static TlvObject ParseFrom(ref ReadOnlySpan<byte> buffer)
128148
{
149+
if (buffer.Length == 0)
150+
{
151+
throw new ArgumentException("Insufficient data for tag");
152+
}
153+
129154
// The first byte of the TLV is the tag.
130155
int tag = buffer[0];
131156

@@ -194,5 +219,20 @@ public override string ToString()
194219
return $"Tlv(0x{Tag:X}, {Length}, {BitConverter.ToString(Value.ToArray()).Replace("-", "")})";
195220
#endif
196221
}
222+
223+
/// <summary>
224+
/// Dispose the object and clears its buffers
225+
/// </summary>
226+
public void Dispose()
227+
{
228+
if (_disposed)
229+
{
230+
return;
231+
}
232+
CryptographicOperations.ZeroMemory(_bytes);
233+
Length = 0;
234+
Tag = 0;
235+
_disposed = true;
236+
}
197237
}
198238
}

Yubico.Core/src/Yubico/Core/Tlv/TlvObjects.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4+
using System.Security.Cryptography;
45

56
namespace Yubico.Core.Tlv
67
{
@@ -33,7 +34,7 @@ public static IReadOnlyList<TlvObject> DecodeList(ReadOnlySpan<byte> data)
3334
/// </summary>
3435
/// <param name="data">Sequence of TLV encoded data</param>
3536
/// <returns>Dictionary of Tag-Value pairs</returns>
36-
public static IReadOnlyDictionary<int, ReadOnlyMemory<byte>> DecodeMap(ReadOnlySpan<byte> data)
37+
public static IReadOnlyDictionary<int, ReadOnlyMemory<byte>> DecodeDictionary(ReadOnlySpan<byte> data)
3738
{
3839
var tlvs = new Dictionary<int, ReadOnlyMemory<byte>>();
3940
ReadOnlySpan<byte> buffer = data;
@@ -93,5 +94,50 @@ public static Memory<byte> UnpackValue(int expectedTag, ReadOnlySpan<byte> tlvDa
9394

9495
return tlv.Value.ToArray();
9596
}
97+
98+
public static Memory<byte> EncodeDictionary(IReadOnlyDictionary<int, byte[]> map)
99+
{
100+
if (map is null)
101+
{
102+
throw new ArgumentNullException(nameof(map));
103+
}
104+
105+
int totalSize = 0;
106+
foreach (KeyValuePair<int, byte[]> entry in map)
107+
{
108+
var tlv = new TlvObject(entry.Key, entry.Value ?? Array.Empty<byte>());
109+
ReadOnlyMemory<byte> bytes = tlv.GetBytes();
110+
totalSize += bytes.Length;
111+
}
112+
113+
byte[] result = new byte[totalSize];
114+
int position = 0;
115+
116+
try
117+
{
118+
foreach (KeyValuePair<int, byte[]> entry in map)
119+
{
120+
var tlv = new TlvObject(entry.Key, entry.Value ?? Array.Empty<byte>());
121+
byte[] tlvBytes = tlv.GetBytes().ToArray();
122+
123+
try
124+
{
125+
Buffer.BlockCopy(tlvBytes, 0, result, position, tlvBytes.Length);
126+
position += tlvBytes.Length;
127+
}
128+
finally
129+
{
130+
CryptographicOperations.ZeroMemory(tlvBytes);
131+
}
132+
}
133+
134+
return result.AsMemory(0, position);
135+
}
136+
catch
137+
{
138+
CryptographicOperations.ZeroMemory(result);
139+
throw;
140+
}
141+
}
96142
}
97143
}

Yubico.Core/tests/Yubico/Core/Devices/Hid/HidDeviceListenerTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class FakeHidListener : HidDeviceListener
3939

4040
public class HidDeviceListenerTests
4141
{
42-
// [Fact]
42+
// [Fact] // Fails on CI because of it's trying to access the HID device which is not available
4343
// public void Create_ReturnsInstanceOfListener()
4444
// {
4545
// var listener = HidDeviceListener.Create();

Yubico.Core/tests/Yubico/Core/Tlv/TlvObjectTests.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,30 @@ namespace Yubico.Core.Tlv.UnitTests
2121
{
2222
public class TlvObjectTests
2323
{
24+
[Fact]
25+
public void Value_Property_Returns_Only_Value_Portion()
26+
{
27+
// Arrange
28+
int tag = 0x7F49;
29+
byte[] originalValue = [0x01, 0x02, 0x03, 0x04];
30+
TlvObject tlv = new TlvObject(tag, originalValue);
31+
32+
// Get the full encoded bytes for reference
33+
byte[] fullEncodedTlv = tlv.GetBytes().ToArray();
34+
35+
// Act
36+
byte[] extractedValue = tlv.Value.ToArray();
37+
38+
// Assert
39+
40+
Assert.Equal(originalValue, extractedValue);
41+
Assert.Equal(originalValue.Length, tlv.Length);
42+
Assert.NotEqual(fullEncodedTlv, extractedValue);
43+
Assert.True(fullEncodedTlv.Length > extractedValue.Length);
44+
Assert.Equal(0x7F, fullEncodedTlv[0]);
45+
Assert.Equal(0x49, fullEncodedTlv[1]);
46+
}
47+
2448
[Fact]
2549
public void TestDoubleByteTags()
2650
{
@@ -74,7 +98,8 @@ public void TestUnwrap()
7498
[Fact]
7599
public void TestUnwrapThrowsException()
76100
{
77-
Assert.Throws<InvalidOperationException>(() => TlvObjects.UnpackValue(0x7F48, new byte[] { 0x7F, 0x49, 0 }));
101+
Assert.Throws<InvalidOperationException>(() =>
102+
TlvObjects.UnpackValue(0x7F48, new byte[] { 0x7F, 0x49, 0 }));
78103
}
79104

80105
[Fact]
@@ -101,7 +126,7 @@ public void DecodeList_EmptyInput_ReturnsEmptyList()
101126
public void DecodeMap_ValidInput_ReturnsCorrectDictionary()
102127
{
103128
var input = new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x02, 0xAA, 0xBB };
104-
var result = TlvObjects.DecodeMap(input);
129+
var result = TlvObjects.DecodeDictionary(input);
105130

106131
Assert.Equal(2, result.Count);
107132
Assert.Equal(new byte[] { 0xFF }, result[0x01].ToArray());
@@ -112,7 +137,7 @@ public void DecodeMap_ValidInput_ReturnsCorrectDictionary()
112137
public void DecodeMap_DuplicateTags_KeepsLastValue()
113138
{
114139
var input = new byte[] { 0x01, 0x01, 0xFF, 0x01, 0x01, 0xEE };
115-
var result = TlvObjects.DecodeMap(input);
140+
var result = TlvObjects.DecodeDictionary(input);
116141

117142
Assert.Single(result);
118143
Assert.Equal(new byte[] { 0xEE }, result[0x01].ToArray());

Yubico.NET.SDK.sln

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "contributordocs", "contribu
251251
contributordocs\allowed-dotnet-things-and-versions.md = contributordocs\allowed-dotnet-things-and-versions.md
252252
contributordocs\code-flow-and-pull-requests.md = contributordocs\code-flow-and-pull-requests.md
253253
contributordocs\getting-started.md = contributordocs\getting-started.md
254-
contributordocs\polyfills.md = contributordocs\polyfills.md
255254
contributordocs\README.md = contributordocs\README.md
256255
contributordocs\testing.md = contributordocs\testing.md
257256
contributordocs\useful-links.md = contributordocs\useful-links.md

Yubico.YubiKey/src/Yubico.YubiKey.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ limitations under the License. -->
118118
<PrivateAssets>all</PrivateAssets>
119119
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
120120
</PackageReference>
121+
<PackageReference Include="System.Formats.Asn1" Version="9.0.3" />
121122

122123
<!-- Remove ExcludeAssets once the package supports netcoreapp and net462 properly -->
123124
<PackageReference Include="System.Formats.Cbor" Version="7.0.0" ExcludeAssets="buildtransitive" />

0 commit comments

Comments
 (0)