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

add NegotiateClientCertificateAsync support on Windows #51905

Merged
merged 7 commits into from
Jun 2, 2021
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 @@ -50,5 +50,6 @@ internal enum SECURITY_STATUS
BadBinding = unchecked((int)0x80090346),
DowngradeDetected = unchecked((int)0x80090350),
ApplicationProtocolMismatch = unchecked((int)0x80090367),
NoRenegotiation = unchecked((int)0x00090360),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace System.Net
{
internal static class SecurityStatusAdapterPal
{
private const int StatusDictionarySize = 42;
private const int StatusDictionarySize = 43;

#if DEBUG
static SecurityStatusAdapterPal()
Expand Down Expand Up @@ -61,7 +61,8 @@ static SecurityStatusAdapterPal()
{ Interop.SECURITY_STATUS.UnsupportedPreauth, SecurityStatusPalErrorCode.UnsupportedPreauth },
{ Interop.SECURITY_STATUS.Unsupported, SecurityStatusPalErrorCode.Unsupported },
{ Interop.SECURITY_STATUS.UntrustedRoot, SecurityStatusPalErrorCode.UntrustedRoot },
{ Interop.SECURITY_STATUS.WrongPrincipal, SecurityStatusPalErrorCode.WrongPrincipal }
{ Interop.SECURITY_STATUS.WrongPrincipal, SecurityStatusPalErrorCode.WrongPrincipal },
{ Interop.SECURITY_STATUS.NoRenegotiation, SecurityStatusPalErrorCode.NoRenegotiation }
};

internal static SecurityStatusPal GetSecurityStatusPalFromNativeInt(int win32SecurityStatus)
Expand Down
3 changes: 2 additions & 1 deletion src/libraries/Common/src/System/Net/SecurityStatusPal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ internal enum SecurityStatusPalErrorCode
UnsupportedPreauth,
BadBinding,
DowngradeDetected,
ApplicationProtocolMismatch
ApplicationProtocolMismatch,
NoRenegotiation
}
}
3 changes: 2 additions & 1 deletion src/libraries/System.Net.Security/ref/System.Net.Security.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ public virtual void AuthenticateAsServer(System.Security.Cryptography.X509Certif
public virtual System.Threading.Tasks.Task AuthenticateAsServerAsync(System.Security.Cryptography.X509Certificates.X509Certificate serverCertificate) { throw null; }
public virtual System.Threading.Tasks.Task AuthenticateAsServerAsync(System.Security.Cryptography.X509Certificates.X509Certificate serverCertificate, bool clientCertificateRequired, bool checkCertificateRevocation) { throw null; }
public virtual System.Threading.Tasks.Task AuthenticateAsServerAsync(System.Security.Cryptography.X509Certificates.X509Certificate serverCertificate, bool clientCertificateRequired, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation) { throw null; }
public System.Threading.Tasks.Task AuthenticateAsServerAsync(ServerOptionsSelectionCallback optionsCallback, object? state, System.Threading.CancellationToken cancellationToken = default) { throw null; }
public virtual System.IAsyncResult BeginAuthenticateAsClient(string targetHost, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; }
public virtual System.IAsyncResult BeginAuthenticateAsClient(string targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection? clientCertificates, bool checkCertificateRevocation, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; }
public virtual System.IAsyncResult BeginAuthenticateAsClient(string targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection? clientCertificates, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; }
Expand All @@ -231,6 +232,7 @@ public override void EndWrite(System.IAsyncResult asyncResult) { }
~SslStream() { }
public override void Flush() { }
public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
public virtual System.Threading.Tasks.Task NegotiateClientCertificateAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override int Read(byte[] buffer, int offset, int count) { throw null; }
public override System.Threading.Tasks.Task<int> ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; }
public override System.Threading.Tasks.ValueTask<int> ReadAsync(System.Memory<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand All @@ -242,7 +244,6 @@ public void Write(byte[] buffer) { }
public override void Write(byte[] buffer, int offset, int count) { }
public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; }
public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Threading.Tasks.Task AuthenticateAsServerAsync(ServerOptionsSelectionCallback optionsCallback, object? state, System.Threading.CancellationToken cancellationToken = default) { throw null; }
}
[System.CLSCompliantAttribute(false)]
public enum TlsCipherSuite : ushort
Expand Down
6 changes: 6 additions & 0 deletions src/libraries/System.Net.Security/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -449,4 +449,10 @@
<data name="SystemNetSecurity_PlatformNotSupported" xml:space="preserve">
<value>System.Net.Security is not supported on this platform.</value>
</data>
<data name="net_ssl_certificate_exist" xml:space="preserve">
<value>Remote certificate is already available.</value>
</data>
<data name="net_ssl_renegotiate_data" xml:space="preserve">
<value>Received data during renegotiation.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,15 @@ private SecurityStatusPal GenerateToken(ReadOnlySpan<byte> inputBuffer, ref byte
return status;
}

internal SecurityStatusPal Renegotiate(out byte[]? output)
{
return SslStreamPal.Renegotiate(
ref _credentialsHandle!,
ref _securityContext,
_sslAuthenticationOptions,
out output);
}

/*++
ProcessHandshakeSuccess -
Called on successful completion of Handshake -
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ private enum Framing
BeforeSSL3, // SSlv2
SinceSSL3, // SSlv3 & TLS
Unified, // Intermediate on first frame until response is processes.
Invalid // Somthing is wrong.
Invalid // Something is wrong.
}

// This is set on the first packet to figure out the framing style.
Expand Down Expand Up @@ -305,6 +305,59 @@ private async Task ReplyOnReAuthenticationAsync<TIOAdapter>(TIOAdapter adapter,
}
}

// This will initiate renegotiation or PHA for Tls1.3
private async Task RenegotiateAsync(CancellationToken cancellationToken)
{
if (Interlocked.Exchange(ref _nestedAuth, 1) == 1)
{
throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "NegotiateClientCertificateAsync", "renegotiate"));
}

if (Interlocked.Exchange(ref _nestedRead, 1) == 1)
{
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, nameof(SslStream.ReadAsync), "read"));
}

if (Interlocked.Exchange(ref _nestedWrite, 1) == 1)
{
_nestedRead = 0;
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, nameof(WriteAsync), "write"));
}

_sslAuthenticationOptions!.RemoteCertRequired = true;
IReadWriteAdapter adapter = new AsyncReadWriteAdapter(InnerStream, cancellationToken);

try
{
SecurityStatusPal status = _context!.Renegotiate(out byte[]? nextmsg);
if (nextmsg?.Length > 0)
{
await adapter.WriteAsync(nextmsg, 0, nextmsg.Length).ConfigureAwait(false);
await adapter.FlushAsync().ConfigureAwait(false);
}

if (status.ErrorCode != SecurityStatusPalErrorCode.OK)
geoffkizer marked this conversation as resolved.
Show resolved Hide resolved
{
if (status.ErrorCode == SecurityStatusPalErrorCode.NoRenegotiation)
{
// peer does not want to renegotiate. That should keep session usable.
return;
}

throw SslStreamPal.GetException(status);
}

// Issue empty read to get renegotiation going.
await ReadAsyncInternal(adapter, Memory<byte>.Empty, renegotiation: true).ConfigureAwait(false);
geoffkizer marked this conversation as resolved.
Show resolved Hide resolved
}
finally
{
_nestedRead = 0;
_nestedWrite = 0;
// We will not release _nestedAuth at this point to prevent another renegotiation attempt.
}
}

// reAuthenticationData is only used on Windows in case of renegotiation.
private async Task ForceAuthenticationAsync<TIOAdapter>(TIOAdapter adapter, bool receiveFirst, byte[]? reAuthenticationData, bool isApm = false)
where TIOAdapter : IReadWriteAdapter
Expand Down Expand Up @@ -612,6 +665,15 @@ private bool CompleteHandshake(ref ProtocolToken? alertToken, out SslPolicyError
{
_context!.ProcessHandshakeSuccess();

if (_nestedAuth != 1)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like something worth logging

if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"Ignoring unsolicited renegotiated certificate.");
// ignore certificates received outside of handshake or requested renegotiation.
sslPolicyErrors = SslPolicyErrors.None;
chainStatus = X509ChainStatusFlags.NoError;
return true;
}

if (!_context.VerifyRemoteCertificate(_sslAuthenticationOptions!.CertValidationDelegate, ref alertToken, out sslPolicyErrors, out chainStatus))
{
_handshakeCompleted = false;
Expand Down Expand Up @@ -753,12 +815,15 @@ private void ReturnReadBufferIfEmpty()
}
}

private async ValueTask<int> ReadAsyncInternal<TIOAdapter>(TIOAdapter adapter, Memory<byte> buffer)
private async ValueTask<int> ReadAsyncInternal<TIOAdapter>(TIOAdapter adapter, Memory<byte> buffer, bool renegotiation = false)
geoffkizer marked this conversation as resolved.
Show resolved Hide resolved
where TIOAdapter : IReadWriteAdapter
{
if (Interlocked.Exchange(ref _nestedRead, 1) == 1)
if (!renegotiation)
{
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, nameof(SslStream.ReadAsync), "read"));
if (Interlocked.Exchange(ref _nestedRead, 1) == 1)
{
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, nameof(SslStream.ReadAsync), "read"));
}
}

Debug.Assert(_internalBuffer is null || _internalBufferCount > 0 || _decryptedBytesCount > 0, "_internalBuffer allocated when no data is buffered.");
Expand All @@ -769,10 +834,15 @@ private async ValueTask<int> ReadAsyncInternal<TIOAdapter>(TIOAdapter adapter, M
{
if (_decryptedBytesCount != 0)
{
if (renegotiation)
{
throw new InvalidOperationException(SR.net_ssl_renegotiate_data);
wfurt marked this conversation as resolved.
Show resolved Hide resolved
}

return CopyDecryptedData(buffer);
}

if (buffer.Length == 0 && _internalBuffer is null)
if (buffer.Length == 0 && _internalBuffer is null && !renegotiation)
{
// User requested a zero-byte read, and we have no data available in the buffer for processing.
// This zero-byte read indicates their desire to trade off the extra cost of a zero-byte read
Expand Down Expand Up @@ -844,7 +914,6 @@ private async ValueTask<int> ReadAsyncInternal<TIOAdapter>(TIOAdapter adapter, M
// If that happen before EncryptData() runs, _handshakeWaiter will be set to null
// and EncryptData() will work normally e.g. no waiting, just exclusion with DecryptData()


if (_sslAuthenticationOptions!.AllowRenegotiation || SslProtocol == SslProtocols.Tls13)
{
// create TCS only if we plan to proceed. If not, we will throw in block bellow outside of the lock.
Expand Down Expand Up @@ -880,8 +949,12 @@ private async ValueTask<int> ReadAsyncInternal<TIOAdapter>(TIOAdapter adapter, M
{
throw new IOException(SR.net_ssl_io_renego);
}

await ReplyOnReAuthenticationAsync(adapter, extraBuffer).ConfigureAwait(false);
if (renegotiation)
geoffkizer marked this conversation as resolved.
Show resolved Hide resolved
{
// if we received data frame instead, we would not be here but we would decrypt data and hit check above.
return 0;
}
// Loop on read.
continue;
}
Expand All @@ -897,7 +970,7 @@ private async ValueTask<int> ReadAsyncInternal<TIOAdapter>(TIOAdapter adapter, M
}
catch (Exception e)
{
if (e is IOException || (e is OperationCanceledException && adapter.CancellationToken.IsCancellationRequested))
if (e is IOException || (e is OperationCanceledException && adapter.CancellationToken.IsCancellationRequested) || renegotiation)
{
throw;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,17 @@ public override long Position

public override Task FlushAsync(CancellationToken cancellationToken) => InnerStream.FlushAsync(cancellationToken);

public virtual Task NegotiateClientCertificateAsync(CancellationToken cancellationToken = default)
{
ThrowIfExceptionalOrNotAuthenticated();
if (RemoteCertificate != null)
{
throw new InvalidOperationException(SR.net_ssl_certificate_exist);
}
geoffkizer marked this conversation as resolved.
Show resolved Hide resolved

return RenegotiateAsync(cancellationToken);
}

protected override void Dispose(bool disposing)
{
try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public static SecurityStatusPal InitializeSecurityContext(
return HandshakeInternal(credential, ref context, inputBuffer, ref outputBuffer, sslAuthenticationOptions);
}

public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentialsHandle, ref SafeDeleteSslContext? context, SslAuthenticationOptions sslAuthenticationOptions, out byte[]? outputBuffer)
{
throw new PlatformNotSupportedException();
}

public static SafeFreeCredentials AcquireCredentialsHandle(
SslStreamCertificateContext? certificateContext,
SslProtocols protocols,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public static SecurityStatusPal InitializeSecurityContext(
return HandshakeInternal(credential, ref context, inputBuffer, ref outputBuffer, sslAuthenticationOptions);
}

public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentialsHandle, ref SafeDeleteSslContext? context, SslAuthenticationOptions sslAuthenticationOptions, out byte[]? outputBuffer)
{
throw new PlatformNotSupportedException();
}

public static SafeFreeCredentials AcquireCredentialsHandle(
SslStreamCertificateContext? certificateContext,
SslProtocols protocols,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public static SecurityStatusPal InitializeSecurityContext(ref SafeFreeCredential
return HandshakeInternal(credential!, ref context, inputBuffer, ref outputBuffer, sslAuthenticationOptions);
}

public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentialsHandle, ref SafeDeleteSslContext? context, SslAuthenticationOptions sslAuthenticationOptions, out byte[]? outputBuffer)
{
throw new PlatformNotSupportedException();
}

public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateContext? certificateContext,
SslProtocols protocols, EncryptionPolicy policy, bool isServer)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ public static SecurityStatusPal InitializeSecurityContext(ref SafeFreeCredential
return SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode);
}

public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentialsHandle, ref SafeDeleteSslContext? context, SslAuthenticationOptions sslAuthenticationOptions, out byte[]? outputBuffer )
{
byte[]? output = Array.Empty<byte>();
SecurityStatusPal status = AcceptSecurityContext(ref credentialsHandle, ref context, Span<byte>.Empty, ref output, sslAuthenticationOptions);
outputBuffer = output;
return status;
}

public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateContext? certificateContext, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
{
// New crypto API supports TLS1.3 but it does not allow to force NULL encryption.
Expand Down
Loading