Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers.Binary;

namespace System.Net.Security
{
internal static class TlsSignatureAlgorithmHelper
{
private const ushort SignatureAlgorithmsExtension = 13;

internal static bool TryGetFamiliesFromClientHello(
ReadOnlySpan<byte> clientHello,
out TlsSignatureAlgorithmFamilies signatureAlgorithmFamilies)
{
// https://www.rfc-editor.org/rfc/rfc8446.html#section-4.1.2
const int HandshakeHeaderLength = 4;
const int ClientHelloFixedLength = 2 + 32;

signatureAlgorithmFamilies = TlsSignatureAlgorithmFamilies.None;

if (clientHello.Length < HandshakeHeaderLength + ClientHelloFixedLength ||
clientHello[0] != 1)
{
return false;
}

int helloLength = (clientHello[1] << 16) | (clientHello[2] << 8) | clientHello[3];
if (helloLength != clientHello.Length - HandshakeHeaderLength)
{
return false;
}

ReadOnlySpan<byte> hello = clientHello.Slice(HandshakeHeaderLength + ClientHelloFixedLength);
if (!TrySkipOpaque1(ref hello) ||
!TrySkipOpaque2(ref hello) ||
!TrySkipOpaque1(ref hello))
{
return false;
}

if (hello.IsEmpty)
{
return true;
}

if (hello.Length < sizeof(ushort))
{
return false;
}

int extensionsLength = BinaryPrimitives.ReadUInt16BigEndian(hello);
hello = hello.Slice(sizeof(ushort));
if (extensionsLength != hello.Length)
{
return false;
}

while (hello.Length >= 2 * sizeof(ushort))
{
ushort extensionType = BinaryPrimitives.ReadUInt16BigEndian(hello);
int extensionLength = BinaryPrimitives.ReadUInt16BigEndian(hello.Slice(sizeof(ushort)));
hello = hello.Slice(2 * sizeof(ushort));

if (extensionLength > hello.Length)
{
return false;
}

if (extensionType == SignatureAlgorithmsExtension)
{
return TryGetFamiliesFromExtension(
hello.Slice(0, extensionLength),
out signatureAlgorithmFamilies);
}

hello = hello.Slice(extensionLength);
}

return hello.IsEmpty;
}

internal static bool TryGetFamiliesFromExtension(
ReadOnlySpan<byte> extensionData,
out TlsSignatureAlgorithmFamilies signatureAlgorithmFamilies)
{
// https://www.rfc-editor.org/rfc/rfc8446.html#section-4.2.3
signatureAlgorithmFamilies = TlsSignatureAlgorithmFamilies.None;

if (extensionData.Length < sizeof(ushort))
{
return false;
}

int signatureAlgorithmsLength = BinaryPrimitives.ReadUInt16BigEndian(extensionData);
ReadOnlySpan<byte> signatureAlgorithms = extensionData.Slice(sizeof(ushort));

if (signatureAlgorithmsLength == 0 ||
signatureAlgorithmsLength != signatureAlgorithms.Length ||
(signatureAlgorithmsLength & 1) != 0)
{
return false;
}

while (!signatureAlgorithms.IsEmpty)
{
ushort signatureScheme = BinaryPrimitives.ReadUInt16BigEndian(signatureAlgorithms);
signatureAlgorithms = signatureAlgorithms.Slice(sizeof(ushort));

signatureAlgorithmFamilies |= signatureScheme switch
{
0x0201 or // rsa_pkcs1_sha1
0x0401 or // rsa_pkcs1_sha256
0x0501 or // rsa_pkcs1_sha384
0x0601 or // rsa_pkcs1_sha512
0x0804 or // rsa_pss_rsae_sha256
0x0805 or // rsa_pss_rsae_sha384
0x0806 or // rsa_pss_rsae_sha512
0x0809 or // rsa_pss_pss_sha256
0x080A or // rsa_pss_pss_sha384
0x080B => // rsa_pss_pss_sha512
TlsSignatureAlgorithmFamilies.Rsa,

0x0203 or // ecdsa_sha1
0x0403 or // ecdsa_secp256r1_sha256
0x0503 or // ecdsa_secp384r1_sha384
0x0603 or // ecdsa_secp521r1_sha512
0x081A or // ecdsa_brainpoolP256r1tls13_sha256
0x081B or // ecdsa_brainpoolP384r1tls13_sha384
0x081C => // ecdsa_brainpoolP512r1tls13_sha512
TlsSignatureAlgorithmFamilies.ECDsa,

0x0807 or // ed25519
0x0808 => // ed448
TlsSignatureAlgorithmFamilies.EdDsa,

0x0904 or // mldsa44
0x0905 or // mldsa65
0x0906 => // mldsa87
TlsSignatureAlgorithmFamilies.MLDsa,

>= 0x0911 and <= 0x091C =>
TlsSignatureAlgorithmFamilies.SlhDsa,

_ => TlsSignatureAlgorithmFamilies.None,
};
}

return true;
}

private static bool TrySkipOpaque1(ref ReadOnlySpan<byte> data)
{
if (data.IsEmpty || data.Length < data[0] + 1)
{
return false;
}

data = data.Slice(data[0] + 1);
return true;
}

private static bool TrySkipOpaque2(ref ReadOnlySpan<byte> data)
{
if (data.Length < sizeof(ushort))
{
return false;
}

int length = BinaryPrimitives.ReadUInt16BigEndian(data);
if (data.Length < sizeof(ushort) + length)
{
return false;
}

data = data.Slice(sizeof(ushort) + length);
return true;
}
}
}
1 change: 1 addition & 0 deletions src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<Compile Include="$(CommonPath)System\Net\Security\TlsAlertMessage.cs" Link="Common\System\Net\Security\TlsAlertMessage.cs" />
<Compile Include="$(CommonPath)System\HexConverter.cs" Link="Common\System\HexConverter.cs" />
<Compile Include="$(CommonPath)System\Net\Security\TargetHostNameHelper.cs" Link="Common\System\Net\Security\TargetHostNameHelper.cs" />
<Compile Include="$(CommonPath)System\Net\Security\TlsSignatureAlgorithmHelper.cs" Link="Common\System\Net\Security\TlsSignatureAlgorithmHelper.cs" />
<Compile Include="$(CommonPath)System\Net\Security\SslKeyLogger.cs" Link="Common\System\Net\Security\SslKeyLogger.cs" />
<!-- IP parser -->
<Compile Include="$(CommonPath)System\Net\IPv4AddressHelper.Common.cs" Link="System\Net\IPv4AddressHelper.Common.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,17 @@ private unsafe int HandleEventNewConnection(ref NEW_CONNECTION_DATA data)
NetEventSource.Info(this, $"{this} New inbound connection {connection}.");
}

SslClientHelloInfo clientHello = new SslClientHelloInfo(data.Info->ServerNameLength > 0 ? Encoding.UTF8.GetString((byte*)data.Info->ServerName, data.Info->ServerNameLength) : "", SslProtocols.Tls13);
ReadOnlySpan<byte> cryptoBuffer = new ReadOnlySpan<byte>(
data.Info->CryptoBuffer,
checked((int)data.Info->CryptoBufferLength));
TlsSignatureAlgorithmHelper.TryGetFamiliesFromClientHello(
cryptoBuffer,
out TlsSignatureAlgorithmFamilies signatureAlgorithmFamilies);

SslClientHelloInfo clientHello = new SslClientHelloInfo(
data.Info->ServerNameLength > 0 ? Encoding.UTF8.GetString((byte*)data.Info->ServerName, data.Info->ServerNameLength) : "",
SslProtocols.Tls13,
signatureAlgorithmFamilies);

// Kicks off the rest of the handshake in the background, the process itself will enqueue the result in the accept queue.
StartConnectionHandshake(connection, clientHello);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,26 @@ public async Task AcceptConnectionAsync_ClientCancels_FiresOptionCallbackCancell
Assert.Equal(SR.Format(SR.net_quic_handshake_timeout, clientOptions.HandshakeTimeout), exception.Message);
}

[Fact]
public async Task ConnectionOptionsCallback_ReceivesSignatureAlgorithmFamilies()
{
TlsSignatureAlgorithmFamilies observedSignatureAlgorithmFamilies = TlsSignatureAlgorithmFamilies.None;
QuicListenerOptions listenerOptions = CreateQuicListenerOptions();
listenerOptions.ConnectionOptionsCallback = (_, hello, _) =>
{
observedSignatureAlgorithmFamilies = hello.SignatureAlgorithmFamilies;
return ValueTask.FromResult(CreateQuicServerOptions());
};

(QuicConnection clientConnection, QuicConnection serverConnection) =
await CreateConnectedQuicConnection(clientOptions: null, listenerOptions);
await using (clientConnection)
await using (serverConnection)
{
Assert.True((observedSignatureAlgorithmFamilies & TlsSignatureAlgorithmFamilies.Rsa) != 0);
}
}

[Fact]
public async Task AcceptConnectionAsync_ListenerDisposed_Throws()
{
Expand Down
12 changes: 12 additions & 0 deletions src/libraries/System.Net.Security/ref/System.Net.Security.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,9 @@ public readonly partial struct SslClientHelloInfo
private readonly object _dummy;
private readonly int _dummyPrimitive;
public SslClientHelloInfo(string serverName, System.Security.Authentication.SslProtocols sslProtocols) { throw null; }
public SslClientHelloInfo(string serverName, System.Security.Authentication.SslProtocols sslProtocols, System.Net.Security.TlsSignatureAlgorithmFamilies signatureAlgorithmFamilies) { throw null; }
public string ServerName { get { throw null; } }
public System.Net.Security.TlsSignatureAlgorithmFamilies SignatureAlgorithmFamilies { get { throw null; } }
public System.Security.Authentication.SslProtocols SslProtocols { get { throw null; } }
}
public partial class SslServerAuthenticationOptions
Expand Down Expand Up @@ -742,6 +744,16 @@ public TlsBufferSession() { }
public System.Net.Security.TlsOperationStatus DrainPendingOutput(System.Span<byte> destination, out int bytesWritten) { throw null; }
public System.Net.Security.TlsOperationStatus RequestClientCertificate(System.Span<byte> destination, out int bytesWritten) { throw null; }
}
[System.FlagsAttribute]
public enum TlsSignatureAlgorithmFamilies
{
None = 0,
Rsa = 1,
ECDsa = 2,
EdDsa = 4,
MLDsa = 8,
SlhDsa = 16,
}
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5007", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public sealed partial class TlsSocketSession : System.Net.Security.TlsSession
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@
<Compile Include="System\Net\Security\StreamSizes.cs" />
<Compile Include="System\Net\Security\TlsAlertType.cs" />
<Compile Include="System\Net\Security\TlsFrameHelper.cs" />
<Compile Include="$(CommonPath)System\Net\Security\TlsSignatureAlgorithmHelper.cs"
Link="Common\System\Net\Security\TlsSignatureAlgorithmHelper.cs" />
<!-- NegotiateStream -->
<Compile Include="System\Net\SecurityStatusPal.cs" />
<Compile Include="System\Net\StreamFramer.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,43 @@

namespace System.Net.Security
{
/// <summary>
/// Specifies families of TLS signature algorithms.
/// </summary>
[Flags]
public enum TlsSignatureAlgorithmFamilies
{
/// <summary>
/// No signature algorithm family is specified.
/// </summary>
None = 0,

/// <summary>
/// The RSA signature algorithm family.
/// </summary>
Rsa = 1 << 0,

/// <summary>
/// The ECDSA signature algorithm family.
/// </summary>
ECDsa = 1 << 1,

/// <summary>
/// The EdDSA signature algorithm family.
/// </summary>
EdDsa = 1 << 2,

/// <summary>
/// The ML-DSA signature algorithm family.
/// </summary>
MLDsa = 1 << 3,

/// <summary>
/// The SLH-DSA signature algorithm family.
/// </summary>
SlhDsa = 1 << 4,
}

/// <summary>
/// This struct contains information from received TLS Client Hello frame.
/// </summary>
Expand All @@ -13,10 +50,36 @@ public readonly struct SslClientHelloInfo
public readonly string ServerName { get; }
public readonly SslProtocols SslProtocols { get; }

/// <summary>
/// Gets the signature algorithm families advertised by the client.
/// </summary>
/// <remarks>
/// This property indicates broad compatibility with a server certificate's public key.
/// It does not guarantee that every certificate or certificate chain using an advertised
/// family will be accepted by the client. The value is <see cref="TlsSignatureAlgorithmFamilies.None"/>
/// when this information is unavailable or the client advertises no recognized family.
/// </remarks>
public readonly TlsSignatureAlgorithmFamilies SignatureAlgorithmFamilies { get; }

public SslClientHelloInfo(string serverName, SslProtocols sslProtocols)
: this(serverName, sslProtocols, TlsSignatureAlgorithmFamilies.None)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SslClientHelloInfo"/> struct.
/// </summary>
/// <param name="serverName">The server name requested by the client.</param>
/// <param name="sslProtocols">A bitwise combination of the enumeration values that specifies the TLS protocols advertised by the client.</param>
/// <param name="signatureAlgorithmFamilies">A bitwise combination of the enumeration values that specifies the signature algorithm families advertised by the client.</param>
public SslClientHelloInfo(
string serverName,
SslProtocols sslProtocols,
TlsSignatureAlgorithmFamilies signatureAlgorithmFamilies)
{
ServerName = serverName;
SslProtocols = sslProtocols;
SignatureAlgorithmFamilies = signatureAlgorithmFamilies;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,9 @@ private async ValueTask<int> ReceiveHandshakeFrameAsync<TIOAdapter>(Cancellation

if (_sslAuthenticationOptions.ServerOptionDelegate != null)
{
// We need to process supported versions extension to pass it to user callback.
options |= TlsFrameHelper.ProcessingOptions.Versions;
// Process ClientHello information exposed to the user callback.
options |= TlsFrameHelper.ProcessingOptions.Versions |
TlsFrameHelper.ProcessingOptions.SignatureAlgorithms;
}

// Process SNI from Client Hello message
Expand All @@ -508,7 +509,10 @@ private async ValueTask<int> ReceiveHandshakeFrameAsync<TIOAdapter>(Cancellation
if (_sslAuthenticationOptions.ServerOptionDelegate != null)
{
SslServerAuthenticationOptions userOptions =
await _sslAuthenticationOptions.ServerOptionDelegate(this, new SslClientHelloInfo(_sslAuthenticationOptions.TargetHost, _lastFrame.SupportedVersions),
await _sslAuthenticationOptions.ServerOptionDelegate(this, new SslClientHelloInfo(
_sslAuthenticationOptions.TargetHost,
_lastFrame.SupportedVersions,
_lastFrame.SignatureAlgorithmFamilies),
_sslAuthenticationOptions.UserState, cancellationToken).ConfigureAwait(false);
_sslAuthenticationOptions.UpdateOptions(userOptions);
}
Expand Down
Loading
Loading