Skip to content

Commit 2854939

Browse files
authored
Merge pull request #186
Feature: Supports enumerating Ed25519 and P521 credentials within FIDO
2 parents e266df5 + 9d37340 commit 2854939

File tree

18 files changed

+1300
-439
lines changed

18 files changed

+1300
-439
lines changed

Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/ECPrivateKeyParameters.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ namespace Yubico.YubiKey.Cryptography
2525
/// contains the necessary private key data.
2626
/// It extends the base <see cref="ECKeyParameters"/> class with additional validation for private key components.
2727
/// </remarks>
28-
2928
public class ECPrivateKeyParameters : ECKeyParameters
3029
{
3130
/// <summary>

Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/EcdsaVerify.cs

Lines changed: 80 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,6 @@ namespace Yubico.YubiKey.Cryptography
8181
/// BER encoding.
8282
/// </para>
8383
/// <para>
84-
/// This class can verify signatures for P-256 and P-384 only.
85-
/// </para>
86-
/// <para>
8784
/// Each of the verify methods will return a boolean, indicating whether the
8885
/// signature verifies or not. If a signature does not verify, that is not an
8986
/// error. The methods will throw exceptions if they encounter bad data, such
@@ -103,15 +100,7 @@ namespace Yubico.YubiKey.Cryptography
103100
/// </remarks>
104101
public class EcdsaVerify : IDisposable
105102
{
106-
private const int P256EncodedPointLength = 65;
107-
private const int P384EncodedPointLength = 97;
108-
private const int MinEncodedPointLength = 65;
109-
private const int P256KeySize = 256;
110-
private const int P384KeySize = 384;
111-
private const string OidP256 = "1.2.840.10045.3.1.7";
112-
private const string OidP384 = "1.3.132.0.34";
113-
114-
private const byte EncodedPointTag = 4;
103+
private const byte EncodedPointTag = 0x04;
115104
private const int SequenceTag = 0x30;
116105
private const int IntegerTag = 0x02;
117106

@@ -121,7 +110,7 @@ public class EcdsaVerify : IDisposable
121110
/// The object built that will perform the verification operation.
122111
/// </summary>
123112
/// <remarks>
124-
/// This must be P-256 or P-384, and contain valid coordinates.
113+
/// This must be P-256, P-384 or P-521, and contain valid coordinates.
125114
/// </remarks>
126115
public ECDsa ECDsa { get; private set; }
127116

@@ -136,9 +125,6 @@ private EcdsaVerify()
136125
/// Create an instance of the <see cref="EcdsaVerify"/> class using the
137126
/// ECDsa object that contains the public key.
138127
/// </summary>
139-
/// <remarks>
140-
/// This supports only NIST P-256 and P-384 curves.
141-
/// </remarks>
142128
/// <param name="ecdsa">
143129
/// The public key to use to verify. This constructor will copy a
144130
/// reference to this object.
@@ -163,9 +149,6 @@ public EcdsaVerify(ECDsa ecdsa)
163149
/// Create an instance of the <see cref="EcdsaVerify"/> class using the
164150
/// PIV ECC public key.
165151
/// </summary>
166-
/// <remarks>
167-
/// This supports only NIST P-256 and P-384 curves.
168-
/// </remarks>
169152
/// <param name="pivPublicKey">
170153
/// The public key to use to verify.
171154
/// </param>
@@ -183,7 +166,7 @@ public EcdsaVerify(PivPublicKey pivPublicKey)
183166
}
184167

185168
var publicPointSpan = pivPublicKey is PivEccPublicKey eccKey
186-
? eccKey.PublicPoint
169+
? eccKey.PublicPoint
187170
: ReadOnlySpan<byte>.Empty;
188171

189172
ECDsa = ConvertPublicKey(publicPointSpan.ToArray());
@@ -193,9 +176,6 @@ public EcdsaVerify(PivPublicKey pivPublicKey)
193176
/// Create an instance of the <see cref="EcdsaVerify"/> class using the
194177
/// COSE EC public key.
195178
/// </summary>
196-
/// <remarks>
197-
/// This supports only NIST P-256 and P-384 curves.
198-
/// </remarks>
199179
/// <param name="coseKey">
200180
/// The public key to use to verify.
201181
/// </param>
@@ -212,13 +192,7 @@ public EcdsaVerify(CoseKey coseKey)
212192
throw new ArgumentNullException(nameof(coseKey));
213193
}
214194

215-
string oid = coseKey.Algorithm switch
216-
{
217-
CoseAlgorithmIdentifier.ES256 => OidP256,
218-
CoseAlgorithmIdentifier.ECDHwHKDF256 => OidP256,
219-
CoseAlgorithmIdentifier.ES384 => OidP384,
220-
_ => "",
221-
};
195+
string oid = GetOidByAlgorithm(coseKey.Algorithm);
222196

223197
byte[] xCoordinate = Array.Empty<byte>();
224198
byte[] yCoordinate = Array.Empty<byte>();
@@ -236,10 +210,9 @@ public EcdsaVerify(CoseKey coseKey)
236210
/// encoded point.
237211
/// </summary>
238212
/// <remarks>
239-
/// This supports only NIST P-256 and P-384 curves and only supports the
240-
/// uncompressed encoded point: <c>04||x-coordinate||y-coordinate</c>
213+
/// This supports the uncompressed encoded point: <c>04||x-coordinate||y-coordinate</c>
241214
/// where both coordinates are the curve size (each coordinate is 32 bytes
242-
/// for P-256 and 48 bytes for P-384), prepended with 00 bytes if
215+
/// for P-256, 48 bytes for P-384 and 66 bytes for P-521), prepended with 00 bytes if
243216
/// necessary.
244217
/// </remarks>
245218
/// <param name="encodedEccPoint">
@@ -260,9 +233,6 @@ public EcdsaVerify(ReadOnlyMemory<byte> encodedEccPoint)
260233
/// Create an instance of the <see cref="EcdsaVerify"/> class using the
261234
/// given certificate.
262235
/// </summary>
263-
/// <remarks>
264-
/// This supports only NIST P-256 and P-384 curves.
265-
/// </remarks>
266236
/// <param name="certificate">
267237
/// The certificate containing the public key to use to verify.
268238
/// </param>
@@ -279,9 +249,8 @@ public EcdsaVerify(X509Certificate2 certificate)
279249

280250
/// <summary>
281251
/// Verify the <c>signature</c> using the <c>dataToVerify</c>. This
282-
/// method will digest the <c>dataToVerify</c> using SHA-256 if the
283-
/// public key is P-256 and SHA-384 if the public key is P-384, and then
284-
/// verify the signature using the digest.
252+
/// method will digest the <c>dataToVerify</c> using SHA-256, SHA-384 or SHA-512,
253+
/// depending on the public key's curve, and then verify the signature using the digest.
285254
/// </summary>
286255
/// <remarks>
287256
/// If the signature is the standard BER encoding, then pass <c>true</c>
@@ -293,7 +262,7 @@ public EcdsaVerify(X509Certificate2 certificate)
293262
/// </remarks>
294263
/// <param name="dataToVerify">
295264
/// The data data to verify. To verify an ECDSA signature, this method
296-
/// will digest the data using SHA-256 or SHA-384, depending on the
265+
/// will digest the data using SHA-256, SHA-384 or SHA-512, depending on the
297266
/// public key's curve.
298267
/// </param>
299268
/// <param name="signature">
@@ -315,12 +284,14 @@ public bool VerifyData(
315284
{
316285
HashAlgorithm digester = ECDsa.KeySize switch
317286
{
318-
P256KeySize => CryptographyProviders.Sha256Creator(),
319-
P384KeySize => CryptographyProviders.Sha384Creator(),
287+
256 => CryptographyProviders.Sha256Creator(),
288+
384 => CryptographyProviders.Sha384Creator(),
289+
521 => CryptographyProviders.Sha512Creator(),
320290
_ => throw new ArgumentException(ExceptionMessages.UnsupportedAlgorithm),
321291
};
322292

323-
return VerifyDigestedData(digester.ComputeHash(dataToVerify), signature, isStandardSignature);
293+
byte[] digestToVerify = digester.ComputeHash(dataToVerify);
294+
return VerifyDigestedData(digestToVerify, signature, isStandardSignature);
324295
}
325296

326297
/// <summary>
@@ -341,7 +312,7 @@ public bool VerifyData(
341312
/// The signature to verify.
342313
/// </param>
343314
/// <param name="isStandardSignature">
344-
/// <c>true</c> if the signature is formatted as the BER encoding
315+
/// <c>true</c> if the signature is formatted as the BER encoding (DSASignatureFormat.Rfc3279DerSequence)
345316
/// specified by most standards, or <c>false</c> if the signature is
346317
/// formatted as the concatenation of <c>r</c> and <c>s</c>.
347318
/// </param>
@@ -360,89 +331,89 @@ public bool VerifyDigestedData(
360331

361332
private static ECDsa ConvertPublicKey(ReadOnlyMemory<byte> encodedEccPoint)
362333
{
363-
string oid = "";
364-
byte[] xCoordinate = Array.Empty<byte>();
365-
byte[] yCoordinate = Array.Empty<byte>();
366-
367-
if (encodedEccPoint.Length >= MinEncodedPointLength && encodedEccPoint.Span[0] == EncodedPointTag)
334+
int minEncodedPointLength = (KeyDefinitions.P256.LengthInBytes * 2) + 1; // This is the minimum length for an encoded point on P-256 (0x04 || x || y)
335+
if (encodedEccPoint.Length < minEncodedPointLength || encodedEccPoint.Span[0] != EncodedPointTag)
368336
{
369-
int coordLength = (encodedEccPoint.Length - 1) / 2;
370-
xCoordinate = encodedEccPoint.Slice(1, coordLength).ToArray();
371-
yCoordinate = encodedEccPoint.Slice(1 + coordLength, coordLength).ToArray();
372-
373-
oid = encodedEccPoint.Length switch
374-
{
375-
P256EncodedPointLength => OidP256,
376-
P384EncodedPointLength => OidP384,
377-
_ => "",
378-
};
337+
throw new ArgumentException(ExceptionMessages.UnsupportedAlgorithm);
379338
}
380339

340+
int coordLength = (encodedEccPoint.Length - 1) / 2;
341+
byte[] xCoordinate = encodedEccPoint.Slice(1, coordLength).ToArray();
342+
byte[] yCoordinate = encodedEccPoint.Slice(1 + coordLength, coordLength).ToArray();
343+
344+
string oid = GetOidByLength(encodedEccPoint.Length);
381345
return ConvertPublicKey(oid, xCoordinate, yCoordinate);
382346
}
383347

384-
private static ECDsa ConvertPublicKey(string oid, byte[] xCoordinate, byte[] yCoordinate)
348+
private static string GetOidByLength(int encodedPointLength)
385349
{
386-
if (!string.IsNullOrEmpty(oid))
350+
if (encodedPointLength == (KeyDefinitions.P256.LengthInBytes * 2) + 1)
387351
{
388-
var eccCurve = ECCurve.CreateFromValue(oid);
389-
var eccParams = new ECParameters
390-
{
391-
Curve = eccCurve
392-
};
393-
394-
eccParams.Q.X = xCoordinate;
395-
eccParams.Q.Y = yCoordinate;
396-
397-
return CheckECDsa(ECDsa.Create(eccParams));
352+
return KeyDefinitions.KeyOids.P256;
353+
}
354+
if (encodedPointLength == (KeyDefinitions.P384.LengthInBytes * 2) + 1)
355+
{
356+
return KeyDefinitions.KeyOids.P384;
357+
}
358+
if (encodedPointLength == (KeyDefinitions.P521.LengthInBytes * 2) + 1)
359+
{
360+
return KeyDefinitions.KeyOids.P521;
398361
}
399362

400363
throw new ArgumentException(ExceptionMessages.UnsupportedAlgorithm);
401364
}
402365

403-
private static ECDsa CheckECDsa(ECDsa toCheck)
366+
private static ECDsa ConvertPublicKey(string oid, byte[] xCoordinate, byte[] yCoordinate)
404367
{
405-
var ecParameters = toCheck.ExportParameters(false);
368+
if (string.IsNullOrEmpty(oid))
369+
{
370+
throw new ArgumentException(ExceptionMessages.UnsupportedAlgorithm);
371+
}
406372

407-
int coordinateLength = ecParameters.Curve.Oid.Value switch
373+
var eccCurve = ECCurve.CreateFromValue(oid);
374+
var eccParams = new ECParameters
408375
{
409-
OidP256 => (P256EncodedPointLength - 1) / 2,
410-
OidP384 => (P384EncodedPointLength - 1) / 2,
411-
_ => -1,
376+
Curve = eccCurve
412377
};
413378

414-
if (ecParameters.Q.X.Length > 0 && ecParameters.Q.X.Length <= coordinateLength)
379+
eccParams.Q.X = xCoordinate;
380+
eccParams.Q.Y = yCoordinate;
381+
382+
var ecdsa = ECDsa.Create(eccParams);
383+
return CheckECDsa(ecdsa);
384+
}
385+
386+
private static ECDsa CheckECDsa(ECDsa toCheck)
387+
{
388+
var ecParameters = toCheck.ExportParameters(false);
389+
var keyDefinition = KeyDefinitions.GetByOid(ecParameters.Curve.Oid.Value);
390+
int coordinateLength = keyDefinition.LengthInBytes;
391+
392+
if (ecParameters.Q.X.Length == 0 ||
393+
ecParameters.Q.X.Length > coordinateLength ||
394+
ecParameters.Q.Y.Length == 0 ||
395+
ecParameters.Q.Y.Length > coordinateLength)
415396
{
416-
if (ecParameters.Q.Y.Length > 0 && ecParameters.Q.Y.Length <= coordinateLength)
417-
{
418-
return toCheck;
419-
}
397+
throw new ArgumentException(ExceptionMessages.UnsupportedAlgorithm);
420398
}
421399

422-
throw new ArgumentException(ExceptionMessages.UnsupportedAlgorithm);
400+
return toCheck;
423401
}
424402

425-
426-
// Convert the signature from standard to the concatenation of r and s.
427403
private static byte[] ConvertSignature(byte[] signature, int publicKeyBitSize)
428404
{
429-
int coordinateLength = publicKeyBitSize / 8;
430-
byte[] convertedSignature = new byte[2 * coordinateLength];
431-
var signatureMemory = new Memory<byte>(convertedSignature);
405+
int bytesNeededForCurve = (publicKeyBitSize + 7) / 8; // Round up to nearest byte
406+
Memory<byte> convertedSignatureBuffer = new byte[2 * bytesNeededForCurve];
432407

433408
var tlvReader = new TlvReader(signature);
434-
if (tlvReader.TryReadNestedTlv(out tlvReader, SequenceTag))
409+
if (!tlvReader.TryReadNestedTlv(out tlvReader, SequenceTag) ||
410+
!TryCopyNextInteger(tlvReader, convertedSignatureBuffer, bytesNeededForCurve) ||
411+
!TryCopyNextInteger(tlvReader, convertedSignatureBuffer[bytesNeededForCurve..], bytesNeededForCurve))
435412
{
436-
if (TryCopyNextInteger(tlvReader, signatureMemory, coordinateLength))
437-
{
438-
if (TryCopyNextInteger(tlvReader, signatureMemory[coordinateLength..], coordinateLength))
439-
{
440-
return convertedSignature;
441-
}
442-
}
413+
throw new ArgumentException(ExceptionMessages.UnsupportedAlgorithm);
443414
}
444415

445-
throw new ArgumentException(ExceptionMessages.UnsupportedAlgorithm);
416+
return convertedSignatureBuffer.ToArray();
446417
}
447418

448419
// Decode the next value in tlvReader, then copy the result into
@@ -483,6 +454,18 @@ private static bool TryCopyNextInteger(TlvReader tlvReader, Memory<byte> signatu
483454
return false;
484455
}
485456

457+
private static string GetOidByAlgorithm(CoseAlgorithmIdentifier algorithm)
458+
{
459+
return algorithm switch
460+
{
461+
CoseAlgorithmIdentifier.ES256 => KeyDefinitions.KeyOids.P256,
462+
CoseAlgorithmIdentifier.ECDHwHKDF256 => KeyDefinitions.KeyOids.P256,
463+
CoseAlgorithmIdentifier.ES384 => KeyDefinitions.KeyOids.P384,
464+
CoseAlgorithmIdentifier.ES512 => KeyDefinitions.KeyOids.P521,
465+
_ => throw new NotSupportedException(ExceptionMessages.UnsupportedAlgorithm)
466+
};
467+
}
468+
486469
/// <summary>
487470
/// Clean up
488471
/// </summary>

0 commit comments

Comments
 (0)