@@ -81,9 +81,6 @@ namespace Yubico.YubiKey.Cryptography
81
81
/// BER encoding.
82
82
/// </para>
83
83
/// <para>
84
- /// This class can verify signatures for P-256 and P-384 only.
85
- /// </para>
86
- /// <para>
87
84
/// Each of the verify methods will return a boolean, indicating whether the
88
85
/// signature verifies or not. If a signature does not verify, that is not an
89
86
/// error. The methods will throw exceptions if they encounter bad data, such
@@ -103,15 +100,7 @@ namespace Yubico.YubiKey.Cryptography
103
100
/// </remarks>
104
101
public class EcdsaVerify : IDisposable
105
102
{
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 ;
115
104
private const int SequenceTag = 0x30 ;
116
105
private const int IntegerTag = 0x02 ;
117
106
@@ -121,7 +110,7 @@ public class EcdsaVerify : IDisposable
121
110
/// The object built that will perform the verification operation.
122
111
/// </summary>
123
112
/// <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.
125
114
/// </remarks>
126
115
public ECDsa ECDsa { get ; private set ; }
127
116
@@ -136,9 +125,6 @@ private EcdsaVerify()
136
125
/// Create an instance of the <see cref="EcdsaVerify"/> class using the
137
126
/// ECDsa object that contains the public key.
138
127
/// </summary>
139
- /// <remarks>
140
- /// This supports only NIST P-256 and P-384 curves.
141
- /// </remarks>
142
128
/// <param name="ecdsa">
143
129
/// The public key to use to verify. This constructor will copy a
144
130
/// reference to this object.
@@ -163,9 +149,6 @@ public EcdsaVerify(ECDsa ecdsa)
163
149
/// Create an instance of the <see cref="EcdsaVerify"/> class using the
164
150
/// PIV ECC public key.
165
151
/// </summary>
166
- /// <remarks>
167
- /// This supports only NIST P-256 and P-384 curves.
168
- /// </remarks>
169
152
/// <param name="pivPublicKey">
170
153
/// The public key to use to verify.
171
154
/// </param>
@@ -183,7 +166,7 @@ public EcdsaVerify(PivPublicKey pivPublicKey)
183
166
}
184
167
185
168
var publicPointSpan = pivPublicKey is PivEccPublicKey eccKey
186
- ? eccKey . PublicPoint
169
+ ? eccKey . PublicPoint
187
170
: ReadOnlySpan < byte > . Empty ;
188
171
189
172
ECDsa = ConvertPublicKey ( publicPointSpan . ToArray ( ) ) ;
@@ -193,9 +176,6 @@ public EcdsaVerify(PivPublicKey pivPublicKey)
193
176
/// Create an instance of the <see cref="EcdsaVerify"/> class using the
194
177
/// COSE EC public key.
195
178
/// </summary>
196
- /// <remarks>
197
- /// This supports only NIST P-256 and P-384 curves.
198
- /// </remarks>
199
179
/// <param name="coseKey">
200
180
/// The public key to use to verify.
201
181
/// </param>
@@ -212,13 +192,7 @@ public EcdsaVerify(CoseKey coseKey)
212
192
throw new ArgumentNullException ( nameof ( coseKey ) ) ;
213
193
}
214
194
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 ) ;
222
196
223
197
byte [ ] xCoordinate = Array . Empty < byte > ( ) ;
224
198
byte [ ] yCoordinate = Array . Empty < byte > ( ) ;
@@ -236,10 +210,9 @@ public EcdsaVerify(CoseKey coseKey)
236
210
/// encoded point.
237
211
/// </summary>
238
212
/// <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>
241
214
/// 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
243
216
/// necessary.
244
217
/// </remarks>
245
218
/// <param name="encodedEccPoint">
@@ -260,9 +233,6 @@ public EcdsaVerify(ReadOnlyMemory<byte> encodedEccPoint)
260
233
/// Create an instance of the <see cref="EcdsaVerify"/> class using the
261
234
/// given certificate.
262
235
/// </summary>
263
- /// <remarks>
264
- /// This supports only NIST P-256 and P-384 curves.
265
- /// </remarks>
266
236
/// <param name="certificate">
267
237
/// The certificate containing the public key to use to verify.
268
238
/// </param>
@@ -279,9 +249,8 @@ public EcdsaVerify(X509Certificate2 certificate)
279
249
280
250
/// <summary>
281
251
/// 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.
285
254
/// </summary>
286
255
/// <remarks>
287
256
/// If the signature is the standard BER encoding, then pass <c>true</c>
@@ -293,7 +262,7 @@ public EcdsaVerify(X509Certificate2 certificate)
293
262
/// </remarks>
294
263
/// <param name="dataToVerify">
295
264
/// 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
297
266
/// public key's curve.
298
267
/// </param>
299
268
/// <param name="signature">
@@ -315,12 +284,14 @@ public bool VerifyData(
315
284
{
316
285
HashAlgorithm digester = ECDsa . KeySize switch
317
286
{
318
- P256KeySize => CryptographyProviders . Sha256Creator ( ) ,
319
- P384KeySize => CryptographyProviders . Sha384Creator ( ) ,
287
+ 256 => CryptographyProviders . Sha256Creator ( ) ,
288
+ 384 => CryptographyProviders . Sha384Creator ( ) ,
289
+ 521 => CryptographyProviders . Sha512Creator ( ) ,
320
290
_ => throw new ArgumentException ( ExceptionMessages . UnsupportedAlgorithm ) ,
321
291
} ;
322
292
323
- return VerifyDigestedData ( digester . ComputeHash ( dataToVerify ) , signature , isStandardSignature ) ;
293
+ byte [ ] digestToVerify = digester . ComputeHash ( dataToVerify ) ;
294
+ return VerifyDigestedData ( digestToVerify , signature , isStandardSignature ) ;
324
295
}
325
296
326
297
/// <summary>
@@ -341,7 +312,7 @@ public bool VerifyData(
341
312
/// The signature to verify.
342
313
/// </param>
343
314
/// <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)
345
316
/// specified by most standards, or <c>false</c> if the signature is
346
317
/// formatted as the concatenation of <c>r</c> and <c>s</c>.
347
318
/// </param>
@@ -360,89 +331,89 @@ public bool VerifyDigestedData(
360
331
361
332
private static ECDsa ConvertPublicKey ( ReadOnlyMemory < byte > encodedEccPoint )
362
333
{
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 )
368
336
{
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 ) ;
379
338
}
380
339
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 ) ;
381
345
return ConvertPublicKey ( oid , xCoordinate , yCoordinate ) ;
382
346
}
383
347
384
- private static ECDsa ConvertPublicKey ( string oid , byte [ ] xCoordinate , byte [ ] yCoordinate )
348
+ private static string GetOidByLength ( int encodedPointLength )
385
349
{
386
- if ( ! string . IsNullOrEmpty ( oid ) )
350
+ if ( encodedPointLength == ( KeyDefinitions . P256 . LengthInBytes * 2 ) + 1 )
387
351
{
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 ;
398
361
}
399
362
400
363
throw new ArgumentException ( ExceptionMessages . UnsupportedAlgorithm ) ;
401
364
}
402
365
403
- private static ECDsa CheckECDsa ( ECDsa toCheck )
366
+ private static ECDsa ConvertPublicKey ( string oid , byte [ ] xCoordinate , byte [ ] yCoordinate )
404
367
{
405
- var ecParameters = toCheck . ExportParameters ( false ) ;
368
+ if ( string . IsNullOrEmpty ( oid ) )
369
+ {
370
+ throw new ArgumentException ( ExceptionMessages . UnsupportedAlgorithm ) ;
371
+ }
406
372
407
- int coordinateLength = ecParameters . Curve . Oid . Value switch
373
+ var eccCurve = ECCurve . CreateFromValue ( oid ) ;
374
+ var eccParams = new ECParameters
408
375
{
409
- OidP256 => ( P256EncodedPointLength - 1 ) / 2 ,
410
- OidP384 => ( P384EncodedPointLength - 1 ) / 2 ,
411
- _ => - 1 ,
376
+ Curve = eccCurve
412
377
} ;
413
378
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 )
415
396
{
416
- if ( ecParameters . Q . Y . Length > 0 && ecParameters . Q . Y . Length <= coordinateLength )
417
- {
418
- return toCheck ;
419
- }
397
+ throw new ArgumentException ( ExceptionMessages . UnsupportedAlgorithm ) ;
420
398
}
421
399
422
- throw new ArgumentException ( ExceptionMessages . UnsupportedAlgorithm ) ;
400
+ return toCheck ;
423
401
}
424
402
425
-
426
- // Convert the signature from standard to the concatenation of r and s.
427
403
private static byte [ ] ConvertSignature ( byte [ ] signature , int publicKeyBitSize )
428
404
{
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 ] ;
432
407
433
408
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 ) )
435
412
{
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 ) ;
443
414
}
444
415
445
- throw new ArgumentException ( ExceptionMessages . UnsupportedAlgorithm ) ;
416
+ return convertedSignatureBuffer . ToArray ( ) ;
446
417
}
447
418
448
419
// Decode the next value in tlvReader, then copy the result into
@@ -483,6 +454,18 @@ private static bool TryCopyNextInteger(TlvReader tlvReader, Memory<byte> signatu
483
454
return false ;
484
455
}
485
456
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
+
486
469
/// <summary>
487
470
/// Clean up
488
471
/// </summary>
0 commit comments