| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Diagnostics; |
| | | 5 | | using System.Diagnostics.CodeAnalysis; |
| | | 6 | | |
| | | 7 | | namespace System.Net.Security |
| | | 8 | | { |
| | | 9 | | /// <summary> |
| | | 10 | | /// Long-lived TLS configuration. Wraps an <see cref="SslAuthenticationOptions"/> |
| | | 11 | | /// constructed from either <see cref="SslClientAuthenticationOptions"/> or |
| | | 12 | | /// <see cref="SslServerAuthenticationOptions"/>. Role (client vs. server) is |
| | | 13 | | /// determined by which factory is used. |
| | | 14 | | /// </summary> |
| | | 15 | | /// <remarks> |
| | | 16 | | /// <para> |
| | | 17 | | /// Holds the resolved options bag. Session caches are reused on supported |
| | | 18 | | /// platforms; each <see cref="TlsSession"/> gets its own native context |
| | | 19 | | /// allocated lazily on the first handshake call. |
| | | 20 | | /// </para> |
| | | 21 | | /// <para> |
| | | 22 | | /// Ownership: the <see cref="TlsContext"/> retains any |
| | | 23 | | /// <see cref="SslStreamCertificateContext"/> it built internally — i.e. |
| | | 24 | | /// when the caller supplied a raw <see cref="System.Security.Cryptography.X509Certificates.X509Certificate2"/> |
| | | 25 | | /// via <see cref="SslServerAuthenticationOptions.ServerCertificate"/> rather |
| | | 26 | | /// than a prebuilt <see cref="SslStreamCertificateContext"/>. A prebuilt |
| | | 27 | | /// certificate context passed via <see cref="SslServerAuthenticationOptions.ServerCertificateContext"/> |
| | | 28 | | /// remains owned by the caller. |
| | | 29 | | /// </para> |
| | | 30 | | /// <para> |
| | | 31 | | /// Lifetime: callers must keep the <see cref="TlsContext"/> alive for as long |
| | | 32 | | /// as any <see cref="TlsSession"/> derived from it is still in use. Native |
| | | 33 | | /// handles (OpenSSL <c>SSL_CTX</c>, SChannel credentials) are ref-counted at |
| | | 34 | | /// the native layer and stay valid for each live session, but the managed |
| | | 35 | | /// certificate chain retained by the context is not, mirroring the ownership |
| | | 36 | | /// model used by <see cref="SslStream"/>. After the context is disposed, |
| | | 37 | | /// attempts to create new sessions from it throw |
| | | 38 | | /// <see cref="ObjectDisposedException"/>. |
| | | 39 | | /// </para> |
| | | 40 | | /// </remarks> |
| | | 41 | | [Experimental(Experimentals.LowLevelTlsDiagId, UrlFormat = Experimentals.SharedUrlFormat)] |
| | | 42 | | public sealed partial class TlsContext : IDisposable |
| | | 43 | | { |
| | | 44 | | // Non-readonly so Dispose can null it out; keeping the reference alive would |
| | | 45 | | // otherwise pin the (potentially large) options bag until TlsContext itself |
| | | 46 | | // is collected. In wedge mode the bag is owned by SslStream and Dispose |
| | | 47 | | // only forgets it; in owned mode Dispose also disposes the bag itself. |
| | | 48 | | private SslAuthenticationOptions? _options; |
| | | 49 | | // True when this context wraps an SslStream-owned options bag (wedge mode): |
| | | 50 | | // sessions share the same bag by reference and TlsContext does not dispose it, |
| | | 51 | | // does not allocate its own native context, and defers cred-handle lifetime to |
| | | 52 | | // the wrapper. False for standalone contexts created via Create(...). |
| | | 53 | | private readonly bool _isWedge; |
| | | 54 | | private readonly bool _templateHasServerOptions; |
| | | 55 | | |
| | | 56 | | // SChannel credentials handle (an SSPI CredHandle from AcquireCredentialsHandle). |
| | | 57 | | // Owned by TlsContext so it can be shared across multiple TlsSession instances. |
| | | 58 | | // In wedge mode (WrapShared) SslStream owns the lifetime and we skip disposing |
| | | 59 | | // here to avoid double-free. Stays null on Unix — the OpenSSL SSL_CTX equivalent |
| | | 60 | | // lives in TlsContext.OpenSsl.cs. |
| | | 61 | | internal SafeFreeCredentials? CredentialsHandle; |
| | | 62 | | |
| | 0 | 63 | | private TlsContext(SslAuthenticationOptions options, bool isWedge, bool templateHasServerOptions) |
| | 0 | 64 | | { |
| | 0 | 65 | | _options = options; |
| | 0 | 66 | | _isWedge = isWedge; |
| | 0 | 67 | | _templateHasServerOptions = templateHasServerOptions; |
| | 0 | 68 | | } |
| | | 69 | | |
| | 0 | 70 | | internal SslAuthenticationOptions Options => _options ?? throw new ObjectDisposedException(nameof(TlsContext)); |
| | | 71 | | |
| | | 72 | | // Internal accessor for the obsolete EncryptionPolicy carried in options. Not exposed |
| | | 73 | | // publicly: a brand-new type should not re-publish a SYSLIB0040-obsolete concept. The |
| | | 74 | | // setting is honored at handshake time via the options bag; internal consumers that |
| | | 75 | | // need to introspect it (e.g. SslStream when re-platformed on TlsSession) read it here. |
| | | 76 | | internal EncryptionPolicy EncryptionPolicy => Options.EncryptionPolicy; |
| | | 77 | | |
| | | 78 | | // True when sessions should reuse the context's options bag directly (wedge mode). |
| | | 79 | | // False when each session must take a private clone before mutating any field. |
| | 0 | 80 | | internal bool ShareOptions => _isWedge; |
| | | 81 | | |
| | | 82 | | // True if the template was constructed with non-null server options. Sessions seed |
| | | 83 | | // their own per-session HasServerOptions from this and flip it in SetContext. |
| | 0 | 84 | | internal bool TemplateHasServerOptions => _templateHasServerOptions; |
| | | 85 | | |
| | | 86 | | // Returns a per-session options bag. For normal contexts each call returns a fresh |
| | | 87 | | // clone of the template so session-scoped mutations (TargetHost, SafeSslHandle, |
| | | 88 | | // resolved server cert, ...) don't leak between sessions. In wedge mode the bag is |
| | | 89 | | // owned by SslStream and we hand it out by reference. On platforms that own a |
| | | 90 | | // long-lived native context (e.g. OpenSSL SSL_CTX), the platform partial stamps it |
| | | 91 | | // onto the returned bag so the PAL can reuse it across sessions. |
| | | 92 | | internal SslAuthenticationOptions CreateSessionOptions() |
| | 0 | 93 | | { |
| | 0 | 94 | | SslAuthenticationOptions options = Options; |
| | 0 | 95 | | SslAuthenticationOptions sessionOptions = _isWedge ? options : options.Clone(); |
| | 0 | 96 | | sessionOptions.ForceSyncPal = true; |
| | | 97 | | AttachSharedNativeContext(sessionOptions); |
| | 0 | 98 | | return sessionOptions; |
| | 0 | 99 | | } |
| | | 100 | | |
| | | 101 | | // Platform hook: lets the OpenSSL partial attach the TlsContext-owned SSL_CTX to |
| | | 102 | | // the per-session options bag. No-op on Windows (which uses CredentialsHandle) and |
| | | 103 | | // on macOS/iOS/Android (no reusable native context to share). |
| | | 104 | | partial void AttachSharedNativeContext(SslAuthenticationOptions sessionOptions); |
| | | 105 | | |
| | | 106 | | // Platform hook: lets the OpenSSL partial dispose the owned SSL_CTX. No-op elsewhere. |
| | | 107 | | partial void DisposeNativeContext(); |
| | | 108 | | |
| | 0 | 109 | | internal bool IsServer => Options.IsServer; |
| | | 110 | | |
| | | 111 | | /// <summary> |
| | | 112 | | /// Creates a server-side TLS context. |
| | | 113 | | /// </summary> |
| | | 114 | | /// <param name="options"> |
| | | 115 | | /// The server authentication options. May be a default-constructed instance |
| | | 116 | | /// (no server certificate, no <see cref="SslServerAuthenticationOptions.ServerCertificateSelectionCallback"/>) |
| | | 117 | | /// to defer server configuration: the first <see cref="TlsBufferSession.Handshake"/> |
| | | 118 | | /// call on a session built from that context returns |
| | | 119 | | /// <see cref="TlsOperationStatus.NeedsTlsContext"/> with |
| | | 120 | | /// <see cref="TlsSession.ClientHelloInfo"/> populated; the caller must then |
| | | 121 | | /// invoke <see cref="TlsSession.SetContext"/> before continuing the |
| | | 122 | | /// handshake. Useful for SNI-based options selection that involves I/O. |
| | | 123 | | /// </param> |
| | | 124 | | /// <returns>A new server-side <see cref="TlsContext"/>.</returns> |
| | | 125 | | /// <exception cref="ArgumentNullException"><paramref name="options"/> is <see langword="null"/>.</exception> |
| | | 126 | | public static TlsContext CreateServer(SslServerAuthenticationOptions options) |
| | 0 | 127 | | { |
| | 0 | 128 | | ArgumentNullException.ThrowIfNull(options); |
| | 0 | 129 | | SslAuthenticationOptions bag = new SslAuthenticationOptions(); |
| | 0 | 130 | | bool hasServerCredentials = |
| | 0 | 131 | | options.ServerCertificate != null || |
| | 0 | 132 | | options.ServerCertificateContext != null || |
| | 0 | 133 | | options.ServerCertificateSelectionCallback != null; |
| | | 134 | | |
| | 0 | 135 | | if (!hasServerCredentials) |
| | 0 | 136 | | { |
| | | 137 | | // Deferred: caller will resolve the credential from SNI and hand back |
| | | 138 | | // a completed TlsContext via TlsSession.SetContext. |
| | 0 | 139 | | bag.IsServer = true; |
| | 0 | 140 | | return new TlsContext(bag, isWedge: false, templateHasServerOptions: false); |
| | | 141 | | } |
| | | 142 | | |
| | 0 | 143 | | bag.UpdateOptions(options); |
| | 0 | 144 | | return new TlsContext(bag, isWedge: false, templateHasServerOptions: true); |
| | 0 | 145 | | } |
| | | 146 | | |
| | | 147 | | /// <summary> |
| | | 148 | | /// Creates a client-side TLS context. |
| | | 149 | | /// </summary> |
| | | 150 | | /// <param name="options">The client authentication options.</param> |
| | | 151 | | /// <returns>A new client-side <see cref="TlsContext"/>.</returns> |
| | | 152 | | /// <exception cref="ArgumentNullException"><paramref name="options"/> is <see langword="null"/>.</exception> |
| | | 153 | | /// <remarks> |
| | | 154 | | /// Peer certificate validation always runs outside the TLS state machine: after the |
| | | 155 | | /// handshake reaches the point at which the peer cert is available, <see cref="TlsBufferSession.Handshake"/> |
| | | 156 | | /// returns <see cref="TlsOperationStatus.NeedsCertificateValidation"/> and the caller |
| | | 157 | | /// must record a result via <see cref="TlsSession.SetRemoteCertificateValidationResult(System.Net.Security.SslP |
| | | 158 | | /// or <see cref="TlsSession.AcceptWithDefaultValidation"/>. Any |
| | | 159 | | /// <see cref="SslClientAuthenticationOptions.RemoteCertificateValidationCallback"/> set on |
| | | 160 | | /// <paramref name="options"/> is invoked only by <see cref="TlsSession.AcceptWithDefaultValidation"/>. |
| | | 161 | | /// </remarks> |
| | | 162 | | public static TlsContext CreateClient(SslClientAuthenticationOptions options) |
| | 0 | 163 | | { |
| | 0 | 164 | | ArgumentNullException.ThrowIfNull(options); |
| | 0 | 165 | | SslAuthenticationOptions bag = new SslAuthenticationOptions(); |
| | 0 | 166 | | bag.UpdateOptions(options); |
| | 0 | 167 | | return new TlsContext(bag, isWedge: false, templateHasServerOptions: false); |
| | 0 | 168 | | } |
| | | 169 | | |
| | | 170 | | // Used by SslStream's TlsSession wedge: share the existing options bag so |
| | | 171 | | // SNI / client-cert selection results made by SslStream are visible to the |
| | | 172 | | // TlsSession-driven PAL calls, and to avoid double Dispose on the bag. |
| | | 173 | | internal static TlsContext WrapShared(SslAuthenticationOptions sharedOptions) |
| | 0 | 174 | | { |
| | 0 | 175 | | Debug.Assert(sharedOptions != null); |
| | 0 | 176 | | return new TlsContext(sharedOptions, isWedge: true, templateHasServerOptions: sharedOptions.IsServer); |
| | 0 | 177 | | } |
| | | 178 | | |
| | | 179 | | public void Dispose() |
| | 0 | 180 | | { |
| | 0 | 181 | | if (_options is null) |
| | 0 | 182 | | { |
| | 0 | 183 | | return; |
| | | 184 | | } |
| | | 185 | | |
| | 0 | 186 | | if (!_isWedge) |
| | 0 | 187 | | { |
| | 0 | 188 | | CredentialsHandle?.Dispose(); |
| | 0 | 189 | | CredentialsHandle = null; |
| | | 190 | | DisposeNativeContext(); |
| | 0 | 191 | | _options.Dispose(); |
| | 0 | 192 | | } |
| | | 193 | | |
| | | 194 | | // In wedge mode the options bag is owned by SslStream and we must not |
| | | 195 | | // dispose it; but we do drop the reference so a disposed TlsContext no |
| | | 196 | | // longer keeps the (possibly large) bag alive. |
| | 0 | 197 | | _options = null; |
| | 0 | 198 | | } |
| | | 199 | | } |
| | | 200 | | } |
| | | 201 | | |