Skip to content
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

Feature: Security Domain and SCP11a/b/c features #164

Merged
merged 56 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
6e537c7
initial Security Domain implementation
DennisDyallo Aug 15, 2024
2bd752f
feat(scp11b): Import Key
DennisDyallo Oct 30, 2024
366aa8b
feat(scp): Import Static Keys
DennisDyallo Oct 31, 2024
5e9ec88
feat(scp11ab): Initialize SCP Connection
DennisDyallo Nov 5, 2024
d2d1cf5
feat(scp): remaining SCP Commands implemented
DennisDyallo Nov 6, 2024
4017ae4
docs: updated docstrings and manual
DennisDyallo Nov 7, 2024
093f919
tests, misc: added and fixed tests, misc updates
DennisDyallo Nov 11, 2024
81799ca
feat(scp): supports apps over SCP
DennisDyallo Nov 18, 2024
01b6ab2
docs: add comments to TlvObject
DennisDyallo Nov 27, 2024
c41fff1
docs: fix incorrect values in docs for RSA key sizes and their identi…
DennisDyallo Nov 27, 2024
00cdeae
docs: add docs to ApplicationSession.cs
DennisDyallo Nov 27, 2024
f90fbee
logs: change log level in ConnectionFactory
DennisDyallo Nov 27, 2024
f5fbbbf
misc: return ReadOnlyMemory<byte> in ECPublicKeyParameters.cs
DennisDyallo Nov 27, 2024
a786bef
misc: OtpSession cleanup
DennisDyallo Nov 27, 2024
7bd0b36
misc: ScpApduTransform.cs edits
DennisDyallo Nov 27, 2024
23af160
misc: PivSession.cs edits
DennisDyallo Nov 27, 2024
cd13afd
misc: ExternalAuthenticateResponse.cs edits
DennisDyallo Nov 27, 2024
d9cc85f
misc: InitializeUpdateCommand.cs edits
DennisDyallo Nov 27, 2024
660095d
docs: added docs to PutKeyCommand
DennisDyallo Nov 27, 2024
6f8b74e
docs: adding comments and docs
DennisDyallo Nov 27, 2024
de43147
misc: misc edits
DennisDyallo Nov 27, 2024
ff36d90
tests: test edits
DennisDyallo Nov 27, 2024
d77bc6a
docs, misc: edits in ScpApduTransform
DennisDyallo Nov 27, 2024
11b87d9
misc: edits to StoreDataCommand and ChannelEncryption
DennisDyallo Nov 27, 2024
3b2753d
Merge remote-tracking branch 'origin' into feature/scp11
DennisDyallo Nov 28, 2024
3e04196
tests: minor work on Scp-related tests
DennisDyallo Nov 28, 2024
48e8d8e
misc: minor work on scp related classes
DennisDyallo Nov 28, 2024
e8fbab3
docs: edited docstrings and comments
DennisDyallo Nov 28, 2024
d50a2c9
misc: typos and formatting
DennisDyallo Nov 28, 2024
ab94c6e
Update build-pull-requests.yml
DennisDyallo Nov 28, 2024
0d9bd6d
Update build.yml
DennisDyallo Nov 28, 2024
a9a1429
misc: minor edits (SCP03, SCP11)
DennisDyallo Nov 28, 2024
25a010b
tests: possibility to select by transport
DennisDyallo Nov 29, 2024
57caf29
tests: worked on SCP tests
DennisDyallo Dec 3, 2024
20898f9
misc, docs: Added some docstrings and made small adjustments (SCP)
DennisDyallo Dec 3, 2024
de0c9bc
docs: Improved documentation on SCP11
DennisDyallo Dec 4, 2024
1a5baf4
misc: addressed pr feedback (SCP03, SCP11)
DennisDyallo Dec 10, 2024
688061d
misc: removed curve enforcement in ECKeyParameters
DennisDyallo Dec 11, 2024
761858c
misc: enclose dispose logic in if-block for ScpState.cs
DennisDyallo Dec 11, 2024
63b26da
tests, misc: worked on tests and reliability of ECParameters
DennisDyallo Dec 12, 2024
e3ecd32
tests: made Scp11 tests FIPS compatible
DennisDyallo Dec 12, 2024
75ca1fb
tests: made Scp03Tests FIPS compatible
DennisDyallo Dec 12, 2024
76fb610
misc: fixed error output for TestDeviceSelection
DennisDyallo Dec 12, 2024
1726cb0
misc: minor work on SCP related classes
DennisDyallo Dec 12, 2024
dc52ca4
misc: minor scp related changes
DennisDyallo Dec 15, 2024
8b71556
docs: rewrote secure-channel-protocol.md to account for scp11
DennisDyallo Dec 16, 2024
129e383
misc: renamed some vars and classes to fit GPC specification
DennisDyallo Dec 16, 2024
ead1811
misc: minor SCP work
DennisDyallo Dec 16, 2024
485b626
docs: added documentation for security domain application
DennisDyallo Dec 16, 2024
c3fd1e8
docs: adjusted security domain docs
DennisDyallo Dec 16, 2024
068560a
fixed header style, typos
equijano21 Dec 17, 2024
12021c8
fixed typos
equijano21 Dec 17, 2024
5923b40
Merge pull request #166 from Yubico/docs/scp-edits
DennisDyallo Dec 17, 2024
f80eca7
docs: added Security Domain
DennisDyallo Dec 17, 2024
b814659
Merge branch 'develop' into feature/scp11
DennisDyallo Dec 17, 2024
cf4b0ed
docs:
DennisDyallo Dec 17, 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
6 changes: 5 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ root = true
dotnet_diagnostic.ide0073.severity = suggestion
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.

# ReSharper properties
resharper_csharp_wrap_after_declaration_lpar = true
resharper_csharp_wrap_parameters_style = chop_always


# C# files
[*.cs]
Expand Down Expand Up @@ -357,4 +361,4 @@ dotnet_diagnostic.ca1014.severity = none
# var preferences
csharp_style_var_elsewhere = false:warning
csharp_style_var_for_built_in_types = false:warning
csharp_style_var_when_type_is_apparent = true:warning
csharp_style_var_when_type_is_apparent = true:warning
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public IDisposable BeginTransaction(out bool cardWasReset)
uint result = SCardBeginTransaction(_cardHandle);
_log.SCardApiCall(nameof(SCardBeginTransaction), result);

// Sometime the smart card is left in a state where it needs to be reset prior to beginning
// Sometimes the smart card is left in a state where it needs to be reset prior to beginning
// a transaction. We should automatically handle this case.
if (result == ErrorCode.SCARD_W_RESET_CARD)
{
Expand Down
198 changes: 198 additions & 0 deletions Yubico.Core/src/Yubico/Core/Tlv/TlvObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// 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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.IO;
using System.Linq;

namespace Yubico.Core.Tlv
{
/// <summary>
/// Tag, length, Value structure that helps to parse APDU response data.
/// This class handles BER-TLV encoded data with determinate length.
/// </summary>
public class TlvObject
{
/// <summary>
/// Returns the tag.
/// </summary>
public int Tag { get; }

/// <summary>
/// Returns the value.
/// </summary>
public Memory<byte> Value => _bytes.Skip(_offset).Take(Length).ToArray();

/// <summary>
/// Returns the length of the value.
/// </summary>
public int Length { get; }

private readonly byte[] _bytes;
private readonly int _offset;

/// <summary>
/// Creates a new TLV (Tag-Length-Value) object with the specified tag and value.
/// </summary>
/// <param name="tag">The tag value, must be between 0x00 and 0xFFFF.</param>
/// <param name="value">The value data as a read-only span of bytes.</param>
/// <exception cref="TlvException">Thrown when the tag value is outside the supported range (0x00-0xFFFF).</exception>
/// <remarks>
/// This constructor creates a BER-TLV encoded structure where:
/// - The tag is encoded in the minimum number of bytes needed
/// - The length is encoded according to BER-TLV rules
/// - The value is stored as provided
/// </remarks>
public TlvObject(int tag, ReadOnlySpan<byte> value)
{
// Validate that the tag is within the supported range (0x00-0xFFFF)
if (tag < 0 || tag > 0xFFFF)
{
throw new TlvException(ExceptionMessages.TlvUnsupportedTag);
}

Tag = tag;
// Create a copy of the input value
byte[] valueBuffer = value.ToArray();
using var ms = new MemoryStream();

// Convert tag to bytes and remove leading zeros, maintaining BER-TLV format
byte[] tagBytes = BitConverter.GetBytes(tag).Reverse().SkipWhile(b => b == 0).ToArray();
ms.Write(tagBytes, 0, tagBytes.Length);

Length = valueBuffer.Length;
// For lengths < 128 (0x80), use single byte encoding
if (Length < 0x80)
{
ms.WriteByte((byte)Length);
}
// For lengths >= 128, use multi-byte encoding with length indicator
else
{
// Convert length to bytes and remove leading zeros
byte[] lnBytes = BitConverter.GetBytes(Length).Reverse().SkipWhile(b => b == 0).ToArray();
// Write number of length bytes with high bit set (0x80 | number of bytes)
ms.WriteByte((byte)(0x80 | lnBytes.Length));
// Write the actual length bytes
ms.Write(lnBytes, 0, lnBytes.Length);
}

// Store the position where the value begins
_offset = (int)ms.Position;

// Write the value bytes
ms.Write(valueBuffer, 0, Length);

// Store the complete TLV encoding
_bytes = ms.ToArray();
}

/// <summary>
/// Returns a copy of the Tlv as a BER-TLV encoded byte array.
/// </summary>
public Memory<byte> GetBytes() => _bytes.ToArray();

/// <summary>
/// Parse a Tlv from a BER-TLV encoded byte array.
/// </summary>
/// <param name="data">A byte array containing the TLV encoded data (and nothing more).</param>
/// <returns>The parsed Tlv</returns>
public static TlvObject Parse(ReadOnlySpan<byte> data)
{
ReadOnlySpan<byte> buffer = data;
return ParseFrom(ref buffer);
}

/// <summary>
/// Parses a TLV from a BER-TLV encoded byte array.
/// </summary>
/// <param name="buffer">A byte array containing the TLV encoded data.</param>
/// <returns>The parsed <see cref="TlvObject"/></returns>
/// <remarks>
/// This method will parse a TLV from the given buffer and return the parsed Tlv.
/// The method will consume the buffer as much as necessary to parse the TLV.
/// </remarks>
/// <exception cref="ArgumentException">Thrown if the buffer does not contain a valid TLV.</exception>
internal static TlvObject ParseFrom(ref ReadOnlySpan<byte> buffer)
{
// The first byte of the TLV is the tag.
int tag = buffer[0];

// Determine if the tag is in long form.
// Long form tags have more than one byte, starting with 0x1F.
if ((buffer[0] & 0x1F) == 0x1F)
{
// Ensure there is enough data for a long form tag.
if (buffer.Length < 2)
{
throw new ArgumentException("Insufficient data for long form tag");
}
// Combine the first two bytes to form the tag.
tag = (buffer[0] << 8) | buffer[1];
buffer = buffer[2..]; // Skip the tag bytes
}
else
{
buffer = buffer[1..]; // Skip the tag byte
}

if (buffer.Length < 1)
{
throw new ArgumentException("Insufficient data for length");
}

// Read the length of the TLV value.
int length = buffer[0];
buffer = buffer[1..];

// If the length is more than one byte, process remaining bytes.
if (length > 0x80)
{
int lengthLn = length - 0x80;
length = 0;
for (int i = 0; i < lengthLn; i++)
{
length = (length << 8) | buffer[0];
buffer = buffer[1..];
}
}

ReadOnlySpan<byte> value = buffer[..length];
buffer = buffer[length..]; // Advance the buffer to the end of the value

return new TlvObject(tag, value);
}

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <remarks>
/// The string is of the form <c>Tlv(0xTAG, LENGTH, VALUE)</c>.
/// <para>
/// The tag is written out in hexadecimal, prefixed by 0x.
/// The length is written out in decimal.
/// The value is written out in hexadecimal.
/// </para>
/// </remarks>
public override string ToString()
{
#if NETSTANDARD2_1_OR_GREATER
return $"Tlv(0x{Tag:X}, {Length}, {BitConverter.ToString(Value.ToArray()).Replace("-", "", StringComparison.Ordinal)})";
#else
return $"Tlv(0x{Tag:X}, {Length}, {BitConverter.ToString(Value.ToArray()).Replace("-", "")})";
#endif
}
}
}
97 changes: 97 additions & 0 deletions Yubico.Core/src/Yubico/Core/Tlv/TlvObjects.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.IO;

namespace Yubico.Core.Tlv
{
/// <summary>
/// Utility methods to encode and decode BER-TLV data.
/// </summary>
public static class TlvObjects
{
/// <summary>
/// Decodes a sequence of BER-TLV encoded data into a list of Tlvs.
/// </summary>
/// <param name="data">Sequence of TLV encoded data</param>
/// <returns>List of <see cref="TlvObject"/></returns>
public static IReadOnlyList<TlvObject> DecodeList(ReadOnlySpan<byte> data)
{
var tlvs = new List<TlvObject>();
ReadOnlySpan<byte> buffer = data;
while (!buffer.IsEmpty)
{
var tlv = TlvObject.ParseFrom(ref buffer);
tlvs.Add(tlv);
}

return tlvs.AsReadOnly();
}

/// <summary>
/// Decodes a sequence of BER-TLV encoded data into a mapping of Tag-Value pairs.
/// Iteration order is preserved. If the same tag occurs more than once only the latest will be kept.
/// </summary>
/// <param name="data">Sequence of TLV encoded data</param>
/// <returns>Dictionary of Tag-Value pairs</returns>
public static IReadOnlyDictionary<int, ReadOnlyMemory<byte>> DecodeMap(ReadOnlySpan<byte> data)
{
var tlvs = new Dictionary<int, ReadOnlyMemory<byte>>();
ReadOnlySpan<byte> buffer = data;
while (!buffer.IsEmpty)
{
var tlv = TlvObject.ParseFrom(ref buffer);
tlvs[tlv.Tag] = tlv.Value;
}

return tlvs;
}

/// <summary>
/// Encodes a list of Tlvs into a sequence of BER-TLV encoded data.
/// </summary>
/// <param name="list">List of Tlvs to encode</param>
/// <returns>BER-TLV encoded list</returns>
public static byte[] EncodeList(IEnumerable<TlvObject> list)
{
if (list is null)
{
throw new ArgumentNullException(nameof(list));
}

using var stream = new MemoryStream();
using var writer = new BinaryWriter(stream);
foreach (TlvObject? tlv in list)
{
ReadOnlyMemory<byte> bytes = tlv.GetBytes();
writer.Write(bytes.Span.ToArray());
}

return stream.ToArray();
}

/// <summary>
/// Encodes an array of Tlvs into a sequence of BER-TLV encoded data.
/// </summary>
/// <param name="tlvs">Array of Tlvs to encode</param>
/// <returns>BER-TLV encoded array</returns>
public static byte[] EncodeMany(params TlvObject[] tlvs) => EncodeList(tlvs);

/// <summary>
/// Decode a single TLV encoded object, returning only the value.
/// </summary>
/// <param name="expectedTag">The expected tag value of the given TLV data</param>
/// <param name="tlvData">The TLV data</param>
/// <returns>The value of the TLV</returns>
/// <exception cref="InvalidOperationException">If the TLV tag differs from expectedTag</exception>
public static Memory<byte> UnpackValue(int expectedTag, ReadOnlySpan<byte> tlvData)
{
var tlv = TlvObject.Parse(tlvData);
if (tlv.Tag != expectedTag)
{
throw new InvalidOperationException($"Expected tag: {expectedTag:X2}, got {tlv.Tag:X2}");
}

return tlv.Value.ToArray();
}
}
}
Loading
Loading