14
14
15
15
using System ;
16
16
using System . Globalization ;
17
+ using System . IO ;
18
+ using System . IO . Compression ;
17
19
using System . Security ;
18
20
using System . Security . Cryptography . X509Certificates ;
19
21
using Yubico . Core . Tlv ;
20
22
using Yubico . YubiKey . Cryptography ;
21
23
using Yubico . YubiKey . Piv . Commands ;
22
24
using Yubico . YubiKey . Piv . Converters ;
23
- using static Yubico . YubiKey . Cryptography . KeyDefinitions ;
24
25
25
26
namespace Yubico . YubiKey . Piv
26
27
{
@@ -29,9 +30,19 @@ namespace Yubico.YubiKey.Piv
29
30
// certificates associated with the private keys.
30
31
public sealed partial class PivSession : IDisposable
31
32
{
32
- private const int PivCompressionTag = 0x71 ;
33
+ private const int PivCertInfoTag = 0x71 ;
33
34
private const int PivLrcTag = 0xFE ;
34
35
36
+ /// <summary>
37
+ /// Indicates the certificate is not compressed
38
+ /// </summary>
39
+ private const byte UncompressedCert = 0 ;
40
+
41
+ /// <summary>
42
+ /// Indicates the certificate is compressed
43
+ /// </summary>
44
+ private const byte CompressedCert = 1 ;
45
+
35
46
[ Obsolete ( "Usage of PivEccPublic/PivEccPrivateKey is deprecated. Use IPublicKey, IPrivateKey instead" , false ) ]
36
47
public PivPublicKey GenerateKeyPair (
37
48
byte slotNumber ,
@@ -452,6 +463,9 @@ public void ImportPrivateKey(
452
463
/// <param name="certificate">
453
464
/// The certificate to import into the YubiKey.
454
465
/// </param>
466
+ /// <param name="compress">
467
+ /// If true the certificate will be compressed before being stored on the YubiKey.
468
+ /// </param>
455
469
/// <exception cref="ArgumentNullException">
456
470
/// The <c>certificate</c> argument is <c>null</c>.
457
471
/// </exception>
@@ -470,7 +484,7 @@ public void ImportPrivateKey(
470
484
/// Mutual authentication was performed and the YubiKey was not
471
485
/// authenticated.
472
486
/// </exception>
473
- public void ImportCertificate ( byte slotNumber , X509Certificate2 certificate )
487
+ public void ImportCertificate ( byte slotNumber , X509Certificate2 certificate , bool compress = false )
474
488
{
475
489
if ( certificate is null )
476
490
{
@@ -479,20 +493,35 @@ public void ImportCertificate(byte slotNumber, X509Certificate2 certificate)
479
493
480
494
RefreshManagementKeyAuthentication ( ) ;
481
495
482
- var dataTag = GetCertDataTagFromSlotNumber ( slotNumber ) ;
483
-
496
+ byte certInfo = UncompressedCert ;
484
497
byte [ ] certDer = certificate . GetRawCertData ( ) ;
485
- var tlvWriter = new TlvWriter ( ) ;
498
+ if ( compress )
499
+ {
500
+ certInfo = CompressedCert ;
501
+ try
502
+ {
503
+ certDer = Compress ( certDer ) ;
504
+ }
505
+ catch ( Exception )
506
+ {
507
+ throw new InvalidOperationException (
508
+ string . Format (
509
+ CultureInfo . CurrentCulture ,
510
+ ExceptionMessages . FailedCompressingCertificate ) ) ;
511
+ }
512
+ }
486
513
514
+ var tlvWriter = new TlvWriter ( ) ;
487
515
using ( tlvWriter . WriteNestedTlv ( PivEncodingTag ) )
488
516
{
489
517
tlvWriter . WriteValue ( PivCertTag , certDer ) ;
490
- tlvWriter . WriteByte ( PivCompressionTag , 0 ) ;
518
+ tlvWriter . WriteByte ( PivCertInfoTag , certInfo ) ;
491
519
tlvWriter . WriteValue ( PivLrcTag , null ) ;
492
520
}
493
521
494
522
byte [ ] encodedCert = tlvWriter . Encode ( ) ;
495
523
524
+ var dataTag = GetCertDataTagFromSlotNumber ( slotNumber ) ;
496
525
var command = new PutDataCommand ( ( int ) dataTag , encodedCert ) ;
497
526
var response = Connection . SendCommand ( command ) ;
498
527
if ( response . Status != ResponseStatus . Success )
@@ -546,22 +575,36 @@ public X509Certificate2 GetCertificate(byte slotNumber)
546
575
var response = Connection . SendCommand ( command ) ;
547
576
var encodedCertData = response . GetData ( ) ;
548
577
549
- var tlvReader = new TlvReader ( encodedCertData ) ;
578
+ var responseData = TlvObjects . UnpackValue ( PivEncodingTag , encodedCertData . Span ) ;
579
+ var certData = TlvObjects . DecodeDictionary ( responseData . Span ) ;
550
580
551
- bool isValid = tlvReader . TryReadNestedTlv ( out var nestedReader , PivEncodingTag ) ;
552
- if ( isValid )
581
+ bool hasCertInfo = certData . TryGetValue ( PivCertInfoTag , out var certInfo ) ;
582
+ if ( ! certData . TryGetValue ( PivCertTag , out var certBytes ) )
553
583
{
554
- isValid = nestedReader . TryReadValue ( out var certData , PivCertTag ) ;
555
- if ( isValid )
556
- {
557
- return new X509Certificate2 ( certData . ToArray ( ) ) ;
558
- }
584
+ throw new InvalidOperationException (
585
+ string . Format (
586
+ CultureInfo . CurrentCulture ,
587
+ ExceptionMessages . FailedParsingCertificate ) ) ;
559
588
}
560
589
561
- throw new InvalidOperationException (
562
- string . Format (
563
- CultureInfo . CurrentCulture ,
564
- ExceptionMessages . FailedParsingCertificate ) ) ;
590
+ byte [ ] certBytesCopy = certBytes . ToArray ( ) ;
591
+ bool isCompressed = hasCertInfo && certInfo . Length > 0 && certInfo . Span [ 0 ] == CompressedCert ;
592
+ if ( ! isCompressed )
593
+ {
594
+ return new X509Certificate2 ( certBytesCopy ) ;
595
+ }
596
+
597
+ try
598
+ {
599
+ return new X509Certificate2 ( Decompress ( certBytesCopy ) ) ;
600
+ }
601
+ catch ( Exception )
602
+ {
603
+ throw new InvalidOperationException (
604
+ string . Format (
605
+ CultureInfo . CurrentCulture ,
606
+ ExceptionMessages . FailedDecompressingCertificate ) ) ;
607
+ }
565
608
}
566
609
567
610
// There is a DataTag to use when calling PUT DATA. To put a cert onto
@@ -587,15 +630,36 @@ private static PivDataTag GetCertDataTagFromSlotNumber(byte slotNumber)
587
630
slotNumber ) ) ,
588
631
} ;
589
632
}
590
-
633
+
591
634
private void RefreshManagementKeyAuthentication ( )
592
635
{
593
636
if ( ManagementKeyAuthenticated )
594
637
{
595
638
return ;
596
639
}
597
-
640
+
598
641
AuthenticateManagementKey ( ) ;
599
642
}
643
+
644
+ static private byte [ ] Compress ( byte [ ] data )
645
+ {
646
+ using var compressedStream = new MemoryStream ( ) ;
647
+ using ( var compressor = new GZipStream ( compressedStream , CompressionLevel . Optimal ) )
648
+ {
649
+ compressor . Write ( data , 0 , data . Length ) ;
650
+ }
651
+
652
+ return compressedStream . ToArray ( ) ;
653
+ }
654
+
655
+ static private byte [ ] Decompress ( byte [ ] data )
656
+ {
657
+ using var dataStream = new MemoryStream ( data ) ;
658
+ using var decompressor = new GZipStream ( dataStream , CompressionMode . Decompress ) ;
659
+ using var decompressedStream = new MemoryStream ( ) ;
660
+ decompressor . CopyTo ( decompressedStream ) ;
661
+
662
+ return decompressedStream . ToArray ( ) ;
663
+ }
600
664
}
601
665
}
0 commit comments