| File: System\Net\Security\SslStream.TlsSessionWedge.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.Security\src\System.Net.Security.csproj (System.Net.Security) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Security.Cryptography.X509Certificates; namespace System.Net.Security { // Routes the SslStream handshake hot-path through TlsSession. The PAL calls // underneath are unchanged; this is a wedge that proves TlsSession is // expressive enough to host SslStream's TLS engine. Compiled on Linux, // FreeBSD, and Windows. // // SslStream's _securityContext / _credentialsHandle fields are mirrored from // the TlsSession after each step so that the rest of SslStream (cert // validation, channel binding, ProcessHandshakeSuccess, renegotiation, // dispose) continues to work against the same SafeHandles. public partial class SslStream { private TlsBufferSession? _tlsSession; private void EnsureTlsSession() { if (_tlsSession is null) { Debug.Assert(_sslAuthenticationOptions != null); TlsContext ctx = TlsContext.WrapShared(_sslAuthenticationOptions); _tlsSession = new TlsBufferSession(); _tlsSession.SetContext(ctx); // SslStream owns post-handshake certificate validation (see // SslStream.IO.cs ProcessHandshakeSuccess). Tell TlsSession not to run // its own callback so the user delegate sees the SslStream as sender // and isn't invoked twice. _tlsSession.SuppressInternalCertificateValidation = true; } } private partial bool TryNextMessageViaTlsSession(ReadOnlySpan<byte> incomingBuffer, out ProtocolToken token, out int consumed) { EnsureTlsSession(); // The legacy GenerateToken acquires credentials before the first PAL call. // On Unix AcquireCredentialsHandle is a no-op (returns null), but // AcquireServerCredentials has a side effect we must preserve: it resolves // the cert via ServerCertSelectionDelegate / CertSelectionDelegate / // CertificateContext and assigns _sslAuthenticationOptions.CertificateContext, // which the OpenSSL handshake asserts on. AcquireClientCredentials similarly // bootstraps the client cert context. Run them once per handshake before the // first PAL call. bool refreshCredentialNeeded = _securityContext is null; bool cachedCreds = false; bool sendTrustList = false; byte[]? thumbPrint = null; try { if (refreshCredentialNeeded) { if (_sslAuthenticationOptions!.IsServer) { sendTrustList = _sslAuthenticationOptions.CertificateContext?.Trust?._sendTrustInHandshake ?? false; cachedCreds = AcquireServerCredentials(ref thumbPrint); } else { cachedCreds = AcquireClientCredentials(ref thumbPrint); } // SChannel-style PALs populate SslStream._credentialsHandle from // SslSessionsCache before the first ASC/ISC. Seed TlsSession with it // so its ref parameter starts from the cached handle rather than null. _tlsSession!.CredentialsHandle = _credentialsHandle; } token = _tlsSession!.HandshakeStepForSslStream(incomingBuffer, out consumed); // SChannel server-side ALPN: when the first ASC call returns // HandshakeStarted, the wire bytes were consumed but ASC stopped so we // can run SelectApplicationProtocol with the parsed ClientHello before // generating the ServerHello. Re-enter with no new input afterwards. if (token.Status.ErrorCode == SecurityStatusPalErrorCode.HandshakeStarted) { token.Status = SslStreamPal.SelectApplicationProtocol( _tlsSession.CredentialsHandle!, _tlsSession.SecurityContext!, _sslAuthenticationOptions!, _lastFrame.RawApplicationProtocols); if (token.Status.ErrorCode == SecurityStatusPalErrorCode.OK) { token = _tlsSession.HandshakeStepForSslStream(ReadOnlySpan<byte>.Empty, out _); } } // OpenSSL surfaces CredentialsNeeded when the local cert callback returned // null on the first call. SChannel surfaces it on a later ISC step after // the server's CertificateRequest is parsed. Re-run client cert selection // with newCredentialsRequested=true (mirrors legacy GenerateToken), then // drive the handshake again with no new input. Set refreshCredentialNeeded // so the finally-block caches the new cert-bound credential. if (token.Status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "TlsSession reported 'CredentialsNeeded'; reselecting client credentials."); } refreshCredentialNeeded = true; cachedCreds = AcquireClientCredentials(ref thumbPrint, newCredentialsRequested: true); _tlsSession.CredentialsHandle = _credentialsHandle; token = _tlsSession.HandshakeStepForSslStream(ReadOnlySpan<byte>.Empty, out _); } // Mirror handles so legacy SslStream paths (cert validation, channel binding, // ProcessHandshakeSuccess, renegotiation, Dispose) keep working unchanged. _securityContext = _tlsSession.SecurityContext; _credentialsHandle = _tlsSession.CredentialsHandle; } finally { if (refreshCredentialNeeded) { // Mirror legacy GenerateToken bookkeeping: the PAL has bumped the cred // refcount, so drop our reference. Then publish a fresh entry to // SslSessionsCache so subsequent connections to the same host can // resume the TLS session (Windows SChannel session ticket lives on // the cred handle). _credentialsHandle?.Dispose(); bool wouldCache = !cachedCreds && _securityContext is not null && !_securityContext.IsInvalid && _credentialsHandle is not null && !_credentialsHandle.IsInvalid; if (wouldCache) { SslSessionsCache.CacheCredential( _credentialsHandle!, thumbPrint, _sslAuthenticationOptions!.EnabledSslProtocols, _sslAuthenticationOptions.IsServer, _sslAuthenticationOptions.EncryptionPolicy, _sslAuthenticationOptions.CertificateRevocationCheckMode != X509RevocationMode.NoCheck, _sslAuthenticationOptions.AllowTlsResume, sendTrustList, _sslAuthenticationOptions.AllowRsaPssPadding, _sslAuthenticationOptions.AllowRsaPkcs1Padding); } } } return true; } } }