Skip to content
Merged
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
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.ExceptionServices;
Expand All @@ -24,6 +23,9 @@ public partial class SslStream
private object _handshakeLock => _sslAuthenticationOptions;
private volatile TaskCompletionSource<bool>? _handshakeWaiter;

private const int HandshakeTypeOffsetSsl2 = 2; // Offset of HelloType in Sslv2 and Unified frames
private const int HandshakeTypeOffsetTls = 5; // Offset of HelloType in Sslv3 and TLS frames

private bool _receivedEOF;

// Used by Telemetry to ensure we log connection close exactly once
Expand Down Expand Up @@ -376,8 +378,10 @@ private async ValueTask<ProtocolToken> ReceiveBlobAsync<TIOAdapter>(Cancellation
}
break;
case TlsContentType.Handshake:
if (!_isRenego && _buffer.EncryptedReadOnlySpan[TlsFrameHelper.HeaderSize] == (byte)TlsHandshakeType.ClientHello &&
#pragma warning disable CS0618
if (!_isRenego && _buffer.EncryptedReadOnlySpan[_lastFrame.Header.Version == SslProtocols.Ssl2 ? HandshakeTypeOffsetSsl2 : HandshakeTypeOffsetTls] == (byte)TlsHandshakeType.ClientHello &&
_sslAuthenticationOptions!.IsServer) // guard against malicious endpoints. We should not see ClientHello on client.
#pragma warning restore CS0618
{
TlsFrameHelper.ProcessingOptions options = NetEventSource.Log.IsEnabled() ?
TlsFrameHelper.ProcessingOptions.All :
Expand Down Expand Up @@ -445,7 +449,7 @@ private ProtocolToken ProcessBlob(int frameSize)
break;
}

frameSize = nextHeader.Length + TlsFrameHelper.HeaderSize;
frameSize = nextHeader.Length;

// Can process more handshake frames in single step or during TLS1.3 post-handshake auth, but we should
// avoid processing too much so as to preserve API boundary between handshake and I/O.
Expand Down Expand Up @@ -974,7 +978,7 @@ private int GetFrameSize(ReadOnlySpan<byte> buffer)
throw new AuthenticationException(SR.net_frame_read_size);
}

return _lastFrame.Header.Length + TlsFrameHelper.HeaderSize;
return _lastFrame.Header.Length;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,6 @@ internal struct TlsFrameHeader
public int Length;

public override string ToString() => $"{Version}:{Type}[{Length}]";

public int GetFrameSize()
{
return Length + TlsFrameHelper.HeaderSize;
}
}

internal static class TlsFrameHelper
Expand Down Expand Up @@ -173,44 +168,52 @@ public override string ToString()

public static bool TryGetFrameHeader(ReadOnlySpan<byte> frame, ref TlsFrameHeader header)
{
bool result = frame.Length > 4;

if (frame.Length >= 1)
if (frame.Length < HeaderSize)
{
header.Type = (TlsContentType)frame[0];
header.Length= -1;
return false;
}

if (frame.Length >= 3)
{
// SSLv3, TLS or later
if (frame[1] == 3)
{
if (frame.Length > 4)
{
header.Length = ((frame[3] << 8) | frame[4]);
}
header.Type = (TlsContentType)frame[0];

header.Version = TlsMinorVersionToProtocol(frame[2]);
}
else
{
header.Length = -1;
header.Version = SslProtocols.None;
}
}
// SSLv3, TLS or later
if (frame[1] == 3)
{
header.Length = ((frame[3] << 8) | frame[4]) + HeaderSize;
header.Version = TlsMinorVersionToProtocol(frame[2]);
}
else if (frame[2] == (byte)TlsHandshakeType.ClientHello &&
frame[3] == 3) // SSL3 or above
{
int length;
if ((frame[0] & 0x80) != 0)
{
// Two bytes
length = (((frame[0] & 0x7f) << 8) | frame[1]) + 2;
}
else
{
// Three bytes
length = (((frame[0] & 0x3f) << 8) | frame[1]) + 3;
}

return result;
}

// Returns frame size e.g. header + content
public static int GetFrameSize(ReadOnlySpan<byte> frame)
{
if (frame.Length < 5 || frame[1] < 3)
// max frame for SSLv2 is 32767.
// However, we expect something reasonable for initial HELLO
// We don't have enough logic to verify full validity,
// the limits bellow are queses.
#pragma warning disable CS0618 // Ssl2 and Ssl3 are obsolete
header.Version = SslProtocols.Ssl2;
#pragma warning restore CS0618
header.Length = length;
header.Type = TlsContentType.Handshake;
}
else
{
return -1;
header.Length = -1;
}

return ((frame[3] << 8) | frame[4]) + HeaderSize;
return true;
}

// This function will try to parse TLS hello frame and fill details in provided info structure.
Expand Down Expand Up @@ -252,9 +255,21 @@ public static bool TryGetFrameInfo(ReadOnlySpan<byte> frame, ref TlsFrameInfo in
}

info.HandshakeType = (TlsHandshakeType)frame[HandshakeTypeOffset];
#pragma warning disable CS0618 // Ssl2 and Ssl3 are obsolete
if (info.Header.Version == SslProtocols.Ssl2)
{
// This is safe. We would not get here if the length is too small.
info.SupportedVersions |= TlsMinorVersionToProtocol(frame[4]);
// We only recognize Unified ClientHello at the moment.
// This is needed to trigger certificate selection callback in SslStream.
info.HandshakeType = TlsHandshakeType.ClientHello;
// There is no more parsing for old protocols.
return true;
}
#pragma warning restore CS0618

// Check if we have full frame.
bool isComplete = frame.Length >= HeaderSize + info.Header.Length;
bool isComplete = frame.Length >= info.Header.Length;

#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
if (((int)info.Header.Version >= (int)SslProtocols.Tls) &&
Expand Down Expand Up @@ -412,10 +427,10 @@ private static bool TryParseClientHello(ReadOnlySpan<byte> clientHello, ref TlsF
// Skip compression methods (max size 2^8-1 => size fits in 1 byte)
p = SkipOpaqueType1(p);

// is invalid structure or no extensions?
// no extensions
if (p.IsEmpty)
{
return false;
return true;
}

// client_hello_extension_list (max size 2^16-1 => size fits in 2 bytes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,71 @@ public async Task SslStream_RandomSizeWrites_OK(int bufferSize, int readBufferSi

}

[Theory]
[InlineData(true)]
[InlineData(false)]
[PlatformSpecific(TestPlatforms.Windows)]
public async Task SslStream_UnifiedHello_Ok(bool useOptionCallback)
{
(Stream client, Stream server) = TestHelper.GetConnectedTcpStreams();
SslStream ssl = new SslStream(server);

var cts = new CancellationTokenSource();
cts.CancelAfter(TestConfiguration.PassingTestTimeout);
bool callbackCalled = false;
Task serverTask;

var options = new SslServerAuthenticationOptions();
if (useOptionCallback)
{
serverTask = ssl.AuthenticateAsServerAsync((ssl, info, o, ct) =>
{
callbackCalled = true;
options.ServerCertificate = Configuration.Certificates.GetServerCertificate();
return new ValueTask<SslServerAuthenticationOptions>(options);
}, null, cts.Token);

}
else
{
options.ServerCertificateSelectionCallback = (o, name) =>
{
callbackCalled = true;
return Configuration.Certificates.GetServerCertificate();
};

serverTask = ssl.AuthenticateAsServerAsync(options, cts.Token);
}

Task.WaitAny(client.WriteAsync(TlsFrameHelperTests.s_UnifiedHello).AsTask(), serverTask);
if (serverTask.IsCompleted)
{
// Something failed. Raise exception.
await serverTask;
}

byte[] buffer = new byte[1024];
Task<int> readTask = client.ReadAsync(buffer, cts.Token).AsTask();
Task.WaitAny(readTask, serverTask);
if (serverTask.IsCompleted)
{
// Something failed. Raise exception.
await serverTask;
}

int readLength = await readTask;
// We should get back ServerHello
Assert.True(readLength > 0);
Assert.True(callbackCalled);
Assert.Equal(22, buffer[0]); // Handshake Protocol
Assert.Equal(2, buffer[5]); // ServerHello

// Handshake should not be finished at this point.
Assert.False(serverTask.IsCompleted);
cts.Cancel();
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => serverTask);
}

private static bool ValidateServerCertificate(
object sender,
X509Certificate retrievedServerPublicCertificate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void TlsFrameHelper_ValidData_Ok()
Assert.True(TlsFrameHelper.TryGetFrameInfo(s_validClientHello, ref info));

Assert.Equal(SslProtocols.Tls12, info.Header.Version);
Assert.Equal(203, info.Header.Length);
Assert.Equal(208, info.Header.Length);
Assert.Equal(SslProtocols.Tls12, info.SupportedVersions);
Assert.Equal(TlsFrameHelper.ApplicationProtocolInfo.None, info.ApplicationProtocols);
}
Expand Down Expand Up @@ -88,6 +88,37 @@ public void TlsFrameHelper_Tls12ServerHello_Ok()
Assert.Equal(TlsFrameHelper.ApplicationProtocolInfo.Http2, info.ApplicationProtocols);
}

[Fact]
public void TlsFrameHelper_UnifiedClientHello_Ok()
{
TlsFrameHelper.TlsFrameInfo info = default;
Assert.True(TlsFrameHelper.TryGetFrameHeader(s_UnifiedHello, ref info.Header));
Assert.Equal(75, info.Header.Length);

Assert.True(TlsFrameHelper.TryGetFrameInfo(s_UnifiedHello, ref info));
#pragma warning disable CS0618 // Ssl2 and Ssl3 are obsolete
#pragma warning disable SYSLIB0039 // Tls is obsolete
Assert.Equal(SslProtocols.Ssl2, info.Header.Version);
Assert.Equal(SslProtocols.Ssl2 | SslProtocols.Tls, info.SupportedVersions);
#pragma warning restore CS0618
#pragma warning restore SYSLIB0039
Assert.Equal(TlsContentType.Handshake, info.Header.Type);
Assert.Equal(TlsFrameHelper.ApplicationProtocolInfo.None, info.ApplicationProtocols);
Assert.Equal(TlsHandshakeType.ClientHello, info.HandshakeType);
}

[Fact]
public void TlsFrameHelper_TlsClientHelloNoExtensions_Ok()
{
TlsFrameHelper.TlsFrameInfo info = default;
Assert.True(TlsFrameHelper.TryGetFrameInfo(s_TlsClientHelloNoExtensions, ref info));
Assert.Equal(SslProtocols.Tls12, info.Header.Version);
Assert.Equal(SslProtocols.Tls12, info.SupportedVersions);
Assert.Equal(TlsContentType.Handshake, info.Header.Type);
Assert.Equal(TlsFrameHelper.ApplicationProtocolInfo.None, info.ApplicationProtocols);
Assert.Equal(TlsHandshakeType.ClientHello, info.HandshakeType);
}

public static IEnumerable<object[]> InvalidClientHelloData()
{
int id = 0;
Expand Down Expand Up @@ -381,6 +412,36 @@ public static IEnumerable<object[]> InvalidClientHelloDataTruncatedBytes()
0x00, 0x10, 0x00, 0x05, 0x00, 0x03, 0x02, 0x68, 0x32,
};

internal static byte[] s_UnifiedHello = new byte[]
{
// Length
0x80, 0x49,
// ClientHello
0x01,
// Version
0x03, 0x01,
0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
0x2F, 0x00, 0x00, 0x35, 0x00, 0x00, 0x04, 0x00,
0x00, 0x05, 0x00, 0x00, 0x0A, 0x01, 0x00, 0x80,
0x07, 0x00, 0xC0, 0x03, 0x00, 0x80, 0x00, 0x00,
0x09, 0x06, 0x00, 0x40, 0x00, 0x00, 0x64, 0x00,
0x00, 0x62, 0x00, 0x00, 0x03, 0x00, 0x00, 0x06,
0x02, 0x00, 0x80, 0x04, 0x00, 0x80, 0x5B, 0x0B,
0xA1, 0xEB, 0xBF, 0x2D, 0x57, 0xF5, 0xD1, 0x0F,
0x52, 0x3B, 0x12, 0x9C, 0xF8, 0xD4,
};

private static byte[] s_TlsClientHelloNoExtensions = new byte[] {
0x16, 0x03, 0x03, 0x00, 0x39, 0x01, 0x00, 0x00,
0x35, 0x03, 0x03, 0x62, 0x5d, 0x50, 0x2a, 0x41,
0x2f, 0xd8, 0xc3, 0x65, 0x35, 0xea, 0x01, 0x70,
0x03, 0x7e, 0x7e, 0x2d, 0xd4, 0xfe, 0x93, 0x39,
0xa4, 0x04, 0x66, 0xbb, 0x46, 0x91, 0x41, 0xc3,
0x48, 0x87, 0x3d, 0x00, 0x00, 0x0e, 0x00, 0x3d,
0x00, 0x3c, 0x00, 0x0a, 0x00, 0x35, 0x00, 0x2f,
0x00, 0x05, 0x00, 0x04, 0x01, 0x00
};

private static IEnumerable<byte[]> InvalidClientHello()
{
// This test covers following test cases:
Expand Down