Skip to content

[WIP] Support certificate validation for TLS alerts for Linux #115996

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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 @@ -213,6 +213,8 @@
throw CreateSslException(SR.net_allocate_ssl_context_failed);
}

Ssl.SslCtxSetCertVerifyCallback(sslCtx, &Ssl.CertVerifyCallback);

Ssl.SslCtxSetProtocolOptions(sslCtx, protocols);

if (sslAuthenticationOptions.EncryptionPolicy != EncryptionPolicy.RequireEncryption)
Expand Down Expand Up @@ -247,7 +249,7 @@
// If you find yourself wanting to remove this line to enable bidirectional
// close-notify, you'll probably need to rewrite SafeSslHandle.Disconnect().
// https://www.openssl.org/docs/manmaster/ssl/SSL_shutdown.html
Ssl.SslCtxSetQuietShutdown(sslCtx);
// Ssl.SslCtxSetQuietShutdown(sslCtx);

if (enableResume)
{
Expand Down Expand Up @@ -434,6 +436,12 @@

if (sslAuthenticationOptions.IsClient)
{
// Client side always verifies the server's certificate.
Ssl.SslSetVerifyPeer(sslHandle);

// HACK: set a bogus code to indicate that we did not perform the validation yet
// Ssl.SslSetVerifyResult(sslHandle, -1);

if (!string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) && !IPAddress.IsValid(sslAuthenticationOptions.TargetHost))
{
// Similar to windows behavior, set SNI on openssl by default for client context, ignore errors.
Expand Down Expand Up @@ -466,6 +474,8 @@
if (sslAuthenticationOptions.RemoteCertRequired)
{
Ssl.SslSetVerifyPeer(sslHandle);
// HACK: set a bogus code to indicate that we did not perform the validation yet
Ssl.SslSetVerifyResult(sslHandle, -1);
}

if (sslAuthenticationOptions.CertificateContext != null)
Expand Down Expand Up @@ -542,6 +552,8 @@
}
}

#pragma warning disable CS0618
System.Console.WriteLine($"[{AppDomain.GetCurrentThreadId()}] SSL_do_handshake");

Check failure on line 556 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs

View check run for this annotation

Azure Pipelines / runtime (Build linux_musl-x64 Release AllSubsets_Mono)

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs#L556

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs(556,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 556 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs

View check run for this annotation

Azure Pipelines / runtime-dev-innerloop (Build Source-Build (Linux_x64))

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs#L556

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs(556,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 556 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs

View check run for this annotation

Azure Pipelines / runtime (Build linux_musl-arm64 Debug AllSubsets_CoreCLR_ReleaseRuntimeLibs)

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs#L556

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs(556,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 556 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs

View check run for this annotation

Azure Pipelines / runtime-dev-innerloop

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs#L556

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs(556,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)
int retVal = Ssl.SslDoHandshake(context, out Ssl.SslErrorCode errorCode);
if (retVal != 1)
{
Expand All @@ -550,6 +562,13 @@
return SecurityStatusPalErrorCode.CredentialsNeeded;
}

if (errorCode == Ssl.SslErrorCode.SSL_ERROR_WANT_ASYNC)
// if (errorCode == Ssl.SslErrorCode.SSL_ERROR_WANT_RETRY_VERIFY)
{
System.Console.WriteLine($"[{AppDomain.GetCurrentThreadId()}] SSL_ERROR_WANT_ASYNC");

Check failure on line 568 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs

View check run for this annotation

Azure Pipelines / runtime (Build linux_musl-x64 Release AllSubsets_Mono)

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs#L568

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs(568,21): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 568 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs

View check run for this annotation

Azure Pipelines / runtime-dev-innerloop (Build Source-Build (Linux_x64))

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs#L568

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs(568,21): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 568 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs

View check run for this annotation

Azure Pipelines / runtime (Build linux_musl-arm64 Debug AllSubsets_CoreCLR_ReleaseRuntimeLibs)

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs#L568

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs(568,21): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 568 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs

View check run for this annotation

Azure Pipelines / runtime-dev-innerloop

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs#L568

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs(568,21): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)
return SecurityStatusPalErrorCode.PeerCertVerifyRequired;
}

if ((retVal != -1) || (errorCode != Ssl.SslErrorCode.SSL_ERROR_WANT_READ))
{
Exception? innerError = GetSslError(retVal, errorCode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ internal static SafeSharedX509StackHandle SslGetPeerCertChain(SafeSslHandle ssl)
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSessionSetData")]
internal static partial void SslSessionSetData(IntPtr session, IntPtr val);

[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetVerifyResult")]
internal static partial void SslSetVerifyResult(SafeSslHandle ssl, long verifyResult);

[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslGetVerifyResult")]
internal static partial long SslGetVerifyResult(SafeSslHandle ssl);

internal static class Capabilities
{
// needs separate type (separate static cctor) to be sure OpenSSL is initialized.
Expand Down Expand Up @@ -340,6 +346,9 @@ internal enum SslErrorCode
SSL_ERROR_SYSCALL = 5,
SSL_ERROR_ZERO_RETURN = 6,

SSL_ERROR_WANT_ASYNC = 9,
SSL_ERROR_WANT_RETRY_VERIFY = 12,

// NOTE: this SslErrorCode value doesn't exist in OpenSSL, but
// we use it to distinguish when a renegotiation is pending.
// Choosing an arbitrarily large value that shouldn't conflict
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@

return true;
}

[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetCertVerifyCallback")]
internal static unsafe partial void SslCtxSetCertVerifyCallback(SafeSslContextHandle ctx, delegate* unmanaged<IntPtr, IntPtr, int> callback);

[UnmanagedCallersOnly]
internal static int CertVerifyCallback(IntPtr ssl, IntPtr store)
{
System.Console.WriteLine($"CertVerifyCallback called with store: {store:x8}, ssl: {ssl:x8}");

Check failure on line 72 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs

View check run for this annotation

Azure Pipelines / runtime (Build linux_musl-x64 Release AllSubsets_Mono)

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs#L72

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs(72,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 72 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs

View check run for this annotation

Azure Pipelines / runtime-dev-innerloop (Build Source-Build (Linux_x64))

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs#L72

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs(72,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 72 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs

View check run for this annotation

Azure Pipelines / runtime (Build linux_musl-arm64 Debug AllSubsets_CoreCLR_ReleaseRuntimeLibs)

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs#L72

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs(72,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 72 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs

View check run for this annotation

Azure Pipelines / runtime-dev-innerloop

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs#L72

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs(72,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)
IntPtr data = Ssl.SslGetData(ssl);
System.Console.WriteLine($"SSL data: {data:x8}");

Check failure on line 74 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs

View check run for this annotation

Azure Pipelines / runtime (Build linux_musl-x64 Release AllSubsets_Mono)

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs#L74

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs(74,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 74 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs

View check run for this annotation

Azure Pipelines / runtime-dev-innerloop (Build Source-Build (Linux_x64))

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs#L74

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs(74,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 74 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs

View check run for this annotation

Azure Pipelines / runtime (Build linux_musl-arm64 Debug AllSubsets_CoreCLR_ReleaseRuntimeLibs)

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs#L74

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs(74,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 74 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs

View check run for this annotation

Azure Pipelines / runtime-dev-innerloop

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs#L74

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs(74,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)
GCHandle gch = GCHandle.FromIntPtr(data);
System.Console.WriteLine($"SSL data handle: {gch.Target}");

Check failure on line 76 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs

View check run for this annotation

Azure Pipelines / runtime (Build linux_musl-x64 Release AllSubsets_Mono)

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs#L76

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs(76,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 76 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs

View check run for this annotation

Azure Pipelines / runtime-dev-innerloop (Build Source-Build (Linux_x64))

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs#L76

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs(76,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)

Check failure on line 76 in src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs

View check run for this annotation

Azure Pipelines / runtime (Build linux_musl-arm64 Debug AllSubsets_CoreCLR_ReleaseRuntimeLibs)

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs#L76

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs(76,13): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'Console' does not exist in the namespace 'System' (are you missing an assembly reference?)
return 0;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,18 @@ internal static SslPolicyErrors VerifyCertificateProperties(
return null;
}

IntPtr remoteCertificate = IntPtr.Zero;
{
using var chainStack = Interop.OpenSsl.GetPeerCertificateChain((SafeSslHandle)securityContext);
int count = Interop.Crypto.GetX509StackFieldCount(chainStack);
if (count > 0)
{
remoteCertificate = Interop.Crypto.GetX509StackField(chainStack, 0);
}
}

X509Certificate2? result = null;
IntPtr remoteCertificate = Interop.OpenSsl.GetPeerCertificate((SafeSslHandle)securityContext);

try
{
if (remoteCertificate == IntPtr.Zero)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,8 @@ private async Task ForceAuthenticationAsync<TIOAdapter>(bool receiveFirst, byte[
{
if (!receiveFirst)
{
token = NextMessage(reAuthenticationData, out int consumed);
int consumed;
(token, consumed) = await NextMessage(reAuthenticationData).ConfigureAwait(false);
Debug.Assert(consumed == (reAuthenticationData?.Length ?? 0));

if (token.Size > 0)
Expand Down Expand Up @@ -489,14 +490,14 @@ private ProtocolToken ProcessTlsFrame(int frameSize)
{
int chunkSize = frameSize;

ReadOnlySpan<byte> availableData = _buffer.EncryptedReadOnlySpan;
ReadOnlyMemory<byte> availableData = _buffer.EncryptedReadOnlyMemory;

// Often more TLS messages fit into same packet. Get as many complete frames as we can.
while (_buffer.EncryptedLength - chunkSize > TlsFrameHelper.HeaderSize)
{
TlsFrameHeader nextHeader = default;

if (!TlsFrameHelper.TryGetFrameHeader(availableData.Slice(chunkSize), ref nextHeader))
if (!TlsFrameHelper.TryGetFrameHeader(availableData.Slice(chunkSize).Span, ref nextHeader))
{
break;
}
Expand All @@ -514,7 +515,7 @@ private ProtocolToken ProcessTlsFrame(int frameSize)
chunkSize += frameSize;
}

ProtocolToken token = NextMessage(availableData.Slice(0, chunkSize), out int consumed);
(ProtocolToken token, int consumed) = NextMessage(availableData.Slice(0, chunkSize)).GetAwaiter().GetResult();
_buffer.DiscardEncrypted(consumed);
return token;
}
Expand All @@ -523,20 +524,15 @@ private ProtocolToken ProcessTlsFrame(int frameSize)
// This is to reset auth state on remote side.
// If this write succeeds we will allow auth retrying.
//
private void SendAuthResetSignal(ReadOnlySpan<byte> alert, ExceptionDispatchInfo exception)
private void SendAuthResetAndThrow(ReadOnlySpan<byte> alert, ExceptionDispatchInfo exception)
{
SetException(exception.SourceException);

if (alert.Length == 0)
if (alert.Length >= 0)
{
//
// We don't have an alert to send so cannot retry and fail prematurely.
//
exception.Throw();
InnerStream.Write(alert);
}

InnerStream.Write(alert);

exception.Throw();
}

Expand Down Expand Up @@ -591,21 +587,26 @@ private void CompleteHandshake(SslAuthenticationOptions sslAuthenticationOptions
ProtocolToken alertToken = default;
if (!CompleteHandshake(ref alertToken, out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus))
{
if (sslAuthenticationOptions!.CertValidationDelegate != null)
{
// there may be some chain errors but the decision was made by custom callback. Details should be tracing if enabled.
SendAuthResetSignal(new ReadOnlySpan<byte>(alertToken.Payload), ExceptionDispatchInfo.Capture(new AuthenticationException(SR.net_ssl_io_cert_custom_validation, null)));
}
else if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors && chainStatus != X509ChainStatusFlags.NoError)
{
// We failed only because of chain and we have some insight.
SendAuthResetSignal(new ReadOnlySpan<byte>(alertToken.Payload), ExceptionDispatchInfo.Capture(new AuthenticationException(SR.Format(SR.net_ssl_io_cert_chain_validation, chainStatus), null)));
}
else
{
// Simple add sslPolicyErrors as crude info.
SendAuthResetSignal(new ReadOnlySpan<byte>(alertToken.Payload), ExceptionDispatchInfo.Capture(new AuthenticationException(SR.Format(SR.net_ssl_io_cert_validation, sslPolicyErrors), null)));
}
ProcessFailedCertificateValidation(sslAuthenticationOptions, ref alertToken, sslPolicyErrors, chainStatus);
}
}

private void ProcessFailedCertificateValidation(SslAuthenticationOptions sslAuthenticationOptions, ref ProtocolToken alertToken, SslPolicyErrors sslPolicyErrors, X509ChainStatusFlags chainStatus)
{
if (sslAuthenticationOptions!.CertValidationDelegate != null)
{
// there may be some chain errors but the decision was made by custom callback. Details should be tracing if enabled.
SendAuthResetAndThrow(new ReadOnlySpan<byte>(alertToken.Payload), ExceptionDispatchInfo.Capture(new AuthenticationException(SR.net_ssl_io_cert_custom_validation, null)));
}
else if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors && chainStatus != X509ChainStatusFlags.NoError)
{
// We failed only because of chain and we have some insight.
SendAuthResetAndThrow(new ReadOnlySpan<byte>(alertToken.Payload), ExceptionDispatchInfo.Capture(new AuthenticationException(SR.Format(SR.net_ssl_io_cert_chain_validation, chainStatus), null)));
}
else
{
// Simple add sslPolicyErrors as crude info.
SendAuthResetAndThrow(new ReadOnlySpan<byte>(alertToken.Payload), ExceptionDispatchInfo.Capture(new AuthenticationException(SR.Format(SR.net_ssl_io_cert_validation, sslPolicyErrors), null)));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Security.Authentication.ExtendedProtection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

namespace System.Net.Security
{
Expand Down Expand Up @@ -813,9 +814,10 @@ static DateTime GetExpiryTimestamp(SslStreamCertificateContext certificateContex
}

//
internal ProtocolToken NextMessage(ReadOnlySpan<byte> incomingBuffer, out int consumed)
internal async Task<(ProtocolToken, int)> NextMessage(ReadOnlyMemory<byte> incomingBuffer)
{
ProtocolToken token = GenerateToken(incomingBuffer, out consumed);
(ProtocolToken token, int consumed) = await GenerateToken(incomingBuffer).ConfigureAwait(false);

if (NetEventSource.Log.IsEnabled())
{
if (token.Failed)
Expand All @@ -824,7 +826,7 @@ internal ProtocolToken NextMessage(ReadOnlySpan<byte> incomingBuffer, out int co
}
}

return token;
return (token, consumed);
}

/*++
Expand All @@ -840,7 +842,7 @@ generates a set of bytes that will be sent next to
Return:
token - ProtocolToken with status and optionally buffer.
--*/
private ProtocolToken GenerateToken(ReadOnlySpan<byte> inputBuffer, out int consumed)
private async Task<(ProtocolToken, int)> GenerateToken(ReadOnlyMemory<byte> inputBuffer)
{
bool cachedCreds = false;
bool sendTrustList = false;
Expand All @@ -849,6 +851,9 @@ private ProtocolToken GenerateToken(ReadOnlySpan<byte> inputBuffer, out int cons
ProtocolToken token = default;
token.RentBuffer = true;

int consumed = 0;
int tmpConsumed;

// We need to try get credentials at the beginning.
// _credentialsHandle may be always null on some platforms but
// _securityContext will be allocated on first call.
Expand All @@ -861,6 +866,7 @@ private ProtocolToken GenerateToken(ReadOnlySpan<byte> inputBuffer, out int cons
{
do
{
retry:
thumbPrint = null;
if (refreshCredentialNeeded)
{
Expand All @@ -876,8 +882,8 @@ private ProtocolToken GenerateToken(ReadOnlySpan<byte> inputBuffer, out int cons
token = SslStreamPal.AcceptSecurityContext(
ref _credentialsHandle!,
ref _securityContext,
inputBuffer,
out consumed,
inputBuffer.Span,
out tmpConsumed,
_sslAuthenticationOptions);
if (token.Status.ErrorCode == SecurityStatusPalErrorCode.HandshakeStarted)
{
Expand Down Expand Up @@ -905,8 +911,8 @@ private ProtocolToken GenerateToken(ReadOnlySpan<byte> inputBuffer, out int cons
ref _credentialsHandle!,
ref _securityContext,
hostName,
inputBuffer,
out consumed,
inputBuffer.Span,
out tmpConsumed,
_sslAuthenticationOptions);

if (token.Status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded)
Expand All @@ -926,6 +932,20 @@ private ProtocolToken GenerateToken(ReadOnlySpan<byte> inputBuffer, out int cons
_sslAuthenticationOptions);
}
}
consumed += tmpConsumed;
inputBuffer = inputBuffer.Slice(tmpConsumed);

if (token.Status.ErrorCode == SecurityStatusPalErrorCode.PeerCertVerifyRequired)
{
await Task.Yield();
if (!VerifyRemoteCertificate(_sslAuthenticationOptions.CertValidationDelegate, _sslAuthenticationOptions.CertificateContext?.Trust, ref token, out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus))
{
ProcessFailedCertificateValidation(_sslAuthenticationOptions, ref token, sslPolicyErrors, chainStatus);
}

goto retry;

}
} while (cachedCreds && _credentialsHandle == null);
}
finally
Expand Down Expand Up @@ -957,7 +977,7 @@ private ProtocolToken GenerateToken(ReadOnlySpan<byte> inputBuffer, out int cons
}
}

return token;
return (token, consumed);
}

internal ProtocolToken Renegotiate()
Expand Down Expand Up @@ -1213,12 +1233,12 @@ private ProtocolToken CreateShutdownToken()
return default;
}

return GenerateToken(default, out _);
return GenerateToken(default).GetAwaiter().GetResult().Item1;
}

private ProtocolToken GenerateAlertToken()
{
return GenerateToken(default, out _);
return GenerateToken(default).GetAwaiter().GetResult().Item1;
}

private static TlsAlertMessage GetAlertMessageFromChain(X509Chain chain)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public ReadOnlySpan<byte> DecryptedReadOnlySpanSliced(int length)
public Span<byte> EncryptedSpanSliced(int length) => _buffer.ActiveSpan.Slice(_decryptedLength + _decryptedPadding, length);

public ReadOnlySpan<byte> EncryptedReadOnlySpan => _buffer.ActiveSpan.Slice(_decryptedLength + _decryptedPadding);
public ReadOnlyMemory<byte> EncryptedReadOnlyMemory => _buffer.ActiveMemory.Slice(_decryptedLength + _decryptedPadding);

public int EncryptedLength => _buffer.ActiveLength - _decryptedPadding - _decryptedLength;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,16 @@ private static ProtocolToken HandshakeInternal(ref SafeDeleteSslContext? context
errorCode = Interop.OpenSsl.DoSslHandshake((SafeSslHandle)context, ReadOnlySpan<byte>.Empty, ref token);
}

if (errorCode == SecurityStatusPalErrorCode.PeerCertVerifyRequired)
{
token.Status = new SecurityStatusPal(SecurityStatusPalErrorCode.PeerCertVerifyRequired);
return token;
// System.Console.WriteLine($"Peer certificate verification required. setting VerifyResult to an error");
// Interop.Ssl.SslSetVerifyResult((SafeSslHandle)context, 0);
// // continue with the handshake, the callback will be invoked later.
// errorCode = Interop.OpenSsl.DoSslHandshake((SafeSslHandle)context, ReadOnlySpan<byte>.Empty, ref token);
}

// sometimes during renegotiation processing message does not yield new output.
// That seems to be flaw in OpenSSL state machine and we have workaround to peek it and try it again.
if (token.Size == 0 && Interop.Ssl.IsSslRenegotiatePending((SafeSslHandle)context))
Expand Down Expand Up @@ -239,6 +249,9 @@ private static ProtocolToken HandshakeInternal(ref SafeDeleteSslContext? context

public static SecurityStatusPal ApplyAlertToken(SafeDeleteContext? securityContext, TlsAlertType alertType, TlsAlertMessage alertMessage)
{
// All the alerts that we manually propagate do with certificate validation
Interop.Ssl.SslSetVerifyResult((SafeSslHandle)securityContext!, 28);

// There doesn't seem to be an exposed API for writing an alert,
// the API seems to assume that all alerts are generated internally by
// SSLHandshake.
Expand Down
Loading
Loading