Skip to content

Commit 3d805dc

Browse files
authored
Merge pull request #224 from Yubico/release/1.13.1
Release 1.13.1
2 parents fc85c3e + 27ab1f8 commit 3d805dc

21 files changed

+310
-266
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/Cryptography/AsnPrivateKeyDecoder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public static IPrivateKey CreatePrivateKey(ReadOnlyMemory<byte> pkcs8EncodedKey)
7070
public static Curve25519PrivateKey CreateCurve25519Key(ReadOnlyMemory<byte> pkcs8EncodedKey) =>
7171
Curve25519PrivateKey.CreateFromPkcs8(pkcs8EncodedKey);
7272

73-
public static (byte[], KeyType) GetCurve25519PrivateKeyData(ReadOnlyMemory<byte> pkcs8EncodedKey)
73+
public static (byte[] privateKey, KeyType keyType) GetCurve25519PrivateKeyData(ReadOnlyMemory<byte> pkcs8EncodedKey)
7474
{
7575
var reader = new AsnReader(pkcs8EncodedKey, AsnEncodingRules.DER);
7676
var seqPrivateKeyInfo = reader.ReadSequence();

Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/AsnUtilities.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ public static Span<byte> GetIntegerBytes(Span<byte> value)
8484
/// <exception cref="CryptographicException">If the private key does not meet the bit clamping requirements.</exception>
8585
public static void VerifyX25519PrivateKey(ReadOnlySpan<byte> x25519PrivateKey)
8686
{
87-
if ((x25519PrivateKey[0] & 0b111) != 0 || // Check that the 3 least significant bits are 0
88-
(x25519PrivateKey[31] & 0x80) != 0 || // Check most significant bit is 0
89-
(x25519PrivateKey[31] & 0x40) != 0x40) // Check second-most significant bit is 1
87+
if ((x25519PrivateKey[0] & 0b111) != 0 || // Check that the 3 least significant bits are set
88+
(x25519PrivateKey[31] & 0x80) != 0 || // Check most significant bit is set
89+
(x25519PrivateKey[31] & 0x40) != 0x40) // Check second most significant bit not set
9090
{
9191
throw new CryptographicException("Invalid X25519 private key: improper bit clamping");
9292
}

Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/Curve25519PrivateKey.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ public override byte[] ExportPkcs8PrivateKey()
6868
/// </remarks>
6969
public override void Clear() => CryptographicOperations.ZeroMemory(_privateKey.Span);
7070

71-
72-
7371
/// <summary>
7472
/// Creates an instance of <see cref="Curve25519PrivateKey"/> from a PKCS#8
7573
/// DER-encoded private key.

Yubico.YubiKey/src/Yubico/YubiKey/Fido2/Commands/BioEnrollmentCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ public BioEnrollmentCommand(
188188
// The pinUvAuthToken is an encrypted value, so there's no need to
189189
// overwrite the array.
190190
byte[] authParam = authProtocol.AuthenticateUsingPinToken(pinUvAuthToken.ToArray(), message);
191-
PinUvAuthParam = new ReadOnlyMemory<byte>(authParam, 0, 16);
191+
PinUvAuthParam = new ReadOnlyMemory<byte>(authParam);
192192
PinUvAuthProtocol = authProtocol.Protocol;
193193
}
194194

Yubico.YubiKey/src/Yubico/YubiKey/Fido2/Commands/ConfigCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ public ConfigCommand(
180180
// The pinUvAuthToken is an encrypted value, so there's no need to
181181
// overwrite the array.
182182
byte[] authParam = authProtocol.AuthenticateUsingPinToken(pinUvAuthToken.ToArray(), message);
183-
PinUvAuthParam = new ReadOnlyMemory<byte>(authParam, 0, 16);
183+
PinUvAuthParam = new ReadOnlyMemory<byte>(authParam);
184184
PinUvAuthProtocol = authProtocol.Protocol;
185185
}
186186

Yubico.YubiKey/src/Yubico/YubiKey/Fido2/Commands/CredentialManagementCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ public CredentialManagementCommand(
184184
// The pinUvAuthToken is an encrypted value, so there's no need to
185185
// overwrite the array.
186186
byte[] authParam = authProtocol.AuthenticateUsingPinToken(pinUvAuthToken.ToArray(), message);
187-
PinUvAuthParam = new ReadOnlyMemory<byte>(authParam, 0, 16);
187+
PinUvAuthParam = new ReadOnlyMemory<byte>(authParam);
188188
PinUvAuthProtocol = authProtocol.Protocol;
189189
}
190190

Yubico.YubiKey/src/Yubico/YubiKey/FirmwareVersion.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,36 @@ public FirmwareVersion(byte major, byte minor = 0, byte patch = 0)
6161
Minor = minor;
6262
Patch = patch;
6363
}
64+
65+
/// <summary>
66+
/// Parse a string of the form "major.minor.patch"
67+
/// </summary>
68+
/// <param name="versionString"></param>
69+
/// <returns>Returns a FirmwareVersion instance</returns>
70+
/// <exception cref="ArgumentNullException"></exception>
71+
/// <exception cref="ArgumentException"></exception>
72+
public static FirmwareVersion Parse(string versionString)
73+
{
74+
if (versionString is null)
75+
{
76+
throw new ArgumentNullException(nameof(versionString));
77+
}
78+
79+
string[] parts = versionString.Split('.');
80+
if (parts.Length != 3)
81+
{
82+
throw new ArgumentException("Must include major.minor.patch", nameof(versionString));
83+
}
84+
85+
if (!byte.TryParse(parts[0], out byte major) ||
86+
!byte.TryParse(parts[1], out byte minor) ||
87+
!byte.TryParse(parts[2], out byte patch))
88+
{
89+
throw new ArgumentException("Major, minor and patch must be valid numbers", nameof(versionString));
90+
}
91+
92+
return new FirmwareVersion(major, minor, patch);
93+
}
6494

6595
public static bool operator >(FirmwareVersion left, FirmwareVersion right)
6696
{

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
}

0 commit comments

Comments
 (0)