From bf0cdbb5a0586e9b4a3e7ebe93d71191041f0cf6 Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Mon, 5 May 2025 12:23:04 +0200 Subject: [PATCH 1/9] fix: fix conversion between PivPrivateKey and encoded formats --- .../YubiKey/Piv/Converters/KeyExtensions.cs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/KeyExtensions.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/KeyExtensions.cs index 7600a7b38..b119fcff3 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/KeyExtensions.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/KeyExtensions.cs @@ -33,31 +33,37 @@ public static class KeyExtensions /// The public key parameters to encode. /// A BER encoded byte array containing the encoded public key. /// Thrown when the key type is not supported. - public static Memory EncodeAsPiv(this IPublicKey parameters) - { - return parameters switch + public static Memory EncodeAsPiv(this IPublicKey parameters) => + parameters switch { ECPublicKey p => PivKeyEncoder.EncodeECPublicKey(p), RSAPublicKey p => PivKeyEncoder.EncodeRSAPublicKey(p), Curve25519PublicKey p => PivKeyEncoder.EncodeCurve25519PublicKey(p), - _ => throw new ArgumentException("The type conversion for the specified key type is not supported", nameof(parameters)) + + #pragma warning disable CS0618 // Type or member is obsolete + PivPublicKey p => p.PivEncodedPublicKey.ToArray(), + #pragma warning restore CS0618 // Type or member is obsolete + _ => throw new ArgumentException( + "The type conversion for the specified key type is not supported", nameof(parameters)) }; - } - + /// /// Encodes a private key into the PIV format. /// /// The private key parameters to encode. /// A BER encoded byte array containing the encoded private key. /// Thrown when the key type is not supported or when RSA key components have invalid lengths. - public static Memory EncodeAsPiv(this IPrivateKey parameters) - { - return parameters switch + public static Memory EncodeAsPiv(this IPrivateKey parameters) => + parameters switch { ECPrivateKey p => PivKeyEncoder.EncodeECPrivateKey(p), RSAPrivateKey p => PivKeyEncoder.EncodeRSAPrivateKey(p), Curve25519PrivateKey p => PivKeyEncoder.EncodeCurve25519PrivateKey(p), - _ => throw new ArgumentException("The type conversion for the specified key type is not supported", nameof(parameters)) + + #pragma warning disable CS0618 // Type or member is obsolete + PivPrivateKey p => p.EncodedPrivateKey.ToArray(), + #pragma warning restore CS0618 // Type or member is obsolete + _ => throw new ArgumentException( + "The type conversion for the specified key type is not supported", nameof(parameters)) }; - } } From 7fdc339a7f4cbca5f96cef8de747b135789e6111 Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Mon, 5 May 2025 12:23:50 +0200 Subject: [PATCH 2/9] fix: fix setting of Algorithm in obsolete PivEccPrivateKey constructor --- .../Yubico/YubiKey/Piv/PivEccPrivateKey.cs | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPrivateKey.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPrivateKey.cs index ee58def59..16fde1d9f 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPrivateKey.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPrivateKey.cs @@ -13,6 +13,7 @@ // limitations under the License. using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Security.Cryptography; using Yubico.Core.Tlv; @@ -27,6 +28,7 @@ namespace Yubico.YubiKey.Piv /// of a point on the curve. So for ECC P-256, each coordinate is 32 bytes /// (256 bits), so the private value will be 32 bytes. /// + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public sealed class PivEccPrivateKey : PivPrivateKey { private const int EccP256PrivateKeySize = 32; @@ -66,29 +68,19 @@ private PivEccPrivateKey() /// /// The size of the private value is not supported by the YubiKey. /// + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey is deprecated. Use ECPublicKey, ECPrivateKey instead", false)] public PivEccPrivateKey( ReadOnlySpan privateValue, PivAlgorithm? algorithm = null) { if (algorithm.HasValue) { - int expectedSize = algorithm.Value.GetPivKeyDefinition().KeyDefinition.LengthInBytes; - if(privateValue.Length != expectedSize) - { - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, ExceptionMessages.InvalidPrivateKeyData)); - } + ValidateSize(privateValue, algorithm); + Algorithm = algorithm.Value; } else { - Algorithm = privateValue.Length switch - { - EccP256PrivateKeySize => PivAlgorithm.EccP256, - EccP384PrivateKeySize => PivAlgorithm.EccP384, - _ => throw new ArgumentException(string.Format( - CultureInfo.CurrentCulture, ExceptionMessages.InvalidPrivateKeyData)) - }; + Algorithm = GetBySize(privateValue); } int tag = Algorithm switch @@ -104,6 +96,28 @@ public PivEccPrivateKey( _privateValue = new Memory(privateValue.ToArray()); } + private static void ValidateSize(ReadOnlySpan privateValue, [DisallowNull] PivAlgorithm? algorithm) + { + int expectedSize = algorithm.Value.GetPivKeyDefinition().KeyDefinition.LengthInBytes; + if(privateValue.Length != expectedSize) + { + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, ExceptionMessages.InvalidPrivateKeyData)); + } + } + + private static PivAlgorithm GetBySize(ReadOnlySpan privateValue) + { + return privateValue.Length switch + { + EccP256PrivateKeySize => PivAlgorithm.EccP256, + EccP384PrivateKeySize => PivAlgorithm.EccP384, + _ => throw new ArgumentException(string.Format( + CultureInfo.CurrentCulture, ExceptionMessages.InvalidPrivateKeyData)) + }; + } + /// /// Create a new instance of an ECC private key object based on the /// encoding. @@ -118,6 +132,7 @@ public PivEccPrivateKey( /// /// The encoding of the private key is not supported. /// + [Obsolete("Obsolete")] public static PivEccPrivateKey CreateEccPrivateKey( ReadOnlyMemory encodedPrivateKey, PivAlgorithm? pivAlgorithm) From 9c0008cb9c31016d85eab168b571da1a9f07023d Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Mon, 5 May 2025 12:25:25 +0200 Subject: [PATCH 3/9] obsolete: set piv keys as obsolete --- .../YubiKey/Cryptography/EcdsaVerify.cs | 20 ++++++++++- .../Piv/Commands/GenerateKeyPairResponse.cs | 6 ++++ .../Commands/ImportAsymmetricKeyCommand.cs | 6 ++-- .../YubiKey/Piv/Converters/PivKeyEncoder.cs | 18 +++++----- .../Yubico/YubiKey/Piv/PivEccPrivateKey.cs | 3 +- .../src/Yubico/YubiKey/Piv/PivEccPublicKey.cs | 6 ++-- .../src/Yubico/YubiKey/Piv/PivMetadata.cs | 3 +- .../src/Yubico/YubiKey/Piv/PivPrivateKey.cs | 3 ++ .../src/Yubico/YubiKey/Piv/PivPublicKey.cs | 36 ++++++++++++------- .../Yubico/YubiKey/Piv/PivRsaPrivateKey.cs | 1 + .../src/Yubico/YubiKey/Piv/PivRsaPublicKey.cs | 1 + .../YubiKey/Piv/PivSession.Attestation.cs | 3 +- .../Yubico/YubiKey/Piv/PivSession.Crypto.cs | 3 +- .../Yubico/YubiKey/Piv/PivSession.KeyPairs.cs | 7 ++-- .../Yubico/YubiKey/Piv/ImportTests.cs | 18 ++++++++++ .../YubiKey/Cryptography/ECPrivateKeyTests.cs | 2 ++ .../YubiKey/Cryptography/ECPublicKeyTests.cs | 2 ++ .../Cryptography/RSAPrivateKeyTests.cs | 2 ++ .../YubiKey/Cryptography/RsaPublicKeyTests.cs | 2 ++ .../Piv/Commands/GenPairResponseTests.cs | 2 ++ .../YubiKey/Piv/PivEncoderDecoderTests.cs | 2 ++ .../Yubico/YubiKey/Piv/PivPrivateKeyTests.cs | 17 ++++++++- .../Yubico/YubiKey/Piv/PivPublicKeyTests.cs | 18 +++++++++- .../YubiKey/TestUtilities/KeyConverter.cs | 3 ++ .../YubiKey/TestUtilities/PivKeyExtensions.cs | 2 ++ .../YubiKey/TestUtilities/SampleKeyPairs.cs | 1 + .../TestUtilities/TestKeyExtensions.cs | 2 ++ 27 files changed, 151 insertions(+), 38 deletions(-) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/EcdsaVerify.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/EcdsaVerify.cs index 6c3b696d2..c0f61a99b 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/EcdsaVerify.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/EcdsaVerify.cs @@ -158,6 +158,7 @@ public EcdsaVerify(ECDsa ecdsa) /// /// The key is not for a supported algorithm or curve, or is malformed. /// + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public EcdsaVerify(PivPublicKey pivPublicKey) { if (pivPublicKey is null) @@ -171,6 +172,22 @@ public EcdsaVerify(PivPublicKey pivPublicKey) ECDsa = ConvertPublicKey(publicPointSpan.ToArray()); } + + // TODO Test + public EcdsaVerify(ECPublicKey publicKey) + { + if (publicKey is null) + { + throw new ArgumentNullException(nameof(publicKey)); + } + + if (!publicKey.KeyType.IsECDsa() || !publicKey.KeyType.IsCurve25519()) + { + throw new ArgumentException("Invalid key type", nameof(publicKey)); + } + + ECDsa = ConvertPublicKey(publicKey.PublicPoint); + } /// /// Create an instance of the class using the @@ -331,7 +348,8 @@ public bool VerifyDigestedData( private static ECDsa ConvertPublicKey(ReadOnlyMemory encodedEccPoint) { - int minEncodedPointLength = (KeyDefinitions.P256.LengthInBytes * 2) + 1; // This is the minimum length for an encoded point on P-256 (0x04 || x || y) + // This is the minimum length for an encoded point on P-256 (0x04 || x || y) + int minEncodedPointLength = (KeyDefinitions.P256.LengthInBytes * 2) + 1; if (encodedEccPoint.Length < minEncodedPointLength || encodedEccPoint.Span[0] != EncodedPointTag) { throw new ArgumentException(ExceptionMessages.UnsupportedAlgorithm); diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/GenerateKeyPairResponse.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/GenerateKeyPairResponse.cs index 936b64c68..fcd6965cd 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/GenerateKeyPairResponse.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/GenerateKeyPairResponse.cs @@ -85,7 +85,9 @@ namespace Yubico.YubiKey.Piv.Commands /// PivPublicKey pubKey = generateKeyPairResponse.GetData(); /// /// + #pragma warning disable CS0618 // Type or member is obsolete public class GenerateKeyPairResponse : PivResponse, IYubiKeyResponseWithData + #pragma warning restore CS0618 // Type or member is obsolete { private byte _slotNumber; private PivAlgorithm _algorithm; @@ -190,10 +192,14 @@ public GenerateKeyPairResponse( /// /// Thrown when is not . /// + #pragma warning disable CS0618 // Type or member is obsolete public PivPublicKey GetData() => + #pragma warning restore CS0618 // Type or member is obsolete Status switch { + #pragma warning disable CS0618 // Type or member is obsolete ResponseStatus.Success => PivPublicKey.Create(ResponseApdu.Data, Algorithm), + #pragma warning restore CS0618 // Type or member is obsolete _ => throw new InvalidOperationException(StatusMessage), }; } diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/ImportAsymmetricKeyCommand.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/ImportAsymmetricKeyCommand.cs index 64d2602a0..0e8688ec1 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/ImportAsymmetricKeyCommand.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/ImportAsymmetricKeyCommand.cs @@ -215,7 +215,8 @@ private ImportAsymmetricKeyCommand() /// /// The privateKey argument does not contain a key. /// - [Obsolete("Usage of PivEccPublic/PivEccPrivateKey is deprecated. Use IPublicKey, IPrivateKey instead", false)] + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey, PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] + public ImportAsymmetricKeyCommand( PivPrivateKey privateKey, byte slotNumber, @@ -274,7 +275,8 @@ public ImportAsymmetricKeyCommand( /// /// The privateKey argument does not contain a key. /// - [Obsolete("Usage of PivEccPublic/PivEccPrivateKey is deprecated. Use IPublicKey, IPrivateKey instead", false)] + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey, PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] + public ImportAsymmetricKeyCommand(PivPrivateKey privateKey) { if (privateKey is null) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/PivKeyEncoder.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/PivKeyEncoder.cs index 9ba76fd67..2a8f0c147 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/PivKeyEncoder.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/PivKeyEncoder.cs @@ -20,11 +20,11 @@ namespace Yubico.YubiKey.Piv.Converters; /// -/// This class converts from a Piv Encoded Key to either instances of the common IPublicKey and IPrivateKey -/// or concrete the concrete types that inherit these interfaces. +/// This class converts from IPublicKey, IPrivateKey and implementations of either to a PIV-encoded key. /// -internal class PivKeyEncoder +internal static class PivKeyEncoder { + [Obsolete("KeyExtensions instead", false)] public static Memory EncodePublicKey(IPublicKey publicKey) { return publicKey switch @@ -35,7 +35,7 @@ public static Memory EncodePublicKey(IPublicKey publicKey) _ => throw new ArgumentException("Unsupported public key type."), }; } - + public static Memory EncodeRSAPublicKey(RSAPublicKey publicKey) { var rsaParameters = publicKey.Parameters; @@ -70,17 +70,17 @@ public static Memory EncodeECPublicKey(ECPublicKey publicKey) return tlvWriter.Encode(); } - - public static Memory EncodePrivateKey(IPrivateKey publicKey) - { - return publicKey switch + + [Obsolete("KeyExtensions instead", false)] + public static Memory EncodePrivateKey(IPrivateKey publicKey) => + publicKey switch { Curve25519PrivateKey curve25519PrivateKey => EncodeCurve25519PrivateKey(curve25519PrivateKey), ECPrivateKey ecPrivateKey => EncodeECPrivateKey(ecPrivateKey), RSAPrivateKey rsaPrivateKey => EncodeRSAPrivateKey(rsaPrivateKey), + _ => throw new ArgumentException("Unsupported public key type."), }; - } public static Memory EncodeECPrivateKey(ECPrivateKey privateKey) { diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPrivateKey.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPrivateKey.cs index 16fde1d9f..3e3dde5e1 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPrivateKey.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPrivateKey.cs @@ -28,7 +28,7 @@ namespace Yubico.YubiKey.Piv /// of a point on the curve. So for ECC P-256, each coordinate is 32 bytes /// (256 bits), so the private value will be 32 bytes. /// - [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey, PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public sealed class PivEccPrivateKey : PivPrivateKey { private const int EccP256PrivateKeySize = 32; @@ -132,7 +132,6 @@ private static PivAlgorithm GetBySize(ReadOnlySpan privateValue) /// /// The encoding of the private key is not supported. /// - [Obsolete("Obsolete")] public static PivEccPrivateKey CreateEccPrivateKey( ReadOnlyMemory encodedPrivateKey, PivAlgorithm? pivAlgorithm) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPublicKey.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPublicKey.cs index 422b637fe..7b858c75c 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPublicKey.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPublicKey.cs @@ -46,6 +46,7 @@ namespace Yubico.YubiKey.Piv /// examine the encoding. /// /// + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public sealed class PivEccPublicKey : PivPublicKey { private const int EccP256PublicKeySize = 65; @@ -83,7 +84,7 @@ private PivEccPublicKey() /// /// The format of the public point is not supported. /// - [Obsolete("Usage of PivEccPublic/PivEccPrivateKey is deprecated. Use IPublicKey, IPrivateKey instead", false)] + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey, PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public PivEccPublicKey(ReadOnlySpan publicPoint, PivAlgorithm? algorithm = null) { if (!LoadEccPublicKey(publicPoint, algorithm)) @@ -213,9 +214,6 @@ private bool LoadEccPublicKey(ReadOnlySpan publicPoint, PivAlgorithm? algo } PivEncodedKey = tlvWriter.Encode(); - - // The Metadate encoded key is the contents of the nested. So set - // that to be a slice of the EncodedKey. YubiKeyEncodedKey = PivEncodedKey[SliceIndex..]; _publicPoint = new Memory(publicPoint.ToArray()); diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivMetadata.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivMetadata.cs index 7188fc838..ed77dd8e8 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivMetadata.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivMetadata.cs @@ -314,7 +314,8 @@ private void ParseResponseData(ReadOnlyMemory responseData) /// /// The public key associated with the private key in the given slot. /// - [Obsolete("Usage of PivEccPublic/PivEccPrivateKey is deprecated. Use IPublicKey, IPrivateKey instead", false)] + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey, PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] + public PivPublicKey PublicKey { get; private set; } /// diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivPrivateKey.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivPrivateKey.cs index 88260bed8..5a10443c6 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivPrivateKey.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivPrivateKey.cs @@ -62,6 +62,7 @@ namespace Yubico.YubiKey.Piv /// /// /// + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public class PivPrivateKey : PrivateKey { protected Memory EncodedKey { get; set; } @@ -107,6 +108,8 @@ public PivPrivateKey() /// /// The key data supplied is not a supported encoding. /// + + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public static PivPrivateKey Create(ReadOnlyMemory encodedPrivateKey, PivAlgorithm? pivAlgorithm = null) { byte tag = 0; diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivPublicKey.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivPublicKey.cs index ecbeb1609..b352b52f6 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivPublicKey.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivPublicKey.cs @@ -14,6 +14,8 @@ using System; using System.Globalization; +using Yubico.YubiKey.Cryptography; +using Yubico.YubiKey.Piv.Converters; namespace Yubico.YubiKey.Piv { @@ -68,7 +70,8 @@ namespace Yubico.YubiKey.Piv /// without the nested 7F49 tag. /// /// - public class PivPublicKey + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey, PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] + public class PivPublicKey : PublicKey { /// /// The algorithm of the key in this object. @@ -78,6 +81,8 @@ public class PivPublicKey /// public PivAlgorithm Algorithm { get; protected set; } + public override KeyType KeyType => Algorithm.GetKeyType(); + protected Memory PivEncodedKey { get; set; } protected Memory YubiKeyEncodedKey { get; set; } @@ -124,21 +129,28 @@ public PivPublicKey() /// public static PivPublicKey Create(ReadOnlyMemory encodedPublicKey, PivAlgorithm? algorithm = null) { - // Try to decode as an RSA public key. If that works, we're done. If - // not, try ECC. If that doesn't work, exception. bool isCreated = PivRsaPublicKey.TryCreate(out var publicKeyObject, encodedPublicKey); - if (!isCreated) + if (isCreated) + { + return publicKeyObject; + } + + isCreated = PivEccPublicKey.TryCreate(out publicKeyObject, encodedPublicKey, algorithm); + if (isCreated) { - if (PivEccPublicKey.TryCreate(out publicKeyObject, encodedPublicKey, algorithm) == false) - { - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - ExceptionMessages.InvalidPublicKeyData)); - } + return publicKeyObject; } - return publicKeyObject; + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + ExceptionMessages.InvalidPublicKeyData)); + } + + public override byte[] ExportSubjectPublicKeyInfo() + { + var publicKey = PivKeyDecoder.CreatePublicKey(PivEncodedPublicKey, Algorithm.GetKeyType()); + return publicKey.ExportSubjectPublicKeyInfo(); } } } diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivRsaPrivateKey.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivRsaPrivateKey.cs index f59fdc964..705f87651 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivRsaPrivateKey.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivRsaPrivateKey.cs @@ -49,6 +49,7 @@ namespace Yubico.YubiKey.Piv /// then then examine the encoding. /// /// + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public sealed class PivRsaPrivateKey : PivPrivateKey { private Memory _primeP; diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivRsaPublicKey.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivRsaPublicKey.cs index 2bcf72459..c98d25803 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivRsaPublicKey.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivRsaPublicKey.cs @@ -59,6 +59,7 @@ namespace Yubico.YubiKey.Piv /// and public exponent, then examine the encoding. /// /// + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public sealed class PivRsaPublicKey : PivPublicKey { private readonly byte[] _exponentF4 = { 0x01, 0x00, 0x01 }; diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Attestation.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Attestation.cs index 1463b791e..d574f88b7 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Attestation.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Attestation.cs @@ -202,7 +202,8 @@ public X509Certificate2 GetAttestationCertificate() } - [Obsolete("Usage of PivEccPublic/PivEccPrivateKey is deprecated. Use IPublicKey, IPrivateKey instead", false)] + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey, PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] + public void ReplaceAttestationKeyAndCertificate(PivPrivateKey privateKey, X509Certificate2 certificate) { byte[] certDer = CheckVersionKeyAndCertRequirements(privateKey.Algorithm.GetKeyType(), certificate); diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Crypto.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Crypto.cs index 1623cac06..fd0dd183d 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Crypto.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.Crypto.cs @@ -267,7 +267,8 @@ public byte[] Decrypt(byte slotNumber, ReadOnlyMemory dataToDecrypt) ExceptionMessages.IncorrectCiphertextLength)); } - [Obsolete("Usage of PivEccPublic/PivEccPrivateKey is deprecated. Use IPublicKey, IPrivateKey instead", false)] + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey, PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] + public byte[] KeyAgree(byte slotNumber, PivPublicKey correspondentPublicKey) { if (correspondentPublicKey is null) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.KeyPairs.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.KeyPairs.cs index 1bc7ea485..3e35ee43b 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.KeyPairs.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.KeyPairs.cs @@ -43,7 +43,7 @@ public sealed partial class PivSession : IDisposable /// private const byte CompressedCert = 1; - [Obsolete("Usage of PivEccPublic/PivEccPrivateKey is deprecated. Use IPublicKey, IPrivateKey instead", false)] + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey, PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public PivPublicKey GenerateKeyPair( byte slotNumber, PivAlgorithm algorithm, @@ -266,7 +266,7 @@ public IPublicKey GenerateKeyPair( /// /// If the specified is not supported by the provided . /// - [Obsolete("Usage of PivEccPublic/PivEccPrivateKey is deprecated. Use IPublicKey, IPrivateKey instead", false)] + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey, PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public void ImportPrivateKey( byte slotNumber, PivPrivateKey privateKey, @@ -400,8 +400,9 @@ public void ImportPrivateKey( RefreshManagementKeyAuthentication(); + var pivEncodedKey = privateKey.EncodeAsPiv(); var command = new ImportAsymmetricKeyCommand( - privateKey.EncodeAsPiv(), + pivEncodedKey, privateKey.KeyType, slotNumber, pinPolicy, diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/ImportTests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/ImportTests.cs index 8ca667810..abfdd10f7 100644 --- a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/ImportTests.cs +++ b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/ImportTests.cs @@ -38,7 +38,9 @@ public void ImportPrivateKey_with_PrivateKey_Succeeds_and_HasExpectedValues( { // Arrange var (testPublicKey, testPrivateKey) = TestKeys.GetKeyPair(keyType); +#pragma warning disable CS0618 // Type or member is obsolete var testPivPublicKey = testPublicKey.AsPivPublicKey(); +#pragma warning restore CS0618 // Type or member is obsolete var keyParameters = AsnPrivateKeyDecoder.CreatePrivateKey(testPrivateKey.EncodedKey); const PivPinPolicy expectedPinPolicy = PivPinPolicy.Once; @@ -104,6 +106,22 @@ public void Import_KeyAndMatchingCert( pivSession.ImportPrivateKey(0x90, privateKey); pivSession.ImportCertificate(0x90, testCert.AsX509Certificate2()); } + + + [SkippableTheory(typeof(NotSupportedException), typeof(DeviceNotFoundException))] + [InlineData(KeyType.ECP256, StandardTestDevice.Fw5)] + [InlineData(KeyType.Ed25519, StandardTestDevice.Fw5)] + [Obsolete()] + public void Import_KeyAndMatchingCer2t( + KeyType keyType, + StandardTestDevice testDeviceType) + { + using var pivSession = GetSession(testDeviceType); + var testPrivateKey = TestKeys.GetTestPrivateKey(keyType); + var piv = testPrivateKey.AsPivPrivateKey(); + + pivSession.ImportPrivateKey(0x90, piv); + } [SkippableTheory(typeof(NotSupportedException), typeof(DeviceNotFoundException))] [InlineData(KeyType.RSA1024, false)] diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ECPrivateKeyTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ECPrivateKeyTests.cs index a997ec304..6b10713e1 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ECPrivateKeyTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ECPrivateKeyTests.cs @@ -29,7 +29,9 @@ public void CreateFromPivEncoding_WithValidParameters_CreatesInstance() { // Arrange var testKey = TestKeys.GetTestPrivateKey(KeyType.ECP256); +#pragma warning disable CS0618 // Type or member is obsolete var pivPrivateKey = testKey.AsPivPrivateKey(); +#pragma warning restore CS0618 // Type or member is obsolete var pivPrivateKeyEncoded = pivPrivateKey.EncodedPrivateKey; // Act diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ECPublicKeyTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ECPublicKeyTests.cs index 08ba4f9ad..8426e6a1d 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ECPublicKeyTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ECPublicKeyTests.cs @@ -16,7 +16,9 @@ public void CreateFromPivEncoding_WithValidParameters_CreatesInstance(KeyType ke { // Arrange var testKey = TestKeys.GetTestPublicKey(keyType); +#pragma warning disable CS0618 // Type or member is obsolete var pivPublicKey = testKey.AsPivPublicKey(); +#pragma warning restore CS0618 // Type or member is obsolete var pivPublicKeyEncoded = pivPublicKey.PivEncodedPublicKey; // Act diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/RSAPrivateKeyTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/RSAPrivateKeyTests.cs index 73a46ef3b..4ff7b9314 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/RSAPrivateKeyTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/RSAPrivateKeyTests.cs @@ -12,6 +12,7 @@ // 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; @@ -44,6 +45,7 @@ public void Dispose_DisposesResources() } [Fact] + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public void CreateFromPivEncoding_WithValidParameters_CreatesInstance() { // Arrange diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/RsaPublicKeyTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/RsaPublicKeyTests.cs index 2f099208b..03c028c04 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/RsaPublicKeyTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/RsaPublicKeyTests.cs @@ -15,7 +15,9 @@ public void CreateFromPivEncoding_WithValidParameters_CreatesInstance() { // Arrange var testKey = TestKeys.GetTestPublicKey(KeyType.RSA2048); +#pragma warning disable CS0618 // Type or member is obsolete var pivPublicKey = testKey.AsPivPublicKey(); +#pragma warning restore CS0618 // Type or member is obsolete var pivPublicKeyEncoded = pivPublicKey.PivEncodedPublicKey; // Act diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/Commands/GenPairResponseTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/Commands/GenPairResponseTests.cs index fdfe6de9d..b91b9feca 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/Commands/GenPairResponseTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/Commands/GenPairResponseTests.cs @@ -141,7 +141,9 @@ public void GetData_AuthenticationRequiredResponse_ThrowsException(KeyType keyTy byte[] keyData = GetCorrectEncodingForAlgorithm(keyType); var response = new GenerateKeyPairResponse(responseApdu, 0x8F, keyType.GetPivAlgorithm()); +#pragma warning disable CS0618 // Type or member is obsolete PivPublicKey getData = response.GetData(); +#pragma warning restore CS0618 // Type or member is obsolete var keyDataSpan = new ReadOnlySpan(keyData); bool compareResult = keyDataSpan.SequenceEqual(getData.PivEncodedPublicKey.Span); diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivEncoderDecoderTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivEncoderDecoderTests.cs index 91c9ac486..67fa413ec 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivEncoderDecoderTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivEncoderDecoderTests.cs @@ -92,7 +92,9 @@ public void PivAndPkcsEncodedKeys_AreEqual( var testKey = TestKeys.GetTestPublicKey(keyType); // Get test key as PivPublicKey +#pragma warning disable CS0618 // Type or member is obsolete var testPivPublicKey = testKey.AsPivPublicKey(); +#pragma warning restore CS0618 // Type or member is obsolete Assert.NotNull(testPivPublicKey); // Convert from PivEncoding to PublicKey using Key Converter. diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivPrivateKeyTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivPrivateKeyTests.cs index a374528d0..bde8b6ab7 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivPrivateKeyTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivPrivateKeyTests.cs @@ -13,7 +13,6 @@ // limitations under the License. using System; -using System.Linq; using System.Security.Cryptography; using Xunit; using Yubico.YubiKey.Cryptography; @@ -21,8 +20,19 @@ namespace Yubico.YubiKey.Piv { + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public class PivPrivateKeyTests { + [Theory] + [InlineData(KeyType.ECP256)] + [InlineData(KeyType.Ed25519)] + public void Constructor_with_Algorithm_SetsAlgorithm(KeyType keyType) + { + var pk = new PivEccPrivateKey(new byte[32], keyType.GetPivAlgorithm()); + Assert.Equal(keyType.GetPivAlgorithm(), pk.Algorithm); + Assert.Equal(keyType, pk.KeyType); + } + [Theory] [InlineData(KeyType.RSA1024)] [InlineData(KeyType.RSA2048)] @@ -241,6 +251,7 @@ public void RsaConstructor_Components_BuildsEncoding( [Theory] [InlineData(KeyType.ECP256)] [InlineData(KeyType.ECP384)] + [Obsolete("Obsolete")] public void EccConstructor_Components_BuildsEncoding( KeyType keyType) { @@ -305,7 +316,9 @@ public void RsaConstructor_BadPrimeQ_ThrowsExcpetion( [Fact] public void EccConstructor_NullData_ThrowsExcpetion() { +#pragma warning disable CS0618 // Type or member is obsolete _ = Assert.Throws(() => new PivEccPrivateKey(null)); +#pragma warning restore CS0618 // Type or member is obsolete } [Theory] @@ -382,7 +395,9 @@ public void GetPivPrivateKey_FromPem( offset = keySize - eccParams.D!.Length; Array.Copy(eccParams.D, 0, privateValue, offset, eccParams.D.Length); +#pragma warning disable CS0618 // Type or member is obsolete var eccPriKey = new PivEccPrivateKey(privateValue); +#pragma warning restore CS0618 // Type or member is obsolete privateKey = (PivPrivateKey)eccPriKey; } diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivPublicKeyTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivPublicKeyTests.cs index 82574fa84..2ad0168e8 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivPublicKeyTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivPublicKeyTests.cs @@ -20,8 +20,24 @@ namespace Yubico.YubiKey.Piv { - public class PivPublicKeyTests + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] + public class PivPublicKeyTests // TODO add test to verify interface implementations { + + [Fact] + public void ExportSubjectPublicKeyInfo_ReturnsSubjectPublicKeyInfo() + { + var testKey = TestKeys.GetTestPublicKey(KeyType.ECP256); + var tlvPublicKey = testKey.AsPivPublicKey(); + var subjectPublicKeyInfo = tlvPublicKey.ExportSubjectPublicKeyInfo(); + var ecPublicKey = ECPublicKey.CreateFromPkcs8(subjectPublicKeyInfo); + + Assert.Equal(KeyType.ECP256, tlvPublicKey.KeyType); + Assert.NotEmpty(subjectPublicKeyInfo); + Assert.Equal(testKey.EncodedKey, subjectPublicKeyInfo); + Assert.Equal(subjectPublicKeyInfo, ecPublicKey.ExportSubjectPublicKeyInfo()); + } + [Theory] [InlineData(KeyType.RSA1024)] [InlineData(KeyType.RSA2048)] diff --git a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/KeyConverter.cs b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/KeyConverter.cs index 055703ceb..97a3545d6 100644 --- a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/KeyConverter.cs +++ b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/KeyConverter.cs @@ -63,6 +63,7 @@ namespace Yubico.YubiKey.TestUtilities // Build an object from a PivPublicKey object and get a PEM string or RSA or // ECDsa object. // It is also possible to get a public key from a private key. + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public class KeyConverter { private const string RequestedKeyMessage = "Requested key was unavailable."; @@ -768,7 +769,9 @@ private void BuildPivPrivateKey( // var eccOid = eccParams.Curve.Oid.Value!; // var keyDefinition = KeyDefinitions.GetByOid(eccOid); // var eccPriKey = new PivEccPrivateKey(privateValue, keyDefinition.KeyType.GetPivAlgorithm()); +#pragma warning disable CS0618 // Type or member is obsolete var eccPriKey = new PivEccPrivateKey(privateValue); +#pragma warning restore CS0618 // Type or member is obsolete _pivPrivateKey = eccPriKey; } diff --git a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/PivKeyExtensions.cs b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/PivKeyExtensions.cs index a8d355223..544686627 100644 --- a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/PivKeyExtensions.cs +++ b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/PivKeyExtensions.cs @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using Yubico.YubiKey.Cryptography; using Yubico.YubiKey.Piv; using Yubico.YubiKey.Piv.Converters; namespace Yubico.YubiKey.TestUtilities; +[Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public static class PivKeyExtensions { public static RSAPrivateKey ConvertToGeneric( diff --git a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/SampleKeyPairs.cs b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/SampleKeyPairs.cs index e3909569c..e49cee211 100644 --- a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/SampleKeyPairs.cs +++ b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/SampleKeyPairs.cs @@ -15,6 +15,7 @@ using System.Security.Cryptography.X509Certificates; using Yubico.YubiKey.Cryptography; using Yubico.YubiKey.Piv; +#pragma warning disable CS0618 // Type or member is obsolete namespace Yubico.YubiKey.TestUtilities { diff --git a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/TestKeyExtensions.cs b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/TestKeyExtensions.cs index f607bfbf5..47ff33112 100644 --- a/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/TestKeyExtensions.cs +++ b/Yubico.YubiKey/tests/utilities/Yubico/YubiKey/TestUtilities/TestKeyExtensions.cs @@ -11,6 +11,7 @@ public static class TestKeyExtensions /// Converts the key to a PIV private key format. /// /// PivPrivateKey instance + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public static PivPrivateKey AsPivPrivateKey( this TestKey key) { @@ -38,6 +39,7 @@ public static PivPrivateKey AsPivPrivateKey( /// Converts the key to a PIV public key format. /// /// PivPublicKey instance + [Obsolete("Usage of PivEccPublic/PivEccPrivateKey PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] public static PivPublicKey AsPivPublicKey( this TestKey key) { From 933ab6dc3fca80dc0d2e47cf990cfbbe3ab9cd97 Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Wed, 7 May 2025 12:56:54 +0200 Subject: [PATCH 4/9] refactor: change zeroMemoryHandle to work with Memory --- .../Cryptography/AsnPrivateKeyEncoder.cs | 4 +- .../Cryptography/ZeroingMemoryHandle.cs | 58 +++---------------- .../Yubico/YubiKey/Piv/PivSession.KeyPairs.cs | 4 +- .../Cryptography/ZeroingMemoryHandleTests.cs | 8 +-- 4 files changed, 16 insertions(+), 58 deletions(-) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/AsnPrivateKeyEncoder.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/AsnPrivateKeyEncoder.cs index 05c44561e..607c16e4c 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/AsnPrivateKeyEncoder.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/AsnPrivateKeyEncoder.cs @@ -190,7 +190,7 @@ private static byte[] EncodeECKey( writer.PopSequence(); // PrivateKey as OCTET STRING - writer.WriteOctetString(ecPrivateKeyHandle.Data); + writer.WriteOctetString(ecPrivateKeyHandle.Data.Span); writer.PopSequence(); return writer.Encode(); @@ -237,7 +237,7 @@ private static byte[] EncodeCurve25519Key(ReadOnlySpan privateKey, string privateKeyWriter.WriteOctetString(privateKey); using var privateKeyBytesHandle = new ZeroingMemoryHandle(privateKeyWriter.Encode()); - writer.WriteOctetString(privateKeyBytesHandle.Data); + writer.WriteOctetString(privateKeyBytesHandle.Data.Span); // End PrivateKeyInfo SEQUENCE writer.PopSequence(); diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/ZeroingMemoryHandle.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/ZeroingMemoryHandle.cs index c4c61167d..4072b24fb 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/ZeroingMemoryHandle.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/ZeroingMemoryHandle.cs @@ -13,68 +13,26 @@ // limitations under the License. using System; -using System.Collections; -using System.Collections.Generic; using System.Security.Cryptography; namespace Yubico.YubiKey.Cryptography; -#pragma warning disable CA1710 -internal class ZeroingMemoryHandle : IDisposable, IReadOnlyCollection, IEnumerable -#pragma warning restore CA1710 +internal class ZeroingMemoryHandle : IDisposable { - private readonly byte[] _data; + private readonly Memory _data; private bool _disposed; - public int Length => _disposed ? 0 : _data.Length; - public int Count => Length; // For IReadOnlyCollection - - public byte this[int index] => _disposed - ? throw new ObjectDisposedException(nameof(ZeroingMemoryHandle)) - : _data[index]; + public int Count => Length; - public ZeroingMemoryHandle(byte[] data) + public ZeroingMemoryHandle(Memory data) { - _data = data ?? throw new ArgumentNullException(nameof(data)); + _data = data; } - public ReadOnlySpan AsSpan() => _disposed - ? ReadOnlySpan.Empty - : _data.AsSpan(); - - public byte[] Data => _disposed + public Memory Data => _disposed ? throw new ObjectDisposedException(nameof(ZeroingMemoryHandle)) : _data; - - public void CopyTo(byte[] destination, int destinationIndex = 0) - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(ZeroingMemoryHandle)); - } - - Buffer.BlockCopy(_data, 0, destination, destinationIndex, _data.Length); - } - - public ReadOnlySpan Slice(int start, int length) => _disposed - ? ReadOnlySpan.Empty - : _data.AsSpan(start, length); - - public IEnumerator GetEnumerator() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(ZeroingMemoryHandle)); - } - - for (int i = 0; i < _data.Length; i++) - { - yield return _data[i]; - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - + public void Dispose() { if (_disposed) @@ -82,7 +40,7 @@ public void Dispose() return; } - CryptographicOperations.ZeroMemory(_data); + CryptographicOperations.ZeroMemory(_data.Span); _disposed = true; GC.SuppressFinalize(this); } diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.KeyPairs.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.KeyPairs.cs index 3e35ee43b..489e6ee9d 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.KeyPairs.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.KeyPairs.cs @@ -400,9 +400,9 @@ public void ImportPrivateKey( RefreshManagementKeyAuthentication(); - var pivEncodedKey = privateKey.EncodeAsPiv(); + using var pivEncodedKeyHandle = new ZeroingMemoryHandle(privateKey.EncodeAsPiv()); var command = new ImportAsymmetricKeyCommand( - pivEncodedKey, + pivEncodedKeyHandle.Data, privateKey.KeyType, slotNumber, pinPolicy, diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ZeroingMemoryHandleTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ZeroingMemoryHandleTests.cs index 00b0638e5..7eda370b1 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ZeroingMemoryHandleTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ZeroingMemoryHandleTests.cs @@ -65,7 +65,7 @@ public void NestedUsage_ZeroesAllArraysWhenDisposed() // Simulate combining key and IV for an operation for (int i = 0; i < 5; i++) { - resultData[i] = (byte)(keyHandle.Data[i] ^ ivHandle.Data[i]); + resultData[i] = (byte)(keyHandle.Data.Span[i] ^ ivHandle.Data.Span[i]); } } @@ -148,14 +148,14 @@ public void MultiLayerComponent_MaintainsSecurityThroughLayers() Assert.All(sensitiveData, b => Assert.Equal(0, b)); } -// Mock processors for testing + // Mock processors for testing private static class FirstLayerProcessor { public static byte[] Process(ZeroingMemoryHandle handle) { // Simulate processing var result = new byte[handle.Data.Length]; - Buffer.BlockCopy(handle.Data, 0, result, 0, handle.Data.Length); + handle.Data.CopyTo(result); return result; } } @@ -167,7 +167,7 @@ public static byte[] Process(ZeroingMemoryHandle originalHandle, byte[] intermed // Simulate more processing for (int i = 0; i < originalHandle.Data.Length; i++) { - intermediateResult[i] ^= originalHandle.Data[i]; + intermediateResult[i] ^= originalHandle.Data.Span[i]; } return intermediateResult; } From 09dd9ae75059edb1cec4fff11bcc613c8d4024bc Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Wed, 7 May 2025 12:57:12 +0200 Subject: [PATCH 5/9] docs: add remark for sensitive data --- .../src/Yubico/YubiKey/Piv/Converters/KeyExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/KeyExtensions.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/KeyExtensions.cs index b119fcff3..9194013eb 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/KeyExtensions.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/KeyExtensions.cs @@ -53,6 +53,7 @@ public static Memory EncodeAsPiv(this IPublicKey parameters) => /// The private key parameters to encode. /// A BER encoded byte array containing the encoded private key. /// Thrown when the key type is not supported or when RSA key components have invalid lengths. + /// This method returns a newly allocated array containing sensitive information. public static Memory EncodeAsPiv(this IPrivateKey parameters) => parameters switch { From 22868ea3545ddba2b4e7cf5853021f7c06f8839c Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Wed, 7 May 2025 12:58:29 +0200 Subject: [PATCH 6/9] test: add legacy test --- .../tests/integration/Yubico/YubiKey/Piv/ImportTests.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/ImportTests.cs b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/ImportTests.cs index abfdd10f7..389ba093e 100644 --- a/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/ImportTests.cs +++ b/Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/ImportTests.cs @@ -112,14 +112,13 @@ public void Import_KeyAndMatchingCert( [InlineData(KeyType.ECP256, StandardTestDevice.Fw5)] [InlineData(KeyType.Ed25519, StandardTestDevice.Fw5)] [Obsolete()] - public void Import_KeyAndMatchingCer2t( + public void Import_with_PivEccPrivateKey_Succeeds( KeyType keyType, StandardTestDevice testDeviceType) { using var pivSession = GetSession(testDeviceType); var testPrivateKey = TestKeys.GetTestPrivateKey(keyType); - var piv = testPrivateKey.AsPivPrivateKey(); - + var piv = new PivEccPrivateKey(testPrivateKey.GetPrivateKey(), keyType.GetPivAlgorithm()); pivSession.ImportPrivateKey(0x90, piv); } From 7f365ffd26ec4fe37e35ac728a14506281bb5276 Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Mon, 26 May 2025 12:18:48 +0200 Subject: [PATCH 7/9] Update GenerateKeyPairResponse.cs --- .../Yubico/YubiKey/Piv/Commands/GenerateKeyPairResponse.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/GenerateKeyPairResponse.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/GenerateKeyPairResponse.cs index fcd6965cd..c9690acfe 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/GenerateKeyPairResponse.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/Commands/GenerateKeyPairResponse.cs @@ -194,13 +194,12 @@ public GenerateKeyPairResponse( /// #pragma warning disable CS0618 // Type or member is obsolete public PivPublicKey GetData() => - #pragma warning restore CS0618 // Type or member is obsolete Status switch { - #pragma warning disable CS0618 // Type or member is obsolete ResponseStatus.Success => PivPublicKey.Create(ResponseApdu.Data, Algorithm), - #pragma warning restore CS0618 // Type or member is obsolete _ => throw new InvalidOperationException(StatusMessage), }; + #pragma warning restore CS0618 // Type or member is obsolete + } } From 5b1352dcb474f916cb183f8321d2ea8b8556cbfa Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Mon, 26 May 2025 12:22:08 +0200 Subject: [PATCH 8/9] fix: EcdsaVerify cannot be Ed25519 --- Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/EcdsaVerify.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/EcdsaVerify.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/EcdsaVerify.cs index c0f61a99b..d495a043f 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/EcdsaVerify.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/EcdsaVerify.cs @@ -173,7 +173,6 @@ public EcdsaVerify(PivPublicKey pivPublicKey) ECDsa = ConvertPublicKey(publicPointSpan.ToArray()); } - // TODO Test public EcdsaVerify(ECPublicKey publicKey) { if (publicKey is null) @@ -181,7 +180,7 @@ public EcdsaVerify(ECPublicKey publicKey) throw new ArgumentNullException(nameof(publicKey)); } - if (!publicKey.KeyType.IsECDsa() || !publicKey.KeyType.IsCurve25519()) + if (!publicKey.KeyType.IsECDsa()) { throw new ArgumentException("Invalid key type", nameof(publicKey)); } From a4b897fa9ab0991d3a613bcc04d7c7189e56bfeb Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Mon, 26 May 2025 16:19:25 +0200 Subject: [PATCH 9/9] refactor: use KeyDefinition lookup for PivEccPrivateKey --- .../Yubico/YubiKey/Piv/PivEccPrivateKey.cs | 38 ++++++++++--------- .../Yubico/YubiKey/Piv/PivPrivateKeyTests.cs | 9 ++++- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPrivateKey.cs b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPrivateKey.cs index 3e3dde5e1..0ed8cb76e 100644 --- a/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPrivateKey.cs +++ b/Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivEccPrivateKey.cs @@ -13,10 +13,12 @@ // limitations under the License. using System; -using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Security.Cryptography; using Yubico.Core.Tlv; +using Yubico.YubiKey.Cryptography; namespace Yubico.YubiKey.Piv { @@ -28,12 +30,11 @@ namespace Yubico.YubiKey.Piv /// of a point on the curve. So for ECC P-256, each coordinate is 32 bytes /// (256 bits), so the private value will be 32 bytes. /// - [Obsolete("Usage of PivEccPublic/PivEccPrivateKey, PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", false)] + [Obsolete( + "Usage of PivEccPublic/PivEccPrivateKey, PivRsaPublic/PivRsaPrivateKey is deprecated. Use implementations of ECPublicKey, ECPrivateKey and RSAPublicKey, RSAPrivateKey instead", + false)] public sealed class PivEccPrivateKey : PivPrivateKey { - private const int EccP256PrivateKeySize = 32; - private const int EccP384PrivateKeySize = 48; - private Memory _privateValue; // @@ -70,12 +71,12 @@ private PivEccPrivateKey() /// [Obsolete("Usage of PivEccPublic/PivEccPrivateKey is deprecated. Use ECPublicKey, ECPrivateKey instead", false)] public PivEccPrivateKey( - ReadOnlySpan privateValue, + ReadOnlySpan privateValue, PivAlgorithm? algorithm = null) { if (algorithm.HasValue) { - ValidateSize(privateValue, algorithm); + ValidateSize(privateValue, algorithm.Value); Algorithm = algorithm.Value; } else @@ -89,17 +90,17 @@ public PivEccPrivateKey( PivAlgorithm.EccX25519 => PivConstants.PrivateECX25519Tag, _ => PivConstants.PrivateECDsaTag }; - + var tlvWriter = new TlvWriter(); tlvWriter.WriteValue(tag, privateValue); EncodedKey = tlvWriter.Encode(); _privateValue = new Memory(privateValue.ToArray()); } - private static void ValidateSize(ReadOnlySpan privateValue, [DisallowNull] PivAlgorithm? algorithm) + private static void ValidateSize(ReadOnlySpan privateValue, PivAlgorithm algorithm) { - int expectedSize = algorithm.Value.GetPivKeyDefinition().KeyDefinition.LengthInBytes; - if(privateValue.Length != expectedSize) + int expectedSize = algorithm.GetPivKeyDefinition().KeyDefinition.LengthInBytes; + if (privateValue.Length != expectedSize) { throw new ArgumentException( string.Format( @@ -109,13 +110,16 @@ private static void ValidateSize(ReadOnlySpan privateValue, [DisallowNull] private static PivAlgorithm GetBySize(ReadOnlySpan privateValue) { - return privateValue.Length switch + int privateValueSize = privateValue.Length; + var allowed = new List { KeyDefinitions.P256, KeyDefinitions.P384 }; + if (allowed.SingleOrDefault(kd => kd.LengthInBytes == privateValueSize) is { } keyDefinition) { - EccP256PrivateKeySize => PivAlgorithm.EccP256, - EccP384PrivateKeySize => PivAlgorithm.EccP384, - _ => throw new ArgumentException(string.Format( - CultureInfo.CurrentCulture, ExceptionMessages.InvalidPrivateKeyData)) - }; + return keyDefinition.GetPivKeyDefinition().Algorithm; + } + + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, ExceptionMessages.InvalidPrivateKeyData)); } /// diff --git a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivPrivateKeyTests.cs b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivPrivateKeyTests.cs index bde8b6ab7..1539864bd 100644 --- a/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivPrivateKeyTests.cs +++ b/Yubico.YubiKey/tests/unit/Yubico/YubiKey/Piv/PivPrivateKeyTests.cs @@ -25,10 +25,17 @@ public class PivPrivateKeyTests { [Theory] [InlineData(KeyType.ECP256)] + [InlineData(KeyType.ECP384)] [InlineData(KeyType.Ed25519)] public void Constructor_with_Algorithm_SetsAlgorithm(KeyType keyType) { - var pk = new PivEccPrivateKey(new byte[32], keyType.GetPivAlgorithm()); + var keyBytes = keyType switch + { + KeyType.ECP384 => new byte[48], + _ => new byte[32], + }; + + var pk = new PivEccPrivateKey(keyBytes, keyType.GetPivAlgorithm()); Assert.Equal(keyType.GetPivAlgorithm(), pk.Algorithm); Assert.Equal(keyType, pk.KeyType); }