Skip to content

Add support for large RSA keys (3072, 4096) and integrationtest allow-list of valid Yubikeys serials #100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f60eee2
Reformatting and small refactor
DennisDyallo May 31, 2024
598555e
Adding larger RSA keys and tests
DennisDyallo Jun 3, 2024
352bf4c
Add allow list for yubikeys in env variables
DennisDyallo Jun 3, 2024
de61810
Temporary store work
DennisDyallo Jun 5, 2024
9aa549f
Ignore rules on test project
DennisDyallo Jun 10, 2024
f0c471f
Refactor tests, add feature flag block of attempt invalid keysize
DennisDyallo Jun 10, 2024
c84110c
Revert change
DennisDyallo Jun 11, 2024
68c4a24
Undo this. notneeded
DennisDyallo Jun 11, 2024
aea8e4a
Handle in different branch
DennisDyallo Jun 11, 2024
544dca9
Add data
DennisDyallo Jun 11, 2024
096f3d8
Reuse common method, remove unsuitable tests, add tests
DennisDyallo Jun 11, 2024
d3bacae
Add RSA3072, RSA4096
DennisDyallo Jun 11, 2024
7c40924
Refactor
DennisDyallo Jun 11, 2024
55488d6
Reorder
DennisDyallo Jun 11, 2024
fe78afc
Addressed PR review
DennisDyallo Jun 11, 2024
e715725
Merge branch 'develop' into feature/large-rsa-keys
DennisDyallo Jun 11, 2024
9ba4bf2
Dotnet format
DennisDyallo Jun 11, 2024
da9014c
Refactor
DennisDyallo Jun 11, 2024
99b19f0
Reformat
DennisDyallo Jun 11, 2024
2befac0
Reorder
DennisDyallo Jun 11, 2024
d54cf7e
Clarification with docs
DennisDyallo Jun 12, 2024
a5ce820
CLRF
DennisDyallo Jun 12, 2024
06f90b0
Address PR feedback
DennisDyallo Jun 13, 2024
e21a090
Removing unused test data
DennisDyallo Jun 14, 2024
a0b87a9
Addressed PR review
DennisDyallo Jun 14, 2024
e4c7510
Clarified the usage
DennisDyallo Jun 14, 2024
b15b87b
Fix failing unit test
DennisDyallo Jun 14, 2024
9d57e3c
Dotnet format
DennisDyallo Jun 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
root = true
# Copyright 2021 Yubico AB
# Copyright 2024 Yubico AB
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
Expand All @@ -19,7 +19,7 @@ root = true

# IDE0073: File header
dotnet_diagnostic.ide0073.severity = suggestion
file_header_template = Copyright 2023 Yubico AB\n\nLicensed under the Apache License, Version 2.0 (the "License").\nYou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an "AS IS" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.
file_header_template = Copyright 2024 Yubico AB\n\nLicensed under the Apache License, Version 2.0 (the "License").\nYou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an "AS IS" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.


# C# files
Expand Down Expand Up @@ -332,6 +332,11 @@ dotnet_diagnostic.ca2208.severity = none
#

[**/tests/**/*.cs]
# var preferences
csharp_style_var_elsewhere = false:none
csharp_style_var_for_built_in_types = false:none
csharp_style_var_when_type_is_apparent = false:none


# CA1707: Identifiers should not contain underscores
dotnet_diagnostic.ca1707.severity = none
Expand Down
45 changes: 45 additions & 0 deletions Yubico.Core/tests/Yubico/Core/Tlv/TlvWriterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@
using System.Linq;
using System.Text;
using Xunit;
using Xunit.Abstractions;

namespace Yubico.Core.Tlv.UnitTests
{
public class TlvWriterTests
{
private readonly ITestOutputHelper _testOutputHelper;

public TlvWriterTests(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
}

[Theory]
[InlineData(0x9B)]
[InlineData(0x5F11)]
Expand Down Expand Up @@ -451,5 +459,42 @@ public void Encode_IncompleteSchema_ThrowsException()
#pragma warning restore CS8625, CA1806, IDE0039, IDE0058 // Cannot convert null literal to non-nullable reference type.
}
}

[Fact]
public void EncodeTag_Has_Correct_LengthAndOffset()
{
_testOutputHelper.WriteLine("Output: ");
int bytesSize = 128;
while (bytesSize < 150000)
{
var writer = new TlvWriter();
var data = new byte[bytesSize];

// My visible data
data[0] = 0xFF;

writer.WriteValue(0x1, data);
var encoding = writer.Encode();
var reader = new TlvReader(encoding);
Assert.Equal(bytesSize, reader.PeekLength());

var bytesAsHex = encoding[..9].Select(b => b.ToString("X2"));
PrettyPrint(bytesSize, bytesAsHex);

// Increase for next round
bytesSize *= 2;
}
}

private void PrettyPrint(int number, IEnumerable<string> bytesAsHex)
{
// Print the contents
int numDigits = number.ToString().Length;
int spacesNeeded = Math.Max(5 - numDigits, 0);
string padding = new string(' ', spacesNeeded);
_testOutputHelper.WriteLine(
$"{padding + number}: {string.Join(",", bytesAsHex)}"
);
}
}
}
1 change: 1 addition & 0 deletions Yubico.NET.SDK.sln
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
LICENSE.txt = LICENSE.txt
nginx.conf = nginx.conf
README.md = README.md
global.json = global.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{8FFE6A2D-4DC1-4CC0-8B96-E6CE11F29B3E}"
Expand Down
61 changes: 45 additions & 16 deletions Yubico.YubiKey/src/Yubico/YubiKey/Cryptography/RsaFormat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,22 @@ public static class RsaFormat
/// </summary>
public const int KeySizeBits2048 = 2048;

/// <summary>
/// Use this value to indicate the key size, in bits, is 3072. The
/// <c>KeySizeBits</c> values listed in this class are the sizes
/// supported and provided as a convenience to the user to verify the
/// supported sizes.
/// </summary>
public const int KeySizeBits3072 = 3072;

/// <summary>
/// Use this value to indicate the key size, in bits, is 4096. The
/// <c>KeySizeBits</c> values listed in this class are the sizes
/// supported and provided as a convenience to the user to verify the
/// supported sizes.
/// </summary>
public const int KeySizeBits4096 = 4096;

/// <summary>
/// Use this value to indicate the digest algorithm is SHA-1.
/// </summary>
Expand Down Expand Up @@ -310,16 +326,19 @@ public static bool TryParsePkcs1Verify(ReadOnlySpan<byte> formattedSignature,
{
var tlvReader = new TlvReader(digestInfo);

isValid = TryReadDer(true, ReadNestedNoMoreData, SequenceTag, tlvReader, out TlvReader infoReader,
isValid = TryReadDer(
true, ReadNestedNoMoreData, SequenceTag, tlvReader, out TlvReader infoReader,
out _);

isValid = TryReadDer(isValid, ReadNested, SequenceTag, infoReader, out TlvReader oidReader, out _);
isValid = TryReadDer(isValid, ReadValue, OidTag, oidReader, out _, out ReadOnlyMemory<byte> oid);

isValid = TryReadDer(isValid, ReadValueNoMoreData, NullTag, oidReader, out _,
isValid = TryReadDer(
isValid, ReadValueNoMoreData, NullTag, oidReader, out _,
out ReadOnlyMemory<byte> oidParams);

isValid = TryReadDer(isValid, ReadValueNoMoreData, OctetTag, infoReader, out _,
isValid = TryReadDer(
isValid, ReadValueNoMoreData, OctetTag, infoReader, out _,
out ReadOnlyMemory<byte> digestData);

isValid = TryParseOid(isValid, oid, oidParams, digestData, out digestAlgorithm);
Expand Down Expand Up @@ -1121,11 +1140,13 @@ public static byte[] FormatPkcs1Oaep(ReadOnlySpan<byte> inputData, int digestAlg
inputData.CopyTo(bufferAsSpan[(buffer.Length - inputData.Length)..]);

// Use the seed to mask the DB.
PerformMgf1(buffer, 1, digestLength, buffer, digestLength + 1, buffer.Length - (digestLength + 1),
PerformMgf1(
buffer, 1, digestLength, buffer, digestLength + 1, buffer.Length - (digestLength + 1),
digester);

// Use the masked DB to mask the seed.
PerformMgf1(buffer, digestLength + 1, buffer.Length - (digestLength + 1), buffer, 1, digestLength,
PerformMgf1(
buffer, digestLength + 1, buffer.Length - (digestLength + 1), buffer, 1, digestLength,
digester);

return buffer;
Expand Down Expand Up @@ -1245,11 +1266,13 @@ public static bool TryParsePkcs1Oaep(ReadOnlySpan<byte> formattedData,
try
{
// Use the masked DB to unmask the seed.
PerformMgf1(buffer, digestLength + 1, buffer.Length - (digestLength + 1), buffer, 1, digestLength,
PerformMgf1(
buffer, digestLength + 1, buffer.Length - (digestLength + 1), buffer, 1, digestLength,
digester);

// Use the seed to unmask the DB.
PerformMgf1(buffer, 1, digestLength, buffer, digestLength + 1, buffer.Length - (digestLength + 1),
PerformMgf1(
buffer, 1, digestLength, buffer, digestLength + 1, buffer.Length - (digestLength + 1),
digester);

// Verify the DB
Expand Down Expand Up @@ -1568,16 +1591,22 @@ private static void PerformMgf1(byte[] seed,
}
}

private static byte[] GetKeySizeBuffer(int keySizeBits) =>
keySizeBits switch
private static byte[] GetKeySizeBuffer(int keySizeBits)
{
switch (keySizeBits)
{
KeySizeBits1024 => new byte[KeySizeBits1024 / 8],
KeySizeBits2048 => new byte[KeySizeBits2048 / 8],
_ => throw new ArgumentException(
string.Format(
CultureInfo.CurrentCulture,
ExceptionMessages.IncorrectRsaKeyLength)),
};
case KeySizeBits1024:
case KeySizeBits2048:
case KeySizeBits3072:
case KeySizeBits4096:
return new byte[keySizeBits / 8];
default:
throw new ArgumentException(
string.Format(
CultureInfo.CurrentCulture,
ExceptionMessages.IncorrectRsaKeyLength));
}
}

private static HashAlgorithm GetHashAlgorithm(int digestAlgorithm) =>
digestAlgorithm switch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;
using Yubico.Core.Iso7816;

namespace Yubico.YubiKey.Management.Commands
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

using Yubico.Core.Iso7816;


namespace Yubico.YubiKey.Otp.Commands
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using Yubico.Core.Iso7816;

namespace Yubico.YubiKey.Piv.Commands
Expand Down Expand Up @@ -90,6 +87,8 @@ public sealed class AuthenticateDecryptCommand : AuthenticateCommand, IYubiKeyCo

private const int Rsa1024BlockSize = 128;
private const int Rsa2048BlockSize = 256;
private const int Rsa3072BlockSize = 384;
private const int Rsa4096BlockSize = 512;

// The default constructor explicitly defined. We don't want it to be
// used.
Expand Down Expand Up @@ -133,6 +132,8 @@ public AuthenticateDecryptCommand(ReadOnlyMemory<byte> dataToDecrypt, byte slotN
{
Rsa1024BlockSize => PivAlgorithm.Rsa1024,
Rsa2048BlockSize => PivAlgorithm.Rsa2048,
Rsa3072BlockSize => PivAlgorithm.Rsa3072,
Rsa4096BlockSize => PivAlgorithm.Rsa4096,
_ => throw new ArgumentException(
string.Format(
CultureInfo.CurrentCulture,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,8 @@
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using Yubico.Core.Iso7816;
using Yubico.Core.Tlv;

namespace Yubico.YubiKey.Piv.Commands
{
Expand Down Expand Up @@ -96,6 +92,8 @@ public sealed class AuthenticateSignCommand : AuthenticateCommand, IYubiKeyComma

private const int Rsa1024DigestDataLength = 128;
private const int Rsa2048DigestDataLength = 256;
private const int Rsa3072DigestDataLength = 384;
private const int Rsa4096DigestDataLength = 512;
private const int EccP256DigestDataLength = 32;
private const int EccP384DigestDataLength = 48;

Expand Down Expand Up @@ -168,14 +166,16 @@ public AuthenticateSignCommand(ReadOnlyMemory<byte> digestData, byte slotNumber)
SlotNumber = slotNumber;

// Determine the algorithm based on the length of the digest data.
// Currently the length of the data must be 128 (RSA-1024), 256
// (RSA-2048), 32 (ECC-P256), or 48 (ECC-P384).
// Currently, the length of the data must be 128 (RSA-1024), 256
// (RSA-2048), 384 (RSA-3072), 512 (RSA-4096), 32 (ECC-P256), or 48 (ECC-P384).
// Return the PivAlgorithm, or if the length is not supported, throw an
// exception.
Algorithm = digestData.Length switch
{
Rsa1024DigestDataLength => PivAlgorithm.Rsa1024,
Rsa2048DigestDataLength => PivAlgorithm.Rsa2048,
Rsa3072DigestDataLength => PivAlgorithm.Rsa3072,
Rsa4096DigestDataLength => PivAlgorithm.Rsa4096,
EccP256DigestDataLength => PivAlgorithm.EccP256,
EccP384DigestDataLength => PivAlgorithm.EccP384,
_ => throw new ArgumentException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Yubico.Core.Iso7816;

namespace Yubico.YubiKey.Piv.Commands
Expand Down Expand Up @@ -89,15 +87,15 @@ public sealed class GenerateKeyPairCommand : IYubiKeyCommand<GenerateKeyPairResp
// Create one List with all three elements, then remove the PIN and/or
// touch policy bytes if one or both are default.
// These are the indices where the actual values go.
private const int indexValueLength = 1;
private const int algorithmCount = 3;
private const int indexAlgorithmByte = 4;
private const int indexPinPolicy = 5;
private const int indexPinPolicyByte = 7;
private const int pinPolicyCount = 3;
private const int indexTouchPolicy = 8;
private const int indexTouchPolicyByte = 10;
private const int touchPolicyCount = 3;
private const int IndexValueLength = 1;
private const int AlgorithmCount = 3;
private const int IndexAlgorithmByte = 4;
private const int IndexPinPolicy = 5;
private const int IndexPinPolicyByte = 7;
private const int PinPolicyCount = 3;
private const int IndexTouchPolicy = 8;
private const int IndexTouchPolicyByte = 10;
private const int TouchPolicyCount = 3;

/// <summary>
/// Gets the YubiKeyApplication to which this command belongs. For this
Expand Down Expand Up @@ -286,29 +284,28 @@ private byte[] BuildGenerateKeyPairApduData()
ExceptionMessages.InvalidAlgorithm));
}

byte[] data = new byte[]
{
byte[] data = {
0xAC, 0x09, 0x80, 0x01, 0x00, 0xAA, 0x01, 0x00, 0xAB, 0x01, 0x00
};
data[indexAlgorithmByte] = (byte)Algorithm;
data[indexTouchPolicyByte] = (byte)TouchPolicy;
data[indexPinPolicyByte] = (byte)PinPolicy;
data[IndexAlgorithmByte] = (byte)Algorithm;
data[IndexTouchPolicyByte] = (byte)TouchPolicy;
data[IndexPinPolicyByte] = (byte)PinPolicy;
int length = data.Length;
int valueLength = algorithmCount + pinPolicyCount + touchPolicyCount;
int valueLength = AlgorithmCount + PinPolicyCount + TouchPolicyCount;

if (PinPolicy == PivPinPolicy.Default || PinPolicy == PivPinPolicy.None)
{
Array.Copy(data, indexTouchPolicy, data, indexPinPolicy, touchPolicyCount);
length -= pinPolicyCount;
valueLength -= pinPolicyCount;
Array.Copy(data, IndexTouchPolicy, data, IndexPinPolicy, TouchPolicyCount);
length -= PinPolicyCount;
valueLength -= PinPolicyCount;
}
if (TouchPolicy == PivTouchPolicy.Default || TouchPolicy == PivTouchPolicy.None)
{
length -= touchPolicyCount;
valueLength -= touchPolicyCount;
length -= TouchPolicyCount;
valueLength -= TouchPolicyCount;
}

data[indexValueLength] = (byte)valueLength;
data[IndexValueLength] = (byte)valueLength;
Span<byte> returnValue = data.AsSpan(0, length);
return returnValue.ToArray();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@
// limitations under the License.

using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Yubico.Core.Iso7816;

namespace Yubico.YubiKey.Piv.Commands
Expand Down
Loading