| File: System\Net\Security\TlsContext.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.Diagnostics.CodeAnalysis; namespace System.Net.Security { /// <summary> /// Long-lived TLS configuration. Wraps an <see cref="SslAuthenticationOptions"/> /// constructed from either <see cref="SslClientAuthenticationOptions"/> or /// <see cref="SslServerAuthenticationOptions"/>. Role (client vs. server) is /// determined by which factory is used. /// </summary> /// <remarks> /// <para> /// Holds the resolved options bag. Session caches are reused on supported /// platforms; each <see cref="TlsSession"/> gets its own native context /// allocated lazily on the first handshake call. /// </para> /// <para> /// Lifetime: it is safe to dispose the <see cref="TlsContext"/> while /// <see cref="TlsSession"/> instances created from it are still in use. /// The underlying native TLS context (e.g. OpenSSL <c>SSL_CTX</c>, SChannel /// credentials handle) is reference-counted at the native layer, so each /// live session keeps it alive until the session itself is disposed. /// After the context is disposed, however, attempts to create new sessions /// from it throw <see cref="ObjectDisposedException"/>. /// </para> /// </remarks> [Experimental(Experimentals.LowLevelTlsDiagId, UrlFormat = Experimentals.SharedUrlFormat)] public sealed partial class TlsContext : IDisposable { // Non-readonly so Dispose can null it out; keeping the reference alive would // otherwise pin the (potentially large) options bag until TlsContext itself // is collected. In wedge mode the bag is owned by SslStream and Dispose // only forgets it; in owned mode Dispose also disposes the bag itself. private SslAuthenticationOptions? _options; // True when this context wraps an SslStream-owned options bag (wedge mode): // sessions share the same bag by reference and TlsContext does not dispose it, // does not allocate its own native context, and defers cred-handle lifetime to // the wrapper. False for standalone contexts created via Create(...). private readonly bool _isWedge; private readonly bool _templateHasServerOptions; // SChannel credentials handle (an SSPI CredHandle from AcquireCredentialsHandle). // Owned by TlsContext so it can be shared across multiple TlsSession instances. // In wedge mode (WrapShared) SslStream owns the lifetime and we skip disposing // here to avoid double-free. Stays null on Unix — the OpenSSL SSL_CTX equivalent // lives in TlsContext.OpenSsl.cs. internal SafeFreeCredentials? CredentialsHandle; private TlsContext(SslAuthenticationOptions options, bool isWedge, bool templateHasServerOptions) { _options = options; _isWedge = isWedge; _templateHasServerOptions = templateHasServerOptions; } internal SslAuthenticationOptions Options => _options ?? throw new ObjectDisposedException(nameof(TlsContext)); // Internal accessor for the obsolete EncryptionPolicy carried in options. Not exposed // publicly: a brand-new type should not re-publish a SYSLIB0040-obsolete concept. The // setting is honored at handshake time via the options bag; internal consumers that // need to introspect it (e.g. SslStream when re-platformed on TlsSession) read it here. internal EncryptionPolicy EncryptionPolicy => Options.EncryptionPolicy; // True when sessions should reuse the context's options bag directly (wedge mode). // False when each session must take a private clone before mutating any field. internal bool ShareOptions => _isWedge; // True if the template was constructed with non-null server options. Sessions seed // their own per-session HasServerOptions from this and flip it in SetContext. internal bool TemplateHasServerOptions => _templateHasServerOptions; // Returns a per-session options bag. For normal contexts each call returns a fresh // clone of the template so session-scoped mutations (TargetHost, SafeSslHandle, // resolved server cert, ...) don't leak between sessions. In wedge mode the bag is // owned by SslStream and we hand it out by reference. On platforms that own a // long-lived native context (e.g. OpenSSL SSL_CTX), the platform partial stamps it // onto the returned bag so the PAL can reuse it across sessions. internal SslAuthenticationOptions CreateSessionOptions() { SslAuthenticationOptions options = Options; SslAuthenticationOptions sessionOptions = _isWedge ? options : options.Clone(); sessionOptions.ForceSyncPal = true; AttachSharedNativeContext(sessionOptions); return sessionOptions; } // Platform hook: lets the OpenSSL partial attach the TlsContext-owned SSL_CTX to // the per-session options bag. No-op on Windows (which uses CredentialsHandle) and // on macOS/iOS/Android (no reusable native context to share). partial void AttachSharedNativeContext(SslAuthenticationOptions sessionOptions); // Platform hook: lets the OpenSSL partial dispose the owned SSL_CTX. No-op elsewhere. partial void DisposeNativeContext(); internal bool IsServer => Options.IsServer; /// <summary> /// Creates a server-side TLS context. /// </summary> /// <param name="options"> /// The server authentication options. May be a default-constructed instance /// (no server certificate, no <see cref="SslServerAuthenticationOptions.ServerCertificateSelectionCallback"/>) /// to defer server configuration: the first <see cref="TlsBufferSession.Handshake"/> /// call on a session built from that context returns /// <see cref="TlsOperationStatus.NeedsTlsContext"/> with /// <see cref="TlsSession.ClientHelloInfo"/> populated; the caller must then /// invoke <see cref="TlsSession.SetContext"/> before continuing the /// handshake. Useful for SNI-based options selection that involves I/O. /// </param> /// <returns>A new server-side <see cref="TlsContext"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="options"/> is <see langword="null"/>.</exception> public static TlsContext CreateServer(SslServerAuthenticationOptions options) { ArgumentNullException.ThrowIfNull(options); SslAuthenticationOptions bag = new SslAuthenticationOptions(); bool hasServerCredentials = options.ServerCertificate != null || options.ServerCertificateContext != null || options.ServerCertificateSelectionCallback != null; if (!hasServerCredentials) { // Deferred: caller will resolve the credential from SNI and hand back // a completed TlsContext via TlsSession.SetContext. bag.IsServer = true; return new TlsContext(bag, isWedge: false, templateHasServerOptions: false); } bag.UpdateOptions(options); return new TlsContext(bag, isWedge: false, templateHasServerOptions: true); } /// <summary> /// Creates a client-side TLS context. /// </summary> /// <param name="options">The client authentication options.</param> /// <returns>A new client-side <see cref="TlsContext"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="options"/> is <see langword="null"/>.</exception> /// <remarks> /// Peer certificate validation always runs outside the TLS state machine: after the /// handshake reaches the point at which the peer cert is available, <see cref="TlsBufferSession.Handshake"/> /// returns <see cref="TlsOperationStatus.NeedsCertificateValidation"/> and the caller /// must record a result via <see cref="TlsSession.SetRemoteCertificateValidationResult(System.Net.Security.SslPolicyErrors)"/> /// or <see cref="TlsSession.AcceptWithDefaultValidation"/>. Any /// <see cref="SslClientAuthenticationOptions.RemoteCertificateValidationCallback"/> set on /// <paramref name="options"/> is invoked only by <see cref="TlsSession.AcceptWithDefaultValidation"/>. /// </remarks> public static TlsContext CreateClient(SslClientAuthenticationOptions options) { ArgumentNullException.ThrowIfNull(options); SslAuthenticationOptions bag = new SslAuthenticationOptions(); bag.UpdateOptions(options); return new TlsContext(bag, isWedge: false, templateHasServerOptions: false); } // Used by SslStream's TlsSession wedge: share the existing options bag so // SNI / client-cert selection results made by SslStream are visible to the // TlsSession-driven PAL calls, and to avoid double Dispose on the bag. internal static TlsContext WrapShared(SslAuthenticationOptions sharedOptions) { Debug.Assert(sharedOptions != null); return new TlsContext(sharedOptions, isWedge: true, templateHasServerOptions: sharedOptions.IsServer); } public void Dispose() { if (_options is null) { return; } if (!_isWedge) { CredentialsHandle?.Dispose(); CredentialsHandle = null; DisposeNativeContext(); _options.Dispose(); } // In wedge mode the options bag is owned by SslStream and we must not // dispose it; but we do drop the reference so a disposed TlsContext no // longer keeps the (possibly large) bag alive. _options = null; } } }