-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
SslStream.IsMutuallyAuthenticated returns wrong value when cached creds are used #65563
Comments
Tagging subscribers to this area: @dotnet/ncl, @vcsjones Issue DetailsDescriptionWe use
see Remarks section This works fine so far. But subsequent connect with authenticate to the same device (server) now results in that the property This wrong behavior comes from the caching which is done by the SslStream as it is mentioned in the docs:
see Remarks section Reproduction Steps
Expected behaviorIn step 8: Actual behaviorIn step 8: Although a TLS 1.2 connection was established without mutual authentication. Regression?I can reproduce this issue with:
Known WorkaroundsThis not-recommended workaround solves the issue:
private static void ClearCache()
{
var sslAssembly = Assembly.GetAssembly(typeof(SslStream));
var sslSessionCacheClass = sslAssembly.GetType("System.Net.Security.SslSessionsCache");
var cachedCredsInfo = sslSessionCacheClass.GetField("s_cachedCreds", BindingFlags.NonPublic | BindingFlags.Static);
var cachedCreds = cachedCredsInfo.GetValue(null);
cachedCreds.GetType().GetMethod("Clear", BindingFlags.Public | BindingFlags.Instance).Invoke(cachedCreds, null);
} ConfigurationNo response Other informationMaybe this is related: #65539
|
I'm not sure this is truly wrong. If given session is created using TLS resume e.g. without full handshake it inherits everything from previous one. That may include peer's certificates. And therefore the authentication status reflects that - the authentication occured previously. We should investigate @rzikm to fully understand this. If anything, we may expose API to prevent TLS resume in cases when client wants to use different certificate for each session. |
Although not directly relevant, is a use case where client uses the same certificate to connect to a different server relevant? Because if the second server does not request client auth we arrive at situation where client believes that the connection is mutually authenticated but server (correctly) assumes otherwise. Could we get target endpoint/hostname into the key used in SSL session cache? |
We probably can. But connecting to different TargetHost should prevent TLS resume. And if I remember right we look for certificate at given session, not credentials. |
Sorry, I probably confused something. I remembered the PoC TLS resumption PR you had for Linux and somehow assumed this wasn't implemented yet on Windows either. Regarding the issue here, I think if the second connection was established using a TLS Session Ticket (i.e. without full TLS handshake), then |
Windows can do it for very long time since the handshake is not handled in process and the decision depends on Schannel. |
Since you touch this recently, can you take a look @rzikm to see if there is work and what the impact is? |
Looks to be real bug, I checked with wireshark and the there is no resumption in that case . It can be even reproduced in a single process by changing the server certificate: Minimal reprousing System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
X509Certificate2 clientCert = new X509Certificate2(@"C:\Users\radekzikmund\Downloads\TestDataCertificates\testclienteku.contoso.com.pfx", "PLACEHOLDER");
X509Certificate2 serverCert = new X509Certificate2(@"C:\Users\radekzikmund\Downloads\TestDataCertificates\testservereku.contoso.com.pfx", "PLACEHOLDER");
X509Certificate2 serverCert2 = new X509Certificate2(@"C:\Users\radekzikmund\Downloads\TestDataCertificates\testselfsignedservereku.contoso.com.pfx", "PLACEHOLDER");
TcpListener listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, 0));
listener.Start();
Console.WriteLine(listener.Server.LocalEndPoint);
async Task Run(X509Certificate2 clientCert, X509Certificate2 serverCert, bool requireClientCert)
{
Console.WriteLine($"clientCert: {clientCert.Subject}");
Console.WriteLine($"serverCert: {serverCert.Subject}");
Console.WriteLine($"requireClientCert: {requireClientCert}");
TcpClient tcpClient = new TcpClient();
TcpClient tcpServer;
{
var clientConnect = tcpClient.ConnectAsync((IPEndPoint)listener.LocalEndpoint);
tcpServer = await listener.AcceptTcpClientAsync();
await clientConnect;
}
using SslStream client = new SslStream(tcpClient.GetStream());
using SslStream server = new SslStream(tcpServer.GetStream());
var clientAuthTask = client.AuthenticateAsClientAsync(new SslClientAuthenticationOptions
{
TargetHost = "SomeTargetHost",
ClientCertificates = new X509Certificate2Collection(clientCert),
RemoteCertificateValidationCallback = delegate { return true; },
EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12
});
var serverAuthTask = server.AuthenticateAsServerAsync(new SslServerAuthenticationOptions
{
ClientCertificateRequired = requireClientCert,
ServerCertificate = serverCert,
RemoteCertificateValidationCallback = delegate { return true; },
EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12
});
await Task.WhenAll(clientAuthTask, serverAuthTask);
Console.WriteLine($"client.IsMutuallyAuthenticated: {client.IsMutuallyAuthenticated}");
Console.WriteLine($"server.IsMutuallyAuthenticated: {server.IsMutuallyAuthenticated}");
Console.WriteLine();
}
await Run(clientCert, serverCert, true);
await Run(clientCert, serverCert2, false);
listener.Stop(); Example output:
It seems to me that we need to somehow detect if the client actually sent the certificate. Perhaps by querying the security context after the handshake finishes? And, if needed, clear the Note that the issue affects all platforms, not only Windows. |
Triage:
|
@karelz: This is a common scenario for us. The remote server is an embedded device that can be configured by the customer to require a client certificate. Additionally, the customer can disable the require client certificate feature via hardware reset. We run into this issue when our client software (Windows, .NET 6) keeps running while the “require client certificate” feature gets disabled. When we re-connect to the device the |
reopening for Android |
Description
We use
SslStream
to connect with a device which allows to be configured for mutual authentication with client certificates. For that we useAuthenticateAsClientAsync
. After calling this method we check theIsMutuallyAuthenticated
property the way it is mentioned in the docs:see Remarks section
This works fine so far. But subsequent connect with authenticate to the same device (server) now results in that the property
IsMutuallyAuthenticated
returnstrue
even when this is not correct anymore.This wrong behavior comes from the caching which is done by the SslStream as it is mentioned in the docs:
see Remarks section
Reproduction Steps
AuthenticateAsClientAsync
and provide the client certificateIsMutuallyAuthenticated
returns trueAuthenticateAsClientAsync
and provide the client certificateIsMutuallyAuthenticated
returns now (see Expected and Actual behavior)Expected behavior
In step 8:
8.
IsMutuallyAuthenticated
should return falseActual behavior
In step 8:
8.
IsMutuallyAuthenticated
returns trueAlthough a TLS 1.2 connection was established without mutual authentication.
Regression?
I can reproduce this issue with:
Known Workarounds
This not-recommended workaround solves the issue:
Configuration
Other information
Maybe this is related: #65539
The text was updated successfully, but these errors were encountered: