You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
TlsSession.SetRemoteCertificateValidationResult rejection message ends in a literal {0} #135349
TlsSession.SetRemoteCertificateValidationResult builds its rejection exception from the net_ssl_io_cert_validation resource without SR.Format, so the exception message ends in a literal {0} instead of the SslPolicyErrors value the caller passed.
<dataname="net_ssl_io_cert_validation"xml:space="preserve">
<value>The remote certificate is invalid according to the validation procedure: {0}</value>
SslStream formats the same resource with its SslPolicyErrors:
A server TlsBufferSession with ClientCertificateRequired = true and no RemoteCertificateValidationCallback rejects the client certificate with SetRemoteCertificateValidationResult(SslPolicyErrors.RemoteCertificateChainErrors). The client is an SslStream over loopback TCP. The program prints the message of the AuthenticationException the session throws, once for TLS 1.2 and once for TLS 1.3.
usingSystem.Net;usingSystem.Net.Security;usingSystem.Net.Sockets;usingSystem.Security.Authentication;usingSystem.Security.Cryptography;usingSystem.Security.Cryptography.X509Certificates;foreach(SslProtocolsprotocolinnew[]{SslProtocols.Tls12,SslProtocols.Tls13}){Console.WriteLine($"{protocol}: {awaitRejectClientCertificateAsync(protocol)}");}staticasyncTask<string>RejectClientCertificateAsync(SslProtocolsprotocol){usingX509Certificate2serverCert=CreateCertificate("CN=localhost");usingX509Certificate2clientCert=CreateCertificate("CN=client");usingvarlistener=newTcpListener(IPAddress.Loopback,0);listener.Start();usingvarclientTcp=newTcpClient();awaitclientTcp.ConnectAsync(IPAddress.Loopback,((IPEndPoint)listener.LocalEndpoint).Port);usingTcpClientserverTcp=awaitlistener.AcceptTcpClientAsync();NetworkStreamtransport=serverTcp.GetStream();usingvarclientSsl=newSslStream(clientTcp.GetStream(),false,(_,_,_,_)=>true);_=clientSsl.AuthenticateAsClientAsync(newSslClientAuthenticationOptions{TargetHost="localhost",EnabledSslProtocols=protocol,ClientCertificates=newX509CertificateCollection{clientCert},});// No RemoteCertificateValidationCallback: the caller validates the client certificate.usingTlsContextcontext=TlsContext.CreateServer(newSslServerAuthenticationOptions{ServerCertificate=serverCert,EnabledSslProtocols=protocol,ClientCertificateRequired=true,});usingvarsession=newTlsBufferSession();session.SetContext(context);byte[]input=newbyte[32*1024];byte[]output=newbyte[32*1024];intinputLength=0;try{while(!session.IsHandshakeComplete){TlsOperationStatusstatus=session.Handshake(input.AsSpan(0,inputLength),output,outintconsumed,outintwritten);input.AsSpan(consumed,inputLength-consumed).CopyTo(input);inputLength-=consumed;awaittransport.WriteAsync(output.AsMemory(0,written));while(session.HasPendingOutput){session.DrainPendingOutput(output,outwritten);awaittransport.WriteAsync(output.AsMemory(0,written));}if(status==TlsOperationStatus.NeedsCertificateValidation){session.SetRemoteCertificateValidationResult(SslPolicyErrors.RemoteCertificateChainErrors);}elseif(status==TlsOperationStatus.NeedMoreData){intread=awaittransport.ReadAsync(input.AsMemory(inputLength));if(read==0){return"peer closed the connection";}inputLength+=read;}}session.Write("hello"u8,output,out_,out_);return"no exception";}catch(AuthenticationExceptionex){return$"{ex.GetType().Name}: {ex.Message}";}}staticX509Certificate2CreateCertificate(stringsubject){usingRSAkey=RSA.Create(2048);varrequest=newCertificateRequest(subject,key,HashAlgorithmName.SHA256,RSASignaturePadding.Pkcs1);usingX509Certificate2certificate=request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1),DateTimeOffset.UtcNow.AddDays(1));returnX509CertificateLoader.LoadPkcs12(certificate.Export(X509ContentType.Pfx),null);}
Build and run:
dotnet build -c Release -o out
dotnet out/repro.dll
Expected behavior
The message names the errors the caller passed in, as SslStream's message does:
Tls12: AuthenticationException: The remote certificate is invalid according to the validation procedure: RemoteCertificateChainErrors
Tls13: AuthenticationException: The remote certificate is invalid according to the validation procedure: RemoteCertificateChainErrors
Actual behavior
On .NET 11.0.0-rc.1.26420.103:
Tls12: AuthenticationException: The remote certificate is invalid according to the validation procedure: {0}
Tls13: AuthenticationException: The remote certificate is invalid according to the validation procedure: {0}
Regression?
No. Both lines came in with the commit that added TlsContext and TlsSession (#130366), and main at d148189 still has them unchanged.
Known Workarounds
The caller has the SslPolicyErrors value it passed to SetRemoteCertificateValidationResult and can log that value itself.
Configuration
.NET 11.0.0-rc.1.26420.103, Ubuntu 24.04.4 LTS, x64, OpenSSL 3.0.13. A local build of main at 6f1d933 prints the same {0} lines.
The repro was run on that Linux machine only. The code is not OpenSSL-specific: TlsSession.cs is compiled for every platform target except Android, Windows included:
<CompileInclude="System\Net\Security\TlsSession.cs"Condition="'$(TargetPlatformIdentifier)' != '' and '$(UseAndroidCrypto)' != 'true'" />
Other information
The fix is SR.Format(SR.net_ssl_io_cert_validation, errors) at both sites. In the repro on Linux (OpenSSL 3.0.13), both TLS 1.2 and TLS 1.3 reach the first site (the _isHandshakeComplete branch); the second site (the _resumeAfterCertValidation branch) was not reached.
On Windows 11 x64 the regression test in the fix's PR fails on the old code too: for TLS 1.2 and TLS 1.3, the server-side message does not contain RemoteCertificateChainErrors.
I have a fix with a test ready and will open a PR for it shortly. Could this be assigned to me?
Note
AI-generated, written at my direction and reviewed by me before posting.
Source read at dotnet/runtime 6f1d933. The repro ran on Ubuntu 24.04.4 LTS x64 with OpenSSL 3.0.13, on .NET 11.0.0-rc.1.26420.103 and on a ./build.sh clr+libs -rc Release build of 6f1d933, with dotnet build -c Release -o out and dotnet out/repro.dll (the local build through its testhost dotnet with DOTNET_ROLL_FORWARD=LatestMajor).
The Windows result is from that test, run on Windows 11 Pro for Workstations x64 (10.0.26300), against a build.cmd clr+libs -rc checked build of dotnet/runtime d148189 (same TlsSession.cs as 6f1d933), run with dotnet.cmd build /t:Test in src/libraries/System.Net.Security/tests/FunctionalTests.
Description
TlsSession.SetRemoteCertificateValidationResultbuilds its rejection exception from thenet_ssl_io_cert_validationresource withoutSR.Format, so the exception message ends in a literal{0}instead of theSslPolicyErrorsvalue the caller passed.TlsSession.cs:490-492
TlsSession.cs:506
The resource has a placeholder:
Strings.resx:218-219
SslStreamformats the same resource with itsSslPolicyErrors:SslStream.IO.cs:710
Reproduction Steps
A server
TlsBufferSessionwithClientCertificateRequired = trueand noRemoteCertificateValidationCallbackrejects the client certificate withSetRemoteCertificateValidationResult(SslPolicyErrors.RemoteCertificateChainErrors). The client is anSslStreamover loopback TCP. The program prints the message of theAuthenticationExceptionthe session throws, once for TLS 1.2 and once for TLS 1.3.repro.csproj:Program.cs:Build and run:
Expected behavior
The message names the errors the caller passed in, as
SslStream's message does:Actual behavior
On .NET 11.0.0-rc.1.26420.103:
Regression?
No. Both lines came in with the commit that added
TlsContextandTlsSession(#130366), andmainat d148189 still has them unchanged.Known Workarounds
The caller has the
SslPolicyErrorsvalue it passed toSetRemoteCertificateValidationResultand can log that value itself.Configuration
.NET 11.0.0-rc.1.26420.103, Ubuntu 24.04.4 LTS, x64, OpenSSL 3.0.13. A local build of
mainat 6f1d933 prints the same{0}lines.The repro was run on that Linux machine only. The code is not OpenSSL-specific:
TlsSession.csis compiled for every platform target except Android, Windows included:System.Net.Security.csproj:80-81
Other information
The fix is
SR.Format(SR.net_ssl_io_cert_validation, errors)at both sites. In the repro on Linux (OpenSSL 3.0.13), both TLS 1.2 and TLS 1.3 reach the first site (the_isHandshakeCompletebranch); the second site (the_resumeAfterCertValidationbranch) was not reached.On Windows 11 x64 the regression test in the fix's PR fails on the old code too: for TLS 1.2 and TLS 1.3, the server-side message does not contain
RemoteCertificateChainErrors.I have a fix with a test ready and will open a PR for it shortly. Could this be assigned to me?
Note
AI-generated, written at my direction and reviewed by me before posting.
Source read at dotnet/runtime 6f1d933. The repro ran on Ubuntu 24.04.4 LTS x64 with OpenSSL 3.0.13, on .NET 11.0.0-rc.1.26420.103 and on a
./build.sh clr+libs -rc Releasebuild of 6f1d933, withdotnet build -c Release -o outanddotnet out/repro.dll(the local build through its testhostdotnetwithDOTNET_ROLL_FORWARD=LatestMajor).The Windows result is from that test, run on Windows 11 Pro for Workstations x64 (10.0.26300), against a
build.cmd clr+libs -rc checkedbuild of dotnet/runtime d148189 (sameTlsSession.csas 6f1d933), run withdotnet.cmd build /t:Testinsrc/libraries/System.Net.Security/tests/FunctionalTests.