Skip to content

feat: Added Ed25519 and X25519 keys to the PIV application #210

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 76 commits into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
a9b255a
wip
DennisDyallo Feb 19, 2025
de74c5a
fix: field _readerName cannot be auto property
DennisDyallo Feb 19, 2025
7febac6
misc: set test projects to target latest lts version (8)
DennisDyallo Feb 19, 2025
80460dc
Merge branch 'develop' into feature/new-piv-keys
DennisDyallo Feb 20, 2025
fd544f3
feat: add support for Sign, Generate and Import for Ed25519
DennisDyallo Feb 28, 2025
f4be7b7
tests: wip on tests
DennisDyallo Feb 28, 2025
d481539
feat: add support for X25519 generate
DennisDyallo Feb 28, 2025
18182fa
fix: GetMetadata can now parse 25519 keys
DennisDyallo Mar 1, 2025
2958ea1
feat: add support for x25519 key agreement
DennisDyallo Mar 4, 2025
b349a2b
tests: add tests for ed25519, x25519
DennisDyallo Mar 4, 2025
56507f9
misc: change to uppercase
DennisDyallo Mar 4, 2025
f075f6e
feat: add algorithm selection to sign method
DennisDyallo Mar 4, 2025
b7bde38
docs: fix piv movekey doc
DennisDyallo Mar 4, 2025
d3a73bc
Works:
DennisDyallo Mar 17, 2025
5290660
fixed ECPublicKeyParametersTests
DennisDyallo Mar 18, 2025
83097ed
Works:
DennisDyallo Mar 18, 2025
34f2f35
changed KeyDefinitions
DennisDyallo Mar 18, 2025
828e7ff
changed KeyDefinitions again so that the classes are not nested
DennisDyallo Mar 19, 2025
0bb8f1b
added feature toggle, removed use of ed and x25519 in favor of common…
DennisDyallo Mar 19, 2025
bffe8fe
change name to ECDSA
DennisDyallo Mar 20, 2025
e5dbac3
fix: apply length normalization for windows
DennisDyallo Mar 20, 2025
4810c56
misc: remove namespace
DennisDyallo Mar 21, 2025
c834e92
work 240331
DennisDyallo Mar 31, 2025
dd644bf
tests 240331
DennisDyallo Mar 31, 2025
ab70699
feat: add zeroingmemoryhandle, clear sensitive data in AsnPrivateKeyR…
DennisDyallo Apr 3, 2025
879094a
tests,feat
DennisDyallo Apr 3, 2025
abbe8e1
fix: clear all data
DennisDyallo Apr 3, 2025
ed5f4a1
fix: use property
DennisDyallo Apr 3, 2025
e1c539c
refactor: use tlvwriter
DennisDyallo Apr 3, 2025
ca1d193
misc: removed comments
DennisDyallo Apr 3, 2025
8aee0f5
refactor: AsnReaders, AsnWriters, KeyParameters
DennisDyallo Apr 3, 2025
60137c2
misc: remove unused directive
DennisDyallo Apr 3, 2025
91c3442
misc: made PivEcc/RsaPrivate/PublicKey obsolete
DennisDyallo Apr 3, 2025
1643221
misc: change exception type
DennisDyallo Apr 3, 2025
7d49beb
refactor: reuse keydefinition and get rsa by length
DennisDyallo Apr 3, 2025
bbe8f50
fix: clear D parameter
DennisDyallo Apr 3, 2025
846f448
misc: mark as obsolete
DennisDyallo Apr 3, 2025
f54ced6
misc: mark as internal
DennisDyallo Apr 3, 2025
5a12ddf
tests: fix tests
DennisDyallo Apr 3, 2025
b96dd39
misc: use factory methods instead of obsolete constructors
DennisDyallo Apr 3, 2025
a148a65
tests: fix tests
DennisDyallo Apr 3, 2025
f19835e
misc: disable need for warnings as errors in sampleprojects
DennisDyallo Apr 3, 2025
6375336
misc: consolidate logic on private key reader
DennisDyallo Apr 3, 2025
792a5b8
improved documentation, minor refactorings,
DennisDyallo Apr 3, 2025
476b8df
worked on keyparameterspivhelper
DennisDyallo Apr 3, 2025
5bccd24
revert change, path of lease surprise
DennisDyallo Apr 3, 2025
f25c281
WIP update PIV samples to support curve25519
AdamVe Apr 4, 2025
87a29b0
wip: Add support for Ed25519 in PIV sample code
DennisDyallo Apr 7, 2025
2593ce5
fix: add backwards compatibility for PivEccPrivateKeys
DennisDyallo Apr 7, 2025
3d816f3
tests: fix testkeys loading legacy pivprivatekeys
DennisDyallo Apr 7, 2025
ecab1e9
tests: add test to verify ed25519 signature using gen key
DennisDyallo Apr 7, 2025
9f85b64
tests: misc changes
DennisDyallo Apr 7, 2025
d6857c2
misc: ignore obsolete warning
DennisDyallo Apr 7, 2025
b37d338
misc: misc changes in Piv key parameters handling
DennisDyallo Apr 7, 2025
e426906
tests: add proper ECDH tests
DennisDyallo Apr 7, 2025
ec842bb
misc: minor work
DennisDyallo Apr 7, 2025
85a8319
samples: revert sample code changes
DennisDyallo Apr 7, 2025
fd65d17
Revert "wip: Add support for Ed25519 in PIV sample code"
DennisDyallo Apr 7, 2025
9c3c41d
tests: fix movekey test
DennisDyallo Apr 7, 2025
acee9d4
misc: name change of KeyDefinitions.cs fields
DennisDyallo Apr 7, 2025
55b67db
Merge branch 'develop' into feature/new-piv-keys-2
DennisDyallo Apr 7, 2025
33684a9
misc: address PR feedback
DennisDyallo Apr 8, 2025
639911c
misc: rename method
DennisDyallo Apr 8, 2025
38a74f3
tests:
DennisDyallo Apr 9, 2025
b518ded
refactor: use PivKeyConverter to convert between formats
DennisDyallo Apr 9, 2025
cbfce51
refactor: common methods for base class PrivateKey
DennisDyallo Apr 9, 2025
a5bb31e
feat: add various extensions to KeyType
DennisDyallo Apr 9, 2025
43ae8f6
test: updated tests
DennisDyallo Apr 9, 2025
cb54ca2
docs: edited comments
DennisDyallo Apr 9, 2025
030bc36
fix: size calculation for EC keys in Piv
DennisDyallo Apr 9, 2025
ee2051e
tests: Added tests to verify that the private key data is cleared on …
DennisDyallo Apr 9, 2025
a555bdb
misc: consistency
DennisDyallo Apr 9, 2025
3025188
tests, misc: fix test, mark as false obsolete
DennisDyallo Apr 9, 2025
73968f0
misc: set constructor as protected
DennisDyallo Apr 9, 2025
b708a98
misc: remove unused method
DennisDyallo Apr 9, 2025
e339ff3
misc: address pr feedback
DennisDyallo Apr 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 44 additions & 4 deletions Yubico.Core/src/Yubico/Core/Tlv/TlvObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,37 @@
// limitations under the License.

using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Security.Cryptography;

namespace Yubico.Core.Tlv
{
/// <summary>
/// Tag, length, Value structure that helps to parse APDU response data.
/// This class handles BER-TLV encoded data with determinate length.
/// </summary>
public class TlvObject
public sealed class TlvObject : IDisposable
{
/// <summary>
/// Returns the tag.
/// </summary>
public int Tag { get; }
public int Tag { get; private set; }

/// <summary>
/// Returns the value.
/// Returns a copy of the value.
/// </summary>
public Memory<byte> Value => _bytes.Skip(_offset).Take(Length).ToArray();

/// <summary>
/// Returns the length of the value.
/// </summary>
public int Length { get; }
public int Length { get; private set; }

private readonly byte[] _bytes;
private readonly int _offset;
private bool _disposed;

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

Tag = tag;

// Create a copy of the input value
byte[] valueBuffer = value.ToArray();
using var ms = new MemoryStream();
Expand Down Expand Up @@ -113,6 +117,22 @@ public static TlvObject Parse(ReadOnlySpan<byte> data)
ReadOnlySpan<byte> buffer = data;
return ParseFrom(ref buffer);
}

/// <inheritdoc cref="TlvObject.Parse(ReadOnlySpan{byte})"/>
public static bool TryParse(ReadOnlySpan<byte> data, [NotNullWhen(true)] out TlvObject? tlvObject)
{
// Poor man's TryParse
tlvObject = null;
try
{
tlvObject = ParseFrom(ref data);
return true;
}
catch
{
return false;
}
}

/// <summary>
/// Parses a TLV from a BER-TLV encoded byte array.
Expand All @@ -126,6 +146,11 @@ public static TlvObject Parse(ReadOnlySpan<byte> data)
/// <exception cref="ArgumentException">Thrown if the buffer does not contain a valid TLV.</exception>
internal static TlvObject ParseFrom(ref ReadOnlySpan<byte> buffer)
{
if (buffer.Length == 0)
{
throw new ArgumentException("Insufficient data for tag");
}

// The first byte of the TLV is the tag.
int tag = buffer[0];

Expand Down Expand Up @@ -194,5 +219,20 @@ public override string ToString()
return $"Tlv(0x{Tag:X}, {Length}, {BitConverter.ToString(Value.ToArray()).Replace("-", "")})";
#endif
}

/// <summary>
/// Dispose the object and clears its buffers
/// </summary>
public void Dispose()
{
if (_disposed)
{
return;
}
CryptographicOperations.ZeroMemory(_bytes);
Length = 0;
Tag = 0;
_disposed = true;
}
}
}
48 changes: 47 additions & 1 deletion Yubico.Core/src/Yubico/Core/Tlv/TlvObjects.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;

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

return tlv.Value.ToArray();
}

public static Memory<byte> EncodeDictionary(IReadOnlyDictionary<int, byte[]> map)
{
if (map is null)
{
throw new ArgumentNullException(nameof(map));
}

int totalSize = 0;
foreach (KeyValuePair<int, byte[]> entry in map)
{
var tlv = new TlvObject(entry.Key, entry.Value ?? Array.Empty<byte>());
ReadOnlyMemory<byte> bytes = tlv.GetBytes();
totalSize += bytes.Length;
}

byte[] result = new byte[totalSize];
int position = 0;

try
{
foreach (KeyValuePair<int, byte[]> entry in map)
{
var tlv = new TlvObject(entry.Key, entry.Value ?? Array.Empty<byte>());
byte[] tlvBytes = tlv.GetBytes().ToArray();

try
{
Buffer.BlockCopy(tlvBytes, 0, result, position, tlvBytes.Length);
position += tlvBytes.Length;
}
finally
{
CryptographicOperations.ZeroMemory(tlvBytes);
}
}

return result.AsMemory(0, position);
}
catch
{
CryptographicOperations.ZeroMemory(result);
throw;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class FakeHidListener : HidDeviceListener

public class HidDeviceListenerTests
{
// [Fact]
// [Fact] // Fails on CI because of it's trying to access the HID device which is not available
// public void Create_ReturnsInstanceOfListener()
// {
// var listener = HidDeviceListener.Create();
Expand Down
31 changes: 28 additions & 3 deletions Yubico.Core/tests/Yubico/Core/Tlv/TlvObjectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,30 @@ namespace Yubico.Core.Tlv.UnitTests
{
public class TlvObjectTests
{
[Fact]
public void Value_Property_Returns_Only_Value_Portion()
{
// Arrange
int tag = 0x7F49;
byte[] originalValue = [0x01, 0x02, 0x03, 0x04];
TlvObject tlv = new TlvObject(tag, originalValue);

// Get the full encoded bytes for reference
byte[] fullEncodedTlv = tlv.GetBytes().ToArray();

// Act
byte[] extractedValue = tlv.Value.ToArray();

// Assert

Assert.Equal(originalValue, extractedValue);
Assert.Equal(originalValue.Length, tlv.Length);
Assert.NotEqual(fullEncodedTlv, extractedValue);
Assert.True(fullEncodedTlv.Length > extractedValue.Length);
Assert.Equal(0x7F, fullEncodedTlv[0]);
Assert.Equal(0x49, fullEncodedTlv[1]);
}

[Fact]
public void TestDoubleByteTags()
{
Expand Down Expand Up @@ -74,7 +98,8 @@ public void TestUnwrap()
[Fact]
public void TestUnwrapThrowsException()
{
Assert.Throws<InvalidOperationException>(() => TlvObjects.UnpackValue(0x7F48, new byte[] { 0x7F, 0x49, 0 }));
Assert.Throws<InvalidOperationException>(() =>
TlvObjects.UnpackValue(0x7F48, new byte[] { 0x7F, 0x49, 0 }));
}

[Fact]
Expand All @@ -101,7 +126,7 @@ public void DecodeList_EmptyInput_ReturnsEmptyList()
public void DecodeMap_ValidInput_ReturnsCorrectDictionary()
{
var input = new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x02, 0xAA, 0xBB };
var result = TlvObjects.DecodeMap(input);
var result = TlvObjects.DecodeDictionary(input);

Assert.Equal(2, result.Count);
Assert.Equal(new byte[] { 0xFF }, result[0x01].ToArray());
Expand All @@ -112,7 +137,7 @@ public void DecodeMap_ValidInput_ReturnsCorrectDictionary()
public void DecodeMap_DuplicateTags_KeepsLastValue()
{
var input = new byte[] { 0x01, 0x01, 0xFF, 0x01, 0x01, 0xEE };
var result = TlvObjects.DecodeMap(input);
var result = TlvObjects.DecodeDictionary(input);

Assert.Single(result);
Assert.Equal(new byte[] { 0xEE }, result[0x01].ToArray());
Expand Down
1 change: 0 additions & 1 deletion Yubico.NET.SDK.sln
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "contributordocs", "contribu
contributordocs\allowed-dotnet-things-and-versions.md = contributordocs\allowed-dotnet-things-and-versions.md
contributordocs\code-flow-and-pull-requests.md = contributordocs\code-flow-and-pull-requests.md
contributordocs\getting-started.md = contributordocs\getting-started.md
contributordocs\polyfills.md = contributordocs\polyfills.md
contributordocs\README.md = contributordocs\README.md
contributordocs\testing.md = contributordocs\testing.md
contributordocs\useful-links.md = contributordocs\useful-links.md
Expand Down
1 change: 1 addition & 0 deletions Yubico.YubiKey/src/Yubico.YubiKey.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ limitations under the License. -->
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Formats.Asn1" Version="9.0.3" />

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