13
13
// limitations under the License.
14
14
15
15
using System ;
16
+ using System . Formats . Asn1 ;
16
17
using System . Globalization ;
18
+ using System . Numerics ;
17
19
using System . Security ;
18
20
using System . Security . Cryptography . X509Certificates ;
19
21
using Yubico . Core . Tlv ;
@@ -35,11 +37,6 @@ public sealed partial class PivSession : IDisposable
35
37
36
38
/// <summary>
37
39
/// Create an attestation statement for the private key in the given slot.
38
- /// > [!NOTE]
39
- /// > In version 1.0.0 of the SDK, it was not possible to get an
40
- /// > attestation statement for keys in slots 82 - 95 (retired key
41
- /// > slots). However, beginning with SDK 1.0.1, it is possible to get
42
- /// > attestation statements for keys in those slots.
43
40
/// </summary>
44
41
/// <remarks>
45
42
/// See the User's Manual entry on
@@ -55,16 +52,7 @@ public sealed partial class PivSession : IDisposable
55
52
/// </para>
56
53
/// <para>
57
54
/// It is possible to create attestation statements only for keys
58
- /// generated on a YubiKey, and only for keys in the following slots:
59
- /// <code>
60
- /// PivSlot.Authentication = 9A
61
- /// PivSlot.Signing = 9C
62
- /// PivSlot.KeyManagement = 9D
63
- /// PivSlot.CardAuthentication = 9E
64
- /// PivSlot.Retired1 = 82
65
- /// through
66
- /// PivSlot.Retired20 = 95
67
- /// </code>
55
+ /// generated on a YubiKey.
68
56
/// If the <c>slotNumber</c> argument is for any other slot, or if there
69
57
/// is no key in the slot, or if the key in the slot was imported and not
70
58
/// generated by the YubiKey, this method will throw an exception.
@@ -115,21 +103,22 @@ public sealed partial class PivSession : IDisposable
115
103
/// </exception>
116
104
public X509Certificate2 CreateAttestationStatement ( byte slotNumber )
117
105
{
118
- if ( YubiKey . HasFeature ( YubiKeyFeature . PivAttestation ) )
106
+ if ( ! YubiKey . HasFeature ( YubiKeyFeature . PivAttestation ) )
119
107
{
120
- // This call will throw an exception if the slot number is incorrect.
121
- var command = new CreateAttestationStatementCommand ( slotNumber ) ;
122
- var response = Connection . SendCommand ( command ) ;
123
-
124
- // This call will throw an exception if there was a problem with
125
- // attestation (imported, invalid cert, etc.).
126
- return response . GetData ( ) ;
108
+ throw new NotSupportedException (
109
+ string . Format (
110
+ CultureInfo . CurrentCulture ,
111
+ ExceptionMessages . NotSupportedByYubiKeyVersion ) ) ;
127
112
}
128
113
129
- throw new NotSupportedException (
130
- string . Format (
131
- CultureInfo . CurrentCulture ,
132
- ExceptionMessages . NotSupportedByYubiKeyVersion ) ) ;
114
+ // This call will throw an exception if the slot number is incorrect.
115
+ var command = new CreateAttestationStatementCommand ( slotNumber ) ;
116
+ var response = Connection . SendCommand ( command ) ;
117
+
118
+ // This call will throw an exception if there was a problem with
119
+ // attestation (imported, invalid cert, etc.).
120
+ return response . GetData ( ) ;
121
+
133
122
}
134
123
135
124
/// <summary>
@@ -201,19 +190,20 @@ public X509Certificate2 GetAttestationCertificate()
201
190
ExceptionMessages . NotSupportedByYubiKeyVersion ) ) ;
202
191
}
203
192
204
-
205
193
[ Obsolete ( "Usage of PivEccPublic/PivEccPrivateKey is deprecated. Use IPublicKey, IPrivateKey instead" , false ) ]
206
194
public void ReplaceAttestationKeyAndCertificate ( PivPrivateKey privateKey , X509Certificate2 certificate )
207
195
{
208
- byte [ ] certDer = CheckVersionKeyAndCertRequirements ( privateKey . Algorithm . GetKeyType ( ) , certificate ) ;
196
+ CheckVersionKeyAndCertRequirements ( privateKey . Algorithm . GetKeyType ( ) , certificate ) ;
209
197
198
+ byte [ ] certDer = certificate . GetRawCertData ( ) ;
210
199
var tlvWriter = new TlvWriter ( ) ;
211
200
using ( tlvWriter . WriteNestedTlv ( 0x53 ) )
212
201
{
213
202
tlvWriter . WriteValue ( 0x70 , certDer ) ;
214
203
tlvWriter . WriteByte ( 0x71 , 0 ) ;
215
204
tlvWriter . WriteValue ( 0xfe , null ) ;
216
205
}
206
+
217
207
byte [ ] encodedCert = tlvWriter . Encode ( ) ;
218
208
219
209
ImportPrivateKey ( PivSlot . Attestation , privateKey ) ;
@@ -229,8 +219,8 @@ public void ReplaceAttestationKeyAndCertificate(PivPrivateKey privateKey, X509Ce
229
219
response . StatusWord . ToString ( "X4" , CultureInfo . InvariantCulture ) ) ) ;
230
220
}
231
221
}
232
-
233
- /// <summary>
222
+
223
+ /// <summary>
234
224
/// Replace the attestation key and certificate.
235
225
/// </summary>
236
226
/// <remarks>
@@ -363,21 +353,23 @@ public void ReplaceAttestationKeyAndCertificate(IPrivateKey privateKey, X509Cert
363
353
{
364
354
throw new ArgumentNullException ( nameof ( privateKey ) ) ;
365
355
}
366
-
356
+
367
357
if ( certificate is null )
368
358
{
369
359
throw new ArgumentNullException ( nameof ( certificate ) ) ;
370
360
}
371
-
372
- byte [ ] certDer = CheckVersionKeyAndCertRequirements ( privateKey . KeyType , certificate ) ;
373
361
362
+ CheckVersionKeyAndCertRequirements ( privateKey . KeyType , certificate ) ;
363
+
364
+ byte [ ] certDer = certificate . GetRawCertData ( ) ;
374
365
var tlvWriter = new TlvWriter ( ) ;
375
366
using ( tlvWriter . WriteNestedTlv ( 0x53 ) )
376
367
{
377
368
tlvWriter . WriteValue ( 0x70 , certDer ) ;
378
369
tlvWriter . WriteByte ( 0x71 , 0 ) ;
379
370
tlvWriter . WriteValue ( 0xfe , null ) ;
380
371
}
372
+
381
373
byte [ ] encodedCert = tlvWriter . Encode ( ) ;
382
374
383
375
ImportPrivateKey ( PivSlot . Attestation , privateKey ) ;
@@ -402,7 +394,7 @@ public void ReplaceAttestationKeyAndCertificate(IPrivateKey privateKey, X509Cert
402
394
// This will throw an exception if a check fails, or if one or both
403
395
// arguments are null, or the algorithm is unsupported.
404
396
// Return the DER encoding of the certificate.
405
- private byte [ ] CheckVersionKeyAndCertRequirements ( KeyType keyType , X509Certificate2 certificate )
397
+ private void CheckVersionKeyAndCertRequirements ( KeyType keyType , X509Certificate2 certificate )
406
398
{
407
399
if ( ! YubiKey . HasFeature ( YubiKeyFeature . PivAttestation ) )
408
400
{
@@ -412,126 +404,73 @@ private byte[] CheckVersionKeyAndCertRequirements(KeyType keyType, X509Certifica
412
404
ExceptionMessages . NotSupportedByYubiKeyVersion ) ) ;
413
405
}
414
406
415
- var keyDefinition = KeyDefinitions . GetByKeyType ( keyType ) ;
416
- int keySize = keyDefinition . LengthInBytes ;
417
-
418
- bool isValidCert = IsCert ( certificate , out byte [ ] certDer ) ;
419
- isValidCert = IsCertSameAlgorithm ( isValidCert , certificate , keySize , keyType ) ;
420
- isValidCert = IsCertNameAndValidity ( isValidCert , certDer ) ;
421
-
422
- if ( isValidCert == false )
407
+ bool isValidCert = IsSupportedCert ( certificate , keyType ) ;
408
+ if ( ! isValidCert )
423
409
{
424
410
throw new ArgumentException (
425
411
string . Format (
426
412
CultureInfo . CurrentCulture ,
427
413
ExceptionMessages . UnsupportedAttestationCert ) ) ;
428
414
}
429
-
430
- return certDer ;
431
415
}
432
416
433
417
// Is there really a cert in this variable? Is it > version 1?
434
418
// If so, set certDer to the DER encoding of the cert.
435
- private static bool IsCert ( X509Certificate2 certificate , out byte [ ] certDer )
419
+ private static bool IsSupportedCert ( X509Certificate2 certificate , KeyType keyType )
436
420
{
437
- certDer = Array . Empty < byte > ( ) ;
421
+ byte [ ] certDer = certificate . GetRawCertData ( ) ;
438
422
439
- if ( certificate is null )
440
- {
441
- throw new ArgumentNullException ( nameof ( certificate ) ) ;
442
- }
423
+ return certificate . Handle != IntPtr . Zero &&
424
+ certificate . Version > 1 &&
425
+ certDer . Length is > 0 and < MaximumCertDerLength &&
426
+ certificate . PublicKey . Oid . Value == keyType . GetAlgorithmOid ( ) &&
427
+ IsValidCertNameAndValidity ( certDer ) ;
428
+ }
443
429
444
- if ( certificate . Handle != IntPtr . Zero )
445
- {
446
- if ( certificate . Version > 1 )
447
- {
448
- certDer = certificate . GetRawCertData ( ) ;
449
- }
450
- }
430
+ private static bool IsValidCertNameAndValidity ( byte [ ] certDer )
431
+ {
432
+ var reader = new AsnReader ( certDer , AsnEncodingRules . BER ) ;
433
+ var seqCert = reader . ReadSequence ( ) ;
434
+ var seqTbsCert = seqCert . ReadSequence ( ) ;
435
+ _ = ReadVersion ( seqTbsCert ) ;
451
436
452
- return certDer . Length > 0 && certDer . Length < MaximumCertDerLength ;
437
+ // Read serial number integer, signature and issuer sequence
438
+ _ = seqTbsCert . ReadInteger ( ) ;
439
+ _ = seqTbsCert . ReadSequence ( ) ;
440
+ _ = seqTbsCert . ReadSequence ( ) ;
441
+
442
+ var validity = seqTbsCert . ReadSequence ( ) ;
443
+ int validityValue = GetSequenceLength ( validity ) ;
444
+
445
+ var subject = seqTbsCert . ReadSequence ( ) ;
446
+ int subjectValue = GetSequenceLength ( subject ) ;
447
+
448
+ return validityValue < MaximumValidityValueLength && subjectValue < MaximumNameValueLength ;
453
449
}
454
450
455
- // Does the cert in the object share the algorithm and key size?
456
- // If the input arg isValidCert is false, don't check, just return false;
457
- private static bool IsCertSameAlgorithm ( bool isValidCert , X509Certificate2 certificate , int keySize , KeyType keyType )
451
+ private static BigInteger ? ReadVersion ( AsnReader seqTbsCert )
458
452
{
459
- bool returnValue = false ;
460
-
461
- if ( isValidCert )
453
+ if ( ! seqTbsCert . HasData ||
454
+ seqTbsCert . PeekTag ( ) . TagClass != TagClass . ContextSpecific ||
455
+ seqTbsCert . PeekTag ( ) . TagValue != 0 )
462
456
{
463
- // For ECC, the Certificate class's PublicKey property does not
464
- // have a Key. But the public key is in the EncodedKeyValue
465
- // property. An encoded ECC public key is a point:
466
- // 04 || x-coord || y-coord fixed length
467
- // The length of each coordinate is the key size. Because keySize
468
- // is given in bits, and the Length of the encoded key is given
469
- // in bytes, compare ( 2 * (keySize / 8)) + 1.
470
- // That's why the comparison is to (keySize / 4) + 1.
471
- if ( certificate . PublicKey . Oid . Value == keyType . GetAlgorithmOid ( ) )
472
- {
473
- returnValue = keySize switch
474
- {
475
- 256 => certificate . PublicKey . EncodedKeyValue . RawData . Length == ( keySize / 4 ) + 1 ,
476
- 384 => certificate . PublicKey . EncodedKeyValue . RawData . Length == ( keySize / 4 ) + 1 ,
477
- 2048 => certificate . PublicKey . Key . KeySize == keySize ,
478
- _ => false ,
479
- } ;
480
- }
457
+ return null ;
481
458
}
482
459
483
- return returnValue ;
460
+ var versionReader = seqTbsCert . ReadSequence ( new Asn1Tag ( TagClass . ContextSpecific , 0 ) ) ;
461
+ return versionReader . ReadInteger ( ) ;
484
462
}
485
463
486
- // Does the cert in the object have Validity and IssuerName encodings
487
- // that are not too long?
488
- // If the input arg isValidCert is false, don't check, just return false;
489
- private static bool IsCertNameAndValidity ( bool isValidCert , byte [ ] certDer )
464
+ private static int GetSequenceLength ( AsnReader validity )
490
465
{
491
- bool returnValue = false ;
492
-
493
- if ( isValidCert )
466
+ var clone = validity . Clone ( ) ;
467
+ int length = 0 ;
468
+ while ( clone . HasData )
494
469
{
495
- // Get some of the elements of a cert. We just want to verify
496
- // their lengths.
497
- // The cert is the DER encoding of
498
- // SEQ {
499
- // SEQ {
500
- // [0] EXPLICIT INTEGER OPTIONAL,
501
- // INTEGER,
502
- // AlgId (a SEQ)
503
- // IssuerName (a SEQ)
504
- // Validity (a SEQ)
505
- // SubjectName (a SEQ)
506
- // We just want to know how long the SubjectName and Validity are,
507
- // so "decode" them as full elements (don't decode the contents
508
- // of the IssuerName and Validity SEQUENCEs).
509
- var reader = new TlvReader ( certDer ) ;
510
- if ( reader . TryReadNestedTlv ( out reader , 0x30 ) )
511
- {
512
- if ( reader . TryReadNestedTlv ( out reader , 0x30 ) )
513
- {
514
- byte [ ] tags = new byte [ ] { 0xA0 , 0x02 , 0x30 , 0x30 , 0x30 , 0x30 } ;
515
- var value = new ReadOnlyMemory < byte > [ tags . Length ] ;
516
- int index = 0 ;
517
- for ( ; index < tags . Length ; index ++ )
518
- {
519
- if ( reader . TryReadValue ( out value [ index ] , tags [ index ] ) == false )
520
- {
521
- break ;
522
- }
523
- }
524
-
525
- if ( index >= tags . Length )
526
- {
527
- returnValue = value [ 4 ] . Length < MaximumValidityValueLength &&
528
- value [ 5 ] . Length < MaximumNameValueLength ;
529
- }
530
- }
531
- }
470
+ length += clone . ReadEncodedValue ( ) . Length ;
532
471
}
533
472
534
- return returnValue ;
473
+ return length ;
535
474
}
536
475
}
537
476
}
0 commit comments