Skip to content

Commit 3058899

Browse files
wfurtTomas Weinfurt
andauthored
improve handling of handshake failure (#35549)
* improve handling of handshake failure * more cleanup * remove console reference * add quotes around parameter * enable ServerAsyncAuthenticate_MismatchProtocols_Fails * cleanup ssl2 test * fix http2 * feedback from review * add back two missing empty lines Co-authored-by: Tomas Weinfurt <[email protected]>
1 parent 9b5805f commit 3058899

15 files changed

+328
-68
lines changed

src/libraries/System.Net.Security/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,9 @@
212212
<data name="net_auth_eof" xml:space="preserve">
213213
<value>Authentication failed because the remote party has closed the transport stream.</value>
214214
</data>
215+
<data name="net_auth_tls_alert" xml:space="preserve">
216+
<value>Authentication failed because the remote party sent a TLS alert: '{0}'.</value>
217+
</data>
215218
<data name="net_auth_alert" xml:space="preserve">
216219
<value>Authentication failed on the remote side (the stream might still be available for additional authentication attempts).</value>
217220
</data>

src/libraries/System.Net.Security/src/System.Net.Security.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<Compile Include="System\Net\Security\StreamSizes.cs" />
3535
<Compile Include="System\Net\Security\TlsAlertType.cs" />
3636
<Compile Include="System\Net\Security\TlsAlertMessage.cs" />
37+
<Compile Include="System\Net\Security\TlsFrameHelper.cs" />
3738
<Compile Include="System\Security\Authentication\AuthenticationException.cs" />
3839
<!-- NegotiateStream -->
3940
<Compile Include="System\Net\BufferAsyncResult.cs" />

src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ private bool AcquireClientCredentials(ref byte[]? thumbPrint)
625625
//
626626
// Acquire Server Side Certificate information and set it on the class.
627627
//
628-
private bool AcquireServerCredentials(ref byte[]? thumbPrint, ReadOnlySpan<byte> clientHello)
628+
private bool AcquireServerCredentials(ref byte[]? thumbPrint)
629629
{
630630
if (NetEventSource.IsEnabled)
631631
NetEventSource.Enter(this);
@@ -639,13 +639,13 @@ private bool AcquireServerCredentials(ref byte[]? thumbPrint, ReadOnlySpan<byte>
639639
// with .NET Framework), and if neither is set we fall back to using ServerCertificate.
640640
if (_sslAuthenticationOptions.ServerCertSelectionDelegate != null)
641641
{
642-
string? serverIdentity = SniHelper.GetServerName(clientHello);
643-
localCertificate = _sslAuthenticationOptions.ServerCertSelectionDelegate(serverIdentity);
644-
642+
localCertificate = _sslAuthenticationOptions.ServerCertSelectionDelegate(_sslAuthenticationOptions.TargetHost);
645643
if (localCertificate == null)
646644
{
647645
throw new AuthenticationException(SR.net_ssl_io_no_server_cert);
648646
}
647+
if (NetEventSource.IsEnabled)
648+
NetEventSource.Info(this, "Use delegate selected Cert");
649649
}
650650
else if (_sslAuthenticationOptions.CertSelectionDelegate != null)
651651
{
@@ -784,7 +784,7 @@ private SecurityStatusPal GenerateToken(ReadOnlySpan<byte> inputBuffer, ref byte
784784
if (_refreshCredentialNeeded)
785785
{
786786
cachedCreds = _sslAuthenticationOptions.IsServer
787-
? AcquireServerCredentials(ref thumbPrint, inputBuffer)
787+
? AcquireServerCredentials(ref thumbPrint)
788788
: AcquireClientCredentials(ref thumbPrint);
789789
}
790790

src/libraries/System.Net.Security/src/System/Net/Security/SniHelper.cs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ internal class SniHelper
2626
// opaque fragment[SSLPlaintext.length];
2727
// } SSLPlaintext;
2828
const int ContentTypeOffset = 0;
29-
const int ProtocolVersionOffset = ContentTypeOffset + sizeof(ContentType);
29+
const int ProtocolVersionOffset = ContentTypeOffset + sizeof(TlsContentType);
3030
const int LengthOffset = ProtocolVersionOffset + ProtocolVersionSize;
3131
const int HandshakeOffset = LengthOffset + sizeof(ushort);
3232

3333
// SSL v2's ContentType has 0x80 bit set.
3434
// We do not care about SSL v2 here because it does not support client hello extensions
35-
if (sslPlainText.Length < HandshakeOffset || (ContentType)sslPlainText[ContentTypeOffset] != ContentType.Handshake)
35+
if (sslPlainText.Length < HandshakeOffset || (TlsContentType)sslPlainText[ContentTypeOffset] != TlsContentType.Handshake)
3636
{
3737
return null;
3838
}
@@ -62,10 +62,10 @@ internal class SniHelper
6262
// } body;
6363
// } Handshake;
6464
const int HandshakeTypeOffset = 0;
65-
const int ClientHelloLengthOffset = HandshakeTypeOffset + sizeof(HandshakeType);
65+
const int ClientHelloLengthOffset = HandshakeTypeOffset + sizeof(TlsHandshakeType);
6666
const int ClientHelloOffset = ClientHelloLengthOffset + UInt24Size;
6767

68-
if (sslHandshake.Length < ClientHelloOffset || (HandshakeType)sslHandshake[HandshakeTypeOffset] != HandshakeType.ClientHello)
68+
if (sslHandshake.Length < ClientHelloOffset || (TlsHandshakeType)sslHandshake[HandshakeTypeOffset] != TlsHandshakeType.ClientHello)
6969
{
7070
return null;
7171
}
@@ -363,16 +363,6 @@ private static Encoding CreateEncoding()
363363
return Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());
364364
}
365365

366-
private enum ContentType : byte
367-
{
368-
Handshake = 0x16
369-
}
370-
371-
private enum HandshakeType : byte
372-
{
373-
ClientHello = 0x01
374-
}
375-
376366
private enum ExtensionType : ushort
377367
{
378368
ServerName = 0x00

src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,8 @@ private enum Framing
3434
// This is set on the first packet to figure out the framing style.
3535
private Framing _framing = Framing.Unknown;
3636

37-
// SSL3/TLS protocol frames definitions.
38-
private enum FrameType : byte
39-
{
40-
ChangeCipherSpec = 20,
41-
Alert = 21,
42-
Handshake = 22,
43-
AppData = 23
44-
}
37+
private TlsAlertDescription _lastAlertDescription;
38+
private TlsFrameHandshakeInfo _lastFrame;
4539

4640
private readonly object _handshakeLock = new object();
4741
private volatile TaskCompletionSource<bool>? _handshakeWaiter;
@@ -274,7 +268,6 @@ private async Task ForceAuthenticationAsync<TIOAdapter>(TIOAdapter adapter, bool
274268
{
275269
// get ready to receive first frame
276270
_handshakeBuffer = new ArrayBuffer(InitialHandshakeBufferSize);
277-
_framing = Framing.Unknown;
278271
}
279272

280273
while (!handshakeCompleted)
@@ -288,6 +281,19 @@ private async Task ForceAuthenticationAsync<TIOAdapter>(TIOAdapter adapter, bool
288281

289282
if (message.Failed)
290283
{
284+
if (_lastFrame.Header.Type == TlsContentType.Handshake && message.Size == 0)
285+
{
286+
// If we failed without OS sending out alert, inject one here to be consistent across platforms.
287+
byte[] alert = TlsFrameHelper.CreateAlertFrame(_lastFrame.Header.Version, TlsAlertDescription.ProtocolVersion);
288+
await adapter.WriteAsync(alert, 0, alert.Length).ConfigureAwait(false);
289+
}
290+
else if (_lastFrame.Header.Type == TlsContentType.Alert && _lastAlertDescription != TlsAlertDescription.CloseNotify &&
291+
message.Status.ErrorCode == SecurityStatusPalErrorCode.IllegalMessage)
292+
{
293+
// Improve generic message and show details if we failed because of TLS Alert.
294+
throw new AuthenticationException(SR.Format(SR.net_auth_tls_alert, _lastAlertDescription.ToString()), message.GetException());
295+
}
296+
291297
throw new AuthenticationException(SR.net_auth_SSPI, message.GetException());
292298
}
293299
else if (message.Status.ErrorCode == SecurityStatusPalErrorCode.OK)
@@ -346,17 +352,49 @@ private async ValueTask<ProtocolToken> ReceiveBlobAsync<TIOAdapter>(TIOAdapter a
346352
_framing = DetectFraming(_handshakeBuffer.ActiveReadOnlySpan);
347353
}
348354

349-
int frameSize = GetFrameSize(_handshakeBuffer.ActiveReadOnlySpan);
350-
if (frameSize < 0)
355+
if (_framing == Framing.BeforeSSL3)
356+
{
357+
#pragma warning disable 0618
358+
_lastFrame.Header.Version = SslProtocols.Ssl2;
359+
#pragma warning restore 0618
360+
_lastFrame.Header.Length = GetFrameSize(_handshakeBuffer.ActiveReadOnlySpan);
361+
}
362+
else
363+
{
364+
TlsFrameHelper.TryGetFrameHeader(_handshakeBuffer.ActiveReadOnlySpan, ref _lastFrame.Header);
365+
}
366+
367+
if (_lastFrame.Header.Length < 0)
351368
{
352369
throw new IOException(SR.net_frame_read_size);
353370
}
354371

372+
// Header length is content only so we must add header size as well.
373+
int frameSize = _lastFrame.Header.Length + TlsFrameHelper.HeaderSize;
355374
if (_handshakeBuffer.ActiveLength < frameSize)
356375
{
357376
await FillHandshakeBufferAsync(adapter, frameSize).ConfigureAwait(false);
358377
}
378+
359379
// At this point, we have at least one TLS frame.
380+
if (_lastFrame.Header.Type == TlsContentType.Alert)
381+
{
382+
TlsAlertLevel level = 0;
383+
if (TlsFrameHelper.TryGetAlertInfo(_handshakeBuffer.ActiveReadOnlySpan, ref level, ref _lastAlertDescription))
384+
{
385+
if (NetEventSource.IsEnabled && _lastAlertDescription != TlsAlertDescription.CloseNotify) NetEventSource.Fail(this, $"Received TLS alert {_lastAlertDescription}");
386+
}
387+
}
388+
else if (_lastFrame.Header.Type == TlsContentType.Handshake)
389+
{
390+
if (_handshakeBuffer.ActiveReadOnlySpan[TlsFrameHelper.HeaderSize] == (byte)TlsHandshakeType.ClientHello &&
391+
_sslAuthenticationOptions!.ServerCertSelectionDelegate != null)
392+
{
393+
// Process SNI from Client Hello message
394+
TlsFrameHelper.TryGetHandshakeInfo(_handshakeBuffer.ActiveReadOnlySpan, ref _lastFrame);
395+
_sslAuthenticationOptions.TargetHost = _lastFrame.TargetName;
396+
}
397+
}
360398

361399
return ProcessBlob(frameSize);
362400
}
@@ -372,23 +410,24 @@ private ProtocolToken ProcessBlob(int frameSize)
372410
_handshakeBuffer.Discard(frameSize);
373411

374412
// Often more TLS messages fit into same packet. Get as many complete frames as we can.
375-
while (_handshakeBuffer.ActiveLength > SecureChannel.ReadHeaderSize)
413+
while (_handshakeBuffer.ActiveLength > TlsFrameHelper.HeaderSize)
376414
{
377-
ReadOnlySpan<byte> remainingData = _handshakeBuffer.ActiveReadOnlySpan;
378-
if (remainingData[0] >= (int)FrameType.AppData)
415+
TlsFrameHeader nextHeader = default;
416+
417+
if (!TlsFrameHelper.TryGetFrameHeader(_handshakeBuffer.ActiveReadOnlySpan, ref nextHeader))
379418
{
380419
break;
381420
}
382421

383-
frameSize = GetFrameSize(remainingData);
384-
if (_handshakeBuffer.ActiveLength >= frameSize)
422+
frameSize = nextHeader.Length + TlsFrameHelper.HeaderSize;
423+
if (nextHeader.Type == TlsContentType.AppData || frameSize > _handshakeBuffer.ActiveLength)
385424
{
386-
chunkSize += frameSize;
387-
_handshakeBuffer.Discard(frameSize);
388-
continue;
425+
// We don't have full frame left or we already have app data which needs to be processed by decrypt.
426+
break;
389427
}
390428

391-
break;
429+
chunkSize += frameSize;
430+
_handshakeBuffer.Discard(frameSize);
392431
}
393432

394433
return _context!.NextMessage(availableData.Slice(0, chunkSize));
@@ -645,7 +684,7 @@ private async ValueTask<int> ReadAsyncInternal<TIOAdapter>(TIOAdapter adapter, M
645684
Debug.Assert(_internalBufferCount >= SecureChannel.ReadHeaderSize);
646685

647686
// Parse the frame header to determine the payload size (which includes the header size).
648-
int payloadBytes = GetFrameSize(_internalBuffer.AsSpan(_internalOffset));
687+
int payloadBytes = TlsFrameHelper.GetFrameSize(_internalBuffer.AsSpan(_internalOffset));
649688
if (payloadBytes < 0)
650689
{
651690
throw new IOException(SR.net_frame_read_size);
@@ -913,6 +952,7 @@ private static byte[] EnsureBufferSize(byte[] buffer, int copyCount, int size)
913952
Buffer.BlockCopy(saved, 0, buffer, 0, copyCount);
914953
}
915954
}
955+
916956
return buffer;
917957
}
918958

@@ -1003,8 +1043,8 @@ private Framing DetectFraming(ReadOnlySpan<byte> bytes)
10031043
}
10041044

10051045
// If the first byte is SSL3 HandShake, then check if we have a SSLv3 Type3 client hello.
1006-
if (bytes[0] == (byte)FrameType.Handshake || bytes[0] == (byte)FrameType.AppData
1007-
|| bytes[0] == (byte)FrameType.Alert)
1046+
if (bytes[0] == (byte)TlsContentType.Handshake || bytes[0] == (byte)TlsContentType.AppData
1047+
|| bytes[0] == (byte)TlsContentType.Alert)
10081048
{
10091049
if (bytes.Length < 3)
10101050
{

src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,8 @@ private static SecurityStatusPal HandshakeInternal(
237237
sslContext = new SafeDeleteSslContext((credential as SafeFreeSslCredentials)!, sslAuthenticationOptions);
238238
context = sslContext;
239239

240-
if (!string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost))
240+
if (!string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) && !sslAuthenticationOptions.IsServer)
241241
{
242-
Debug.Assert(!sslAuthenticationOptions.IsServer, "targetName should not be set for server-side handshakes");
243242
Interop.AppleCrypto.SslSetTargetName(sslContext.SslContext, sslAuthenticationOptions.TargetHost);
244243
}
245244

0 commit comments

Comments
 (0)