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 @@ -117,7 +117,14 @@ internal static unsafe ReadOnlySpan<byte> SslGetAlpnSelected(SafeSslHandle ssl)
internal static partial IntPtr SslGetPeerCertificate(SafeSslHandle ssl);

[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslGetPeerCertChain")]
internal static partial SafeSharedX509StackHandle SslGetPeerCertChain(SafeSslHandle ssl);
private static partial SafeSharedX509StackHandle SslGetPeerCertChain_private(SafeSslHandle ssl);

internal static SafeSharedX509StackHandle SslGetPeerCertChain(SafeSslHandle ssl)
{
return SafeInteriorHandle.OpenInteriorHandle(
SslGetPeerCertChain_private,
ssl);
}

[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslGetPeerFinished")]
internal static partial int SslGetPeerFinished(SafeSslHandle ssl, IntPtr buf, int count);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1029,8 +1029,9 @@ internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remot
return true;
}

_remoteCertificate = certificate;
if (_remoteCertificate == null)
// don't assign to _remoteCertificate yet, this prevents weird exceptions if SslStream is disposed in parallel with X509Chain building

if (certificate == null)
{
if (NetEventSource.Log.IsEnabled() && RemoteCertRequired) NetEventSource.Error(this, $"Remote certificate required, but no remote certificate received");
sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNotAvailable;
Expand Down Expand Up @@ -1072,15 +1073,17 @@ internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remot
sslPolicyErrors |= CertificateValidationPal.VerifyCertificateProperties(
_securityContext!,
chain,
_remoteCertificate,
certificate,
_sslAuthenticationOptions.CheckCertName,
_sslAuthenticationOptions.IsServer,
TargetHostNameHelper.NormalizeHostName(_sslAuthenticationOptions.TargetHost));
}

_remoteCertificate = certificate;

if (remoteCertValidationCallback != null)
{
success = remoteCertValidationCallback(this, _remoteCertificate, chain, sslPolicyErrors);
success = remoteCertValidationCallback(this, certificate, chain, sslPolicyErrors);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Security.Authentication;
using System.Threading.Tasks;

using Xunit;
Expand Down Expand Up @@ -59,6 +60,7 @@ public async Task Dispose_PendingReadAsync_ThrowsODE(bool bufferedRead)
using CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TestConfiguration.PassingTestTimeout);


(SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(leaveInnerStreamOpen: true);
using (client)
using (server)
Expand Down Expand Up @@ -102,5 +104,65 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout(
await Assert.ThrowsAnyAsync<ObjectDisposedException>(() => client.ReadAsync(readBuffer, cts.Token).AsTask());
}
}

[Fact]
[OuterLoop("Computationally expensive")]
public async Task Dispose_ParallelWithHandshake_ThrowsODE()
{
using CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TestConfiguration.PassingTestTimeout);

await Parallel.ForEachAsync(System.Linq.Enumerable.Range(0, 10000), cts.Token, async (i, token) =>
{
(Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams();

using SslStream client = new SslStream(clientStream);
using SslStream server = new SslStream(serverStream);
using X509Certificate2 serverCertificate = Configuration.Certificates.GetServerCertificate();
using X509Certificate2 clientCertificate = Configuration.Certificates.GetClientCertificate();

SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions()
{
TargetHost = Guid.NewGuid().ToString("N"),
RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true,
};

SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions()
{
ServerCertificate = serverCertificate,
};

var clientTask = Task.Run(() => client.AuthenticateAsClientAsync(clientOptions, cts.Token));
var serverTask = Task.Run(() => server.AuthenticateAsServerAsync(serverOptions, cts.Token));

// Dispose the instances while the handshake is in progress.
client.Dispose();
server.Dispose();

await ValidateExceptionAsync(clientTask);
await ValidateExceptionAsync(serverTask);
});

static async Task ValidateExceptionAsync(Task task)
{
try
{
await task;
}
catch (InvalidOperationException ex) when (ex.StackTrace?.Contains("System.IO.StreamBuffer.WriteAsync") ?? true)
{
// Writing to a disposed ConnectedStream (test only, does not happen with NetworkStream)
return;
}
catch (Exception ex) when (ex
is ObjectDisposedException // disposed locally
or IOException // disposed remotely (received unexpected EOF)
or AuthenticationException) // disposed wrapped in AuthenticationException or error from platform library
{
// expected
return;
}
}
}
}
}
Loading