Skip to content

Commit b518ded

Browse files
committed
refactor: use PivKeyConverter to convert between formats
1 parent 38a74f3 commit b518ded

File tree

9 files changed

+101
-117
lines changed

9 files changed

+101
-117
lines changed

Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/KeyExtensions.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ public static Memory<byte> EncodeAsPiv(this IPublicKey parameters)
3737
{
3838
return parameters switch
3939
{
40-
ECPublicKey p => KeyToPivEncoding.EncodeECPublicKey(p),
41-
RSAPublicKey p => KeyToPivEncoding.EncodeRSAPublicKey(p),
42-
Curve25519PublicKey p => KeyToPivEncoding.EncodeCurve25519PublicKey(p),
40+
ECPublicKey p => PivKeyConverter.EncodeECPublicKey(p),
41+
RSAPublicKey p => PivKeyConverter.EncodeRSAPublicKey(p),
42+
Curve25519PublicKey p => PivKeyConverter.EncodeCurve25519PublicKey(p),
4343
_ => throw new ArgumentException("Unsupported key type.", nameof(parameters))
4444
};
4545
}
@@ -54,9 +54,9 @@ public static Memory<byte> EncodeAsPiv(this IPrivateKey parameters)
5454
{
5555
return parameters switch
5656
{
57-
ECPrivateKey p => KeyToPivEncoding.EncodeECPrivateKey(p),
58-
RSAPrivateKey p => KeyToPivEncoding.EncodeRSAPrivateKey(p),
59-
Curve25519PrivateKey p => KeyToPivEncoding.EncodeCurve25519PrivateKey(p),
57+
ECPrivateKey p => PivKeyConverter.EncodeECPrivateKey(p),
58+
RSAPrivateKey p => PivKeyConverter.EncodeRSAPrivateKey(p),
59+
Curve25519PrivateKey p => PivKeyConverter.EncodeCurve25519PrivateKey(p),
6060
_ => throw new ArgumentException("Unsupported key type.", nameof(parameters))
6161
};
6262
}

Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/PivEncodingReader.cs

Lines changed: 72 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -21,111 +21,99 @@ namespace Yubico.YubiKey.Piv.Converters;
2121

2222
internal static class PivEncodingReader
2323
{
24+
// Works with both Piv Encoded and GetMetaData encoded keys (with or without leading Public Key Tag)
25+
// public static (ReadOnlyMemory<byte> Modulus, ReadOnlyMemory<byte> Exponent) GetPublicRSAValues(
26+
// ReadOnlyMemory<byte> encodedPublicKey)
27+
// {
28+
// var tlvReader = new TlvReader(encodedPublicKey);
29+
// int tag = tlvReader.PeekTag(2);
30+
// var rsaKeyEncoding = tag == PivConstants.PublicKeyTag
31+
// ? tlvReader.ReadValue(PivConstants.PublicKeyTag)
32+
// : encodedPublicKey;
33+
//
34+
// var publicKeyValues = TlvObjects.DecodeDictionary(rsaKeyEncoding.Span);
35+
// bool hasModulus = publicKeyValues.TryGetValue(PivConstants.PublicRSAModulusTag, out var modulus);
36+
// bool hasExponent = publicKeyValues.TryGetValue(PivConstants.PublicRSAExponentTag, out var exponent);
37+
// if (!hasModulus || !hasExponent)
38+
// {
39+
// throw new ArgumentException(
40+
// string.Format(
41+
// CultureInfo.CurrentCulture,
42+
// ExceptionMessages.InvalidPublicKeyData
43+
// ));
44+
// }
45+
// return (modulus, exponent);
46+
// }
47+
2448
public static (ReadOnlyMemory<byte> Modulus, ReadOnlyMemory<byte> Exponent) GetPublicRSAValues(
2549
ReadOnlyMemory<byte> encodedPublicKey)
2650
{
27-
var tlvReader = new TlvReader(encodedPublicKey);
28-
int tag = tlvReader.PeekTag(2);
29-
if (tag == PivConstants.PublicKeyTag)
51+
var tlvObject = TlvObject.Parse(encodedPublicKey.Span);
52+
var rsaKeyEncoding = tlvObject.Tag == PivConstants.PublicKeyTag
53+
? tlvObject.Value
54+
: encodedPublicKey;
55+
56+
var publicKeyValues = TlvObjects.DecodeDictionary(rsaKeyEncoding.Span);
57+
bool hasModulus = publicKeyValues.TryGetValue(PivConstants.PublicRSAModulusTag, out var modulus);
58+
bool hasExponent = publicKeyValues.TryGetValue(PivConstants.PublicRSAExponentTag, out var exponent);
59+
if (!hasModulus || !hasExponent)
3060
{
31-
tlvReader = tlvReader.ReadNestedTlv(tag);
32-
}
33-
34-
var valueArray = new ReadOnlyMemory<byte>[2];
35-
36-
while (tlvReader.HasData)
37-
{
38-
tag = tlvReader.PeekTag();
39-
int valueIndex = tag switch
40-
{
41-
PivConstants.PublicRSAModulusTag => 0,
42-
PivConstants.PublicRSAExponentTag => 1,
43-
_ => throw new ArgumentException(
44-
string.Format(CultureInfo.CurrentCulture, ExceptionMessages.InvalidPublicKeyData))
45-
};
46-
47-
if (!valueArray[valueIndex].IsEmpty)
48-
{
49-
throw new ArgumentException(
50-
string.Format(
51-
CultureInfo.CurrentCulture,
52-
ExceptionMessages.InvalidPublicKeyData)
53-
);
54-
}
55-
56-
valueArray[valueIndex] = tlvReader.ReadValue(tag);
61+
throw new ArgumentException(
62+
string.Format(
63+
CultureInfo.CurrentCulture,
64+
ExceptionMessages.InvalidPublicKeyData
65+
));
5766
}
58-
59-
return (valueArray[0], valueArray[1]);
67+
return (modulus, exponent);
6068
}
6169

6270
// Will read PIV public key data and return the public point
6371
public static ReadOnlyMemory<byte> GetECPublicPointValues(ReadOnlyMemory<byte> encodedPublicKey)
6472
{
65-
var tlvReader = new TlvReader(encodedPublicKey);
66-
67-
int tag = tlvReader.PeekTag(2);
68-
if (tag == PivConstants.PublicKeyTag)
69-
{
70-
tlvReader = tlvReader.ReadNestedTlv(tag);
71-
}
72-
73-
tag = tlvReader.PeekTag();
74-
if (tag != PivConstants.PublicECTag)
73+
var tlvObject = TlvObject.Parse(encodedPublicKey.Span);
74+
var keyEncoding = tlvObject.Tag == PivConstants.PublicKeyTag
75+
? tlvObject.Value
76+
: encodedPublicKey;
77+
78+
var publicKeyValues = TlvObjects.DecodeDictionary(keyEncoding.Span);
79+
bool hasPublicPoint = publicKeyValues.TryGetValue(PivConstants.PublicECTag, out var publicPoint);
80+
if (!hasPublicPoint)
7581
{
7682
throw new ArgumentException(
7783
string.Format(
7884
CultureInfo.CurrentCulture,
79-
ExceptionMessages.InvalidPublicKeyData)
80-
);
85+
ExceptionMessages.InvalidPublicKeyData
86+
));
8187
}
82-
83-
var publicPoint = tlvReader.ReadValue(PivConstants.PublicECTag);
8488
return publicPoint;
8589
}
8690

8791
public static RSAParameters GetRSAParameters(ReadOnlyMemory<byte> pivEncodedKey)
8892
{
89-
const int CrtComponentCount = 5;
90-
91-
var tlvReader = new TlvReader(pivEncodedKey);
92-
var valueArray = new ReadOnlyMemory<byte>[CrtComponentCount];
93-
94-
int index = 0;
95-
for (; index < CrtComponentCount; index++)
93+
var tlvObject = TlvObject.Parse(pivEncodedKey.Span);
94+
var keyEncoding = tlvObject.Tag == PivConstants.PublicKeyTag
95+
? tlvObject.Value
96+
: pivEncodedKey;
97+
98+
var rsaTlvValues = TlvObjects.DecodeDictionary(keyEncoding.Span);
99+
if (!rsaTlvValues.ContainsKey(PivConstants.PrivateRSAPrimePTag) ||
100+
!rsaTlvValues.ContainsKey(PivConstants.PrivateRSAPrimeQTag) ||
101+
!rsaTlvValues.ContainsKey(PivConstants.PrivateRSAExponentPTag) ||
102+
!rsaTlvValues.ContainsKey(PivConstants.PrivateRSAExponentQTag) ||
103+
!rsaTlvValues.ContainsKey(PivConstants.PrivateRSACoefficientTag))
96104
{
97-
valueArray[index] = ReadOnlyMemory<byte>.Empty;
98-
}
99-
100-
index = 0;
101-
while (index < CrtComponentCount)
102-
{
103-
if (tlvReader.HasData == false)
104-
{
105-
break;
106-
}
107-
108-
int tag = tlvReader.PeekTag();
109-
var temp = tlvReader.ReadValue(tag);
110-
if (tag <= 0 || tag > CrtComponentCount)
111-
{
112-
continue;
113-
}
114-
115-
if (valueArray[tag - 1].IsEmpty == false)
116-
{
117-
continue;
118-
}
119-
120-
index++;
121-
valueArray[tag - 1] = temp;
105+
throw new ArgumentException(
106+
string.Format(
107+
CultureInfo.CurrentCulture,
108+
ExceptionMessages.InvalidPrivateKeyData
109+
));
122110
}
123-
124-
var primeP = valueArray[PivConstants.PrivateRSAPrimePTag - 1].Span;
125-
var primeQ = valueArray[PivConstants.PrivateRSAPrimeQTag - 1].Span;
126-
var exponentP = valueArray[PivConstants.PrivateRSAExponentPTag - 1].Span;
127-
var exponentQ = valueArray[PivConstants.PrivateRSAExponentQTag - 1].Span;
128-
var coefficient = valueArray[PivConstants.PrivateRSACoefficientTag - 1].Span;
111+
112+
var primeP = rsaTlvValues[PivConstants.PrivateRSAPrimePTag].Span;
113+
var primeQ = rsaTlvValues[PivConstants.PrivateRSAPrimeQTag].Span;
114+
var exponentP = rsaTlvValues[PivConstants.PrivateRSAExponentPTag].Span;
115+
var exponentQ = rsaTlvValues[PivConstants.PrivateRSAExponentQTag].Span;
116+
var coefficient = rsaTlvValues[PivConstants.PrivateRSACoefficientTag].Span;
129117

130118
return new RSAParameters
131119
{
@@ -134,6 +122,7 @@ public static RSAParameters GetRSAParameters(ReadOnlyMemory<byte> pivEncodedKey)
134122
DP = exponentP.ToArray(),
135123
DQ = exponentQ.ToArray(),
136124
InverseQ = coefficient.ToArray(),
125+
137126
// The YubiKey only works with the CRT components of the private RSA key,
138127
// that's why we set these values as empty.
139128
D = Array.Empty<byte>(),

Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/PivEncodingToKey.cs renamed to Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/PivKeyConverter.Decode.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace Yubico.YubiKey.Piv.Converters;
2525
/// This class converts from a Piv Encoded Key to either instances of the common IPublicKey and IPrivateKey
2626
/// or concrete the concrete types that inherit these interfaces.
2727
/// </summary>
28-
internal static class PivEncodingToKey
28+
internal partial class PivKeyConverter
2929
{
3030
public static IPublicKey CreatePublicKey(ReadOnlyMemory<byte> pivEncodedKey, KeyType keyType) =>
3131
keyType switch
@@ -38,7 +38,7 @@ _ when keyType.IsRSA() => CreateRSAPublicKey(pivEncodedKey),
3838
CultureInfo.CurrentCulture,
3939
ExceptionMessages.InvalidApduResponseData))
4040
};
41-
41+
4242
public static RSAPublicKey CreateRSAPublicKey(ReadOnlyMemory<byte> pivEncodedKey)
4343
{
4444
var (modulus, exponent) = PivEncodingReader.GetPublicRSAValues(pivEncodedKey);
@@ -81,7 +81,7 @@ public static Curve25519PublicKey CreateCurve25519PublicKey(ReadOnlyMemory<byte>
8181
var publicPoint = PivEncodingReader.GetECPublicPointValues(pivEncodedKey);
8282
return Curve25519PublicKey.CreateFromValue(publicPoint, keyType);
8383
}
84-
84+
8585
/// <summary>
8686
/// Creates an instance of <see cref="IPrivateKey"/> from the
8787
/// given PIV-encoded key.
@@ -113,7 +113,7 @@ _ when keyType.IsRSA() => CreateRSAPrivateKey(pivEncodedKey),
113113
};
114114

115115
public static Curve25519PrivateKey CreateCurve25519PrivateKey(
116-
ReadOnlyMemory<byte> pivEncodedKey,
116+
ReadOnlyMemory<byte> pivEncodedKey,
117117
KeyType keyType)
118118
{
119119
if (!TlvObject.TryParse(pivEncodedKey.Span, out var tlv) || !PivConstants.IsValidPrivateECTag(tlv.Tag))
@@ -125,15 +125,15 @@ public static Curve25519PrivateKey CreateCurve25519PrivateKey(
125125
}
126126

127127
using var privateValueHandle = new ZeroingMemoryHandle(tlv.Value.ToArray());
128-
128+
129129
return tlv.Tag switch
130130
{
131131
PivConstants.PrivateECEd25519Tag when keyType == KeyType.Ed25519 => Curve25519PrivateKey.CreateFromValue(
132132
privateValueHandle.Data, KeyType.Ed25519),
133-
133+
134134
PivConstants.PrivateECX25519Tag when keyType == KeyType.X25519 => Curve25519PrivateKey.CreateFromValue(
135135
privateValueHandle.Data, KeyType.X25519),
136-
136+
137137
_ => throw new ArgumentException(
138138
string.Format(
139139
CultureInfo.CurrentCulture,
@@ -150,14 +150,16 @@ public static ECPrivateKey CreateECPrivateKey(ReadOnlyMemory<byte> pivEncodedKey
150150
CultureInfo.CurrentCulture,
151151
ExceptionMessages.InvalidPrivateKeyData));
152152
}
153-
153+
154154
var allowedKeyDefinitions = KeyDefinitions
155155
.GetEcKeyDefinitions()
156156
.Where(kd => kd.AlgorithmOid == Oids.ECDSA);
157+
157158
try
158159
{
159160
var keyDefinition = allowedKeyDefinitions
160161
.Single(kd => kd.LengthInBytes == tlv.Value.Span.Length);
162+
161163
using var privateValueHandle = new ZeroingMemoryHandle(tlv.Value.ToArray());
162164
return ECPrivateKey.CreateFromValue(privateValueHandle.Data, keyDefinition.KeyType);
163165
}

Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/KeyToPivEncoding.cs renamed to Yubico.YubiKey/src/Yubico/YubiKey/Piv/Converters/PivKeyConverter.Encode.cs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,11 @@
2020
namespace Yubico.YubiKey.Piv.Converters;
2121

2222
/// <summary>
23-
/// This class contains methods to convert between the different key parameters and the PIV format.
23+
/// This class converts from a Piv Encoded Key to either instances of the common IPublicKey and IPrivateKey
24+
/// or concrete the concrete types that inherit these interfaces.
2425
/// </summary>
25-
public static class KeyToPivEncoding
26+
internal partial class PivKeyConverter
2627
{
27-
#region PublicKeys
28-
2928
public static Memory<byte> EncodeRSAPublicKey(RSAPublicKey parameters)
3029
{
3130
var rsaParameters = parameters.Parameters;
@@ -61,10 +60,6 @@ public static Memory<byte> EncodeECPublicKey(ECPublicKey parameters)
6160
return tlvWriter.Encode();
6261
}
6362

64-
#endregion
65-
66-
#region PrivateKeys
67-
6863
public static Memory<byte> EncodeECPrivateKey(ECPrivateKey parameters)
6964
{
7065
var tlvWriter = new TlvWriter();
@@ -105,6 +100,4 @@ public static Memory<byte> EncodeCurve25519PrivateKey(Curve25519PrivateKey param
105100
tlvWriter.WriteValue(typeTag, parameters.PrivateKey.Span);
106101
return tlvWriter.Encode();
107102
}
108-
109-
#endregion
110103
}

Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivMetadata.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ private void ParseResponseData(ReadOnlyMemory<byte> responseData)
245245

246246
case PublicTag:
247247
// Public: public key partner to the private key in the slot
248-
PublicKeyParameters = PivEncodingToKey.CreatePublicKey(value, Algorithm.GetKeyType());
248+
PublicKeyParameters = PivKeyConverter.CreatePublicKey(value, Algorithm.GetKeyType());
249249

250250
#pragma warning disable CS0618 // Type or member is obsolete
251251
PublicKey = PivPublicKey.Create(value, Algorithm);

Yubico.YubiKey/src/Yubico/YubiKey/Piv/PivSession.KeyPairs.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public IPublicKey GenerateKeyPair(
160160
var command = new GenerateKeyPairCommand(slotNumber, keyType, pinPolicy, touchPolicy);
161161
var response = Connection.SendCommand(command);
162162

163-
return PivEncodingToKey.CreatePublicKey(response.Data, keyType);
163+
return PivKeyConverter.CreatePublicKey(response.Data, keyType);
164164
}
165165

166166
/// <summary>

Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ECPrivateKeyTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public void CreateFromPivEncoding_WithValidParameters_CreatesInstance()
2020
var pivPrivateKeyEncoded = pivPrivateKey.EncodedPrivateKey;
2121

2222
// Act
23-
var privateKeyParams = PivEncodingToKey.CreateECPrivateKey(pivPrivateKeyEncoded);
23+
var privateKeyParams = PivKeyConverter.CreateECPrivateKey(pivPrivateKeyEncoded);
2424
var parameters = privateKeyParams.Parameters;
2525

2626
// Assert

Yubico.YubiKey/tests/unit/Yubico/YubiKey/Cryptography/ECPublicKeyTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public void CreateFromPivEncoding_WithValidParameters_CreatesInstance(KeyType ke
2020
var pivPublicKeyEncoded = pivPublicKey.PivEncodedPublicKey;
2121

2222
// Act
23-
var publicKeyParams = PivEncodingToKey.CreateECPublicKey(pivPublicKeyEncoded);
23+
var publicKeyParams = PivKeyConverter.CreateECPublicKey(pivPublicKeyEncoded);
2424
var resultParameters = publicKeyParams.Parameters;
2525

2626
// Assert

0 commit comments

Comments
 (0)