Skip to content

Commit a4241ab

Browse files
committed
Merge PR #219
2 parents ed18144 + c5e22a6 commit a4241ab

File tree

5 files changed

+141
-162
lines changed

5 files changed

+141
-162
lines changed

Yubico.YubiKey/src/Resources/ExceptionMessages.Designer.cs

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Yubico.YubiKey/src/Resources/ExceptionMessages.resx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,8 +475,14 @@
475475
<data name="Ctap2UnknownAttestationFormat" xml:space="preserve">
476476
<value>The attestation was provided in an unknown format and cannot be parsed.</value>
477477
</data>
478+
<data name="FailedCompressingCertificate" xml:space="preserve">
479+
<value>Failed to compress certificate.</value>
480+
</data>
481+
<data name="FailedDecompressingCertificate" xml:space="preserve">
482+
<value>Failed to decompress certificate.</value>
483+
</data>
478484
<data name="FailedParsingCertificate" xml:space="preserve">
479-
<value>A certificate was not able to parsed.</value>
485+
<value>Failed to parse certificate data.</value>
480486
</data>
481487
<data name="InvalidOtpChallengeResponseAlgorithm" xml:space="preserve">
482488
<value>The challenge / response algorithm specified is invalid.</value>

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

Lines changed: 85 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414

1515
using System;
1616
using System.Globalization;
17+
using System.IO;
18+
using System.IO.Compression;
1719
using System.Security;
1820
using System.Security.Cryptography.X509Certificates;
1921
using Yubico.Core.Tlv;
2022
using Yubico.YubiKey.Cryptography;
2123
using Yubico.YubiKey.Piv.Commands;
2224
using Yubico.YubiKey.Piv.Converters;
23-
using static Yubico.YubiKey.Cryptography.KeyDefinitions;
2425

2526
namespace Yubico.YubiKey.Piv
2627
{
@@ -29,9 +30,19 @@ namespace Yubico.YubiKey.Piv
2930
// certificates associated with the private keys.
3031
public sealed partial class PivSession : IDisposable
3132
{
32-
private const int PivCompressionTag = 0x71;
33+
private const int PivCertInfoTag = 0x71;
3334
private const int PivLrcTag = 0xFE;
3435

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+
3546
[Obsolete("Usage of PivEccPublic/PivEccPrivateKey is deprecated. Use IPublicKey, IPrivateKey instead", false)]
3647
public PivPublicKey GenerateKeyPair(
3748
byte slotNumber,
@@ -452,6 +463,9 @@ public void ImportPrivateKey(
452463
/// <param name="certificate">
453464
/// The certificate to import into the YubiKey.
454465
/// </param>
466+
/// <param name="compress">
467+
/// If true the certificate will be compressed before being stored on the YubiKey.
468+
/// </param>
455469
/// <exception cref="ArgumentNullException">
456470
/// The <c>certificate</c> argument is <c>null</c>.
457471
/// </exception>
@@ -470,7 +484,7 @@ public void ImportPrivateKey(
470484
/// Mutual authentication was performed and the YubiKey was not
471485
/// authenticated.
472486
/// </exception>
473-
public void ImportCertificate(byte slotNumber, X509Certificate2 certificate)
487+
public void ImportCertificate(byte slotNumber, X509Certificate2 certificate, bool compress = false)
474488
{
475489
if (certificate is null)
476490
{
@@ -479,20 +493,35 @@ public void ImportCertificate(byte slotNumber, X509Certificate2 certificate)
479493

480494
RefreshManagementKeyAuthentication();
481495

482-
var dataTag = GetCertDataTagFromSlotNumber(slotNumber);
483-
496+
byte certInfo = UncompressedCert;
484497
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+
}
486513

514+
var tlvWriter = new TlvWriter();
487515
using (tlvWriter.WriteNestedTlv(PivEncodingTag))
488516
{
489517
tlvWriter.WriteValue(PivCertTag, certDer);
490-
tlvWriter.WriteByte(PivCompressionTag, 0);
518+
tlvWriter.WriteByte(PivCertInfoTag, certInfo);
491519
tlvWriter.WriteValue(PivLrcTag, null);
492520
}
493521

494522
byte[] encodedCert = tlvWriter.Encode();
495523

524+
var dataTag = GetCertDataTagFromSlotNumber(slotNumber);
496525
var command = new PutDataCommand((int)dataTag, encodedCert);
497526
var response = Connection.SendCommand(command);
498527
if (response.Status != ResponseStatus.Success)
@@ -546,22 +575,36 @@ public X509Certificate2 GetCertificate(byte slotNumber)
546575
var response = Connection.SendCommand(command);
547576
var encodedCertData = response.GetData();
548577

549-
var tlvReader = new TlvReader(encodedCertData);
578+
var responseData = TlvObjects.UnpackValue(PivEncodingTag, encodedCertData.Span);
579+
var certData = TlvObjects.DecodeDictionary(responseData.Span);
550580

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))
553583
{
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));
559588
}
560589

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+
}
565608
}
566609

567610
// 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)
587630
slotNumber)),
588631
};
589632
}
590-
633+
591634
private void RefreshManagementKeyAuthentication()
592635
{
593636
if (ManagementKeyAuthenticated)
594637
{
595638
return;
596639
}
597-
640+
598641
AuthenticateManagementKey();
599642
}
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+
}
600664
}
601665
}

Yubico.YubiKey/tests/integration/Yubico/YubiKey/Piv/CertTests.cs

Lines changed: 0 additions & 104 deletions
This file was deleted.

0 commit comments

Comments
 (0)