File: System\Net\Security\SslAuthenticationOptions.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.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;

namespace System.Net.Security
{
    internal sealed partial class SslAuthenticationOptions : IDisposable
    {
        // Hook invoked by OpenSSL's CertVerifyCallback to drive remote
        // certificate validation. Set by SslStream and by standalone TlsSession
        // so both flows share the same callback plumbing. Declared on the
        // cross-platform partial so the delegate type is resolvable in test
        // projects (unit-test fakes) that compile SslStream.cs on non-Linux
        // targets where the OpenSSL partial file isn't included.
        internal delegate bool VerifyRemoteCertificateCallback(
            X509Certificate2? certificate,
            X509Chain? chain,
            SslCertificateTrust? trust,
            ref ProtocolToken alertToken,
            ref SslPolicyErrors sslPolicyErrors,
            out X509ChainStatusFlags chainStatus);

        internal VerifyRemoteCertificateCallback? RemoteCertificateValidator { get; set; }

        internal const X509RevocationMode DefaultRevocationMode = X509RevocationMode.NoCheck;

        internal SslAuthenticationOptions()
        {
            TargetHost = string.Empty;
        }

        internal void UpdateOptions(SslClientAuthenticationOptions sslClientAuthenticationOptions)
        {
            if (CertValidationDelegate == null)
            {
                CertValidationDelegate = sslClientAuthenticationOptions.RemoteCertificateValidationCallback;
            }
            else if (sslClientAuthenticationOptions.RemoteCertificateValidationCallback != null &&
                     CertValidationDelegate != sslClientAuthenticationOptions.RemoteCertificateValidationCallback)
            {
                // Callback was set in constructor to different value.
                throw new InvalidOperationException(SR.Format(SR.net_conflicting_options, nameof(RemoteCertificateValidationCallback)));
            }

            if (CertSelectionDelegate == null)
            {
                CertSelectionDelegate = sslClientAuthenticationOptions.LocalCertificateSelectionCallback;
            }
            else if (sslClientAuthenticationOptions.LocalCertificateSelectionCallback != null &&
                     CertSelectionDelegate != sslClientAuthenticationOptions.LocalCertificateSelectionCallback)
            {
                throw new InvalidOperationException(SR.Format(SR.net_conflicting_options, nameof(LocalCertificateSelectionCallback)));
            }

            // Common options.
            AllowRenegotiation = sslClientAuthenticationOptions.AllowRenegotiation;
            AllowTlsResume = sslClientAuthenticationOptions.AllowTlsResume;
            ApplicationProtocols = sslClientAuthenticationOptions.ApplicationProtocols;
            CheckCertName = !(sslClientAuthenticationOptions.CertificateChainPolicy?.VerificationFlags.HasFlag(X509VerificationFlags.IgnoreInvalidName) == true);
            EnabledSslProtocols = FilterOutIncompatibleSslProtocols(sslClientAuthenticationOptions.EnabledSslProtocols);
            EncryptionPolicy = sslClientAuthenticationOptions.EncryptionPolicy;
            IsServer = false;
            RemoteCertRequired = true;
            CertificateContext = sslClientAuthenticationOptions.ClientCertificateContext;
            TargetHost = sslClientAuthenticationOptions.TargetHost ?? string.Empty;

            AllowRsaPssPadding = sslClientAuthenticationOptions.AllowRsaPssPadding;
            AllowRsaPkcs1Padding = sslClientAuthenticationOptions.AllowRsaPkcs1Padding;

            if (!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
            {
                if (!sslClientAuthenticationOptions.AllowRsaPssPadding || !sslClientAuthenticationOptions.AllowRsaPkcs1Padding)
                {
                    throw new PlatformNotSupportedException(SR.net_ssl_allow_rsa_padding_not_supported);
                }
            }

            // Client specific options.
            CertificateRevocationCheckMode = sslClientAuthenticationOptions.CertificateRevocationCheckMode;
            ClientCertificates = sslClientAuthenticationOptions.ClientCertificates;
            CipherSuitesPolicy = sslClientAuthenticationOptions.CipherSuitesPolicy;

            if (sslClientAuthenticationOptions.CertificateChainPolicy != null)
            {
                CertificateChainPolicy = sslClientAuthenticationOptions.CertificateChainPolicy.Clone();
            }
        }

        internal void UpdateOptions(ServerOptionsSelectionCallback optionCallback, object? state)
        {
            CheckCertName = false;
            TargetHost = string.Empty;
            IsServer = true;
            UserState = state;
            ServerOptionDelegate = optionCallback;
        }

        internal void UpdateOptions(SslServerAuthenticationOptions sslServerAuthenticationOptions)
        {
            if (sslServerAuthenticationOptions.ServerCertificate == null &&
                sslServerAuthenticationOptions.ServerCertificateContext == null &&
                sslServerAuthenticationOptions.ServerCertificateSelectionCallback == null &&
                CertSelectionDelegate == null)
            {
                throw new NotSupportedException(SR.net_ssl_io_no_server_cert);
            }

            if ((sslServerAuthenticationOptions.ServerCertificate != null ||
                 sslServerAuthenticationOptions.ServerCertificateContext != null ||
                 CertSelectionDelegate != null) &&
                sslServerAuthenticationOptions.ServerCertificateSelectionCallback != null)
            {
                throw new InvalidOperationException(SR.Format(SR.net_conflicting_options, nameof(ServerCertificateSelectionCallback)));
            }

            if (CertValidationDelegate == null)
            {
                CertValidationDelegate = sslServerAuthenticationOptions.RemoteCertificateValidationCallback;
            }
            else if (sslServerAuthenticationOptions.RemoteCertificateValidationCallback != null &&
                     CertValidationDelegate != sslServerAuthenticationOptions.RemoteCertificateValidationCallback)
            {
                // Callback was set in constructor to differet value.
                throw new InvalidOperationException(SR.Format(SR.net_conflicting_options, nameof(RemoteCertificateValidationCallback)));
            }

            IsServer = true;
            AllowRenegotiation = sslServerAuthenticationOptions.AllowRenegotiation;
            AllowTlsResume = sslServerAuthenticationOptions.AllowTlsResume;
            ApplicationProtocols = sslServerAuthenticationOptions.ApplicationProtocols;
            EnabledSslProtocols = FilterOutIncompatibleSslProtocols(sslServerAuthenticationOptions.EnabledSslProtocols);
            EncryptionPolicy = sslServerAuthenticationOptions.EncryptionPolicy;
            RemoteCertRequired = sslServerAuthenticationOptions.ClientCertificateRequired;
            CipherSuitesPolicy = sslServerAuthenticationOptions.CipherSuitesPolicy;
            CertificateRevocationCheckMode = sslServerAuthenticationOptions.CertificateRevocationCheckMode;

            AllowRsaPssPadding = sslServerAuthenticationOptions.AllowRsaPssPadding;
            AllowRsaPkcs1Padding = sslServerAuthenticationOptions.AllowRsaPkcs1Padding;

            if (!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
            {
                if (!sslServerAuthenticationOptions.AllowRsaPssPadding || !sslServerAuthenticationOptions.AllowRsaPkcs1Padding)
                {
                    throw new PlatformNotSupportedException(SR.net_ssl_allow_rsa_padding_not_supported);
                }
            }

            if (sslServerAuthenticationOptions.ServerCertificateContext != null)
            {
                CertificateContext = sslServerAuthenticationOptions.ServerCertificateContext;
            }
            else if (sslServerAuthenticationOptions.ServerCertificate != null)
            {
                X509Certificate2? certificateWithKey = sslServerAuthenticationOptions.ServerCertificate as X509Certificate2;

                if (certificateWithKey != null && certificateWithKey.HasPrivateKey)
                {
                    bool ocspFetch = LocalAppContextSwitches.EnableOcspStapling;
                    // given cert is X509Certificate2 with key. We can use it directly.
                    SetCertificateContextFromCert(certificateWithKey, !ocspFetch);
                }
                else
                {
                    // This is legacy fix-up. If the Certificate did not have key, we will search stores and we
                    // will try to find one with matching hash.
                    certificateWithKey = SslStream.FindCertificateWithPrivateKey(this, true, sslServerAuthenticationOptions.ServerCertificate);
                    if (certificateWithKey == null)
                    {
                        throw new AuthenticationException(SR.net_ssl_io_no_server_cert);
                    }

                    SetCertificateContextFromCert(certificateWithKey);
                }
            }

            if (sslServerAuthenticationOptions.ServerCertificateSelectionCallback != null)
            {
                ServerCertSelectionDelegate = sslServerAuthenticationOptions.ServerCertificateSelectionCallback;
            }

            if (sslServerAuthenticationOptions.CertificateChainPolicy != null)
            {
                CertificateChainPolicy = sslServerAuthenticationOptions.CertificateChainPolicy.Clone();
            }
        }

        private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols protocols)
        {
            if ((protocols & (SslProtocols.Tls12 | SslProtocols.Tls13)) != SslProtocols.None)
            {
#pragma warning disable 0618
                // SSL2 is mutually exclusive with >= TLS1.2
                protocols &= ~SslProtocols.Ssl2;
#pragma warning restore 0618
            }

            return protocols;
        }

        internal void SetCertificateContextFromCert(X509Certificate2 certificate, bool? noOcspFetch = null)
        {
            CertificateContext = SslStreamCertificateContext.Create(certificate, null, offline: false, null, noOcspFetch ?? true);
            OwnsCertificateContext = true;
        }

        // Shallow copy of the configuration carried by this bag. Per-handle/per-stream
        // state (SafeSslHandle, SslStream, RemoteCertificateValidator) is intentionally
        // not propagated, and the clone does not take ownership of CertificateContext
        // even if the source did.
        internal SslAuthenticationOptions Clone()
        {
            SslAuthenticationOptions copy = new SslAuthenticationOptions
            {
                AllowRenegotiation = AllowRenegotiation,
                TargetHost = TargetHost,
                ClientCertificates = ClientCertificates,
                ApplicationProtocols = ApplicationProtocols,
                IsServer = IsServer,
                CertificateContext = CertificateContext,
                OwnsCertificateContext = false,
                EnabledSslProtocols = EnabledSslProtocols,
                CertificateRevocationCheckMode = CertificateRevocationCheckMode,
                EncryptionPolicy = EncryptionPolicy,
                RemoteCertRequired = RemoteCertRequired,
                CheckCertName = CheckCertName,
                CertValidationDelegate = CertValidationDelegate,
                CertSelectionDelegate = CertSelectionDelegate,
                ServerCertSelectionDelegate = ServerCertSelectionDelegate,
                CipherSuitesPolicy = CipherSuitesPolicy,
                UserState = UserState,
                ServerOptionDelegate = ServerOptionDelegate,
                CertificateChainPolicy = CertificateChainPolicy,
                AllowTlsResume = AllowTlsResume,
                AllowRsaPssPadding = AllowRsaPssPadding,
                AllowRsaPkcs1Padding = AllowRsaPkcs1Padding,
                ForceSyncPal = ForceSyncPal,
            };
            return copy;
        }

        // Bulk-copy field values from another options bag into this one. Used by
        // TlsSession.SetContext to inherit a fully-configured server context's
        // options into an existing session (whose bag was originally created empty
        // from a deferred TlsContext.Create((SslServerAuthenticationOptions?)null)).
        // Mirrors the field set copied by Clone(). Session-scoped state (SafeSslHandle,
        // RemoteCertificateValidator, SocketHandle, ReplayPrefix, PreallocatedSslContext)
        // is intentionally NOT copied — those belong to the receiving session.
        internal void CopyFrom(SslAuthenticationOptions other)
        {
            AllowRenegotiation = other.AllowRenegotiation;
            TargetHost = other.TargetHost;
            ClientCertificates = other.ClientCertificates;
            ApplicationProtocols = other.ApplicationProtocols;
            IsServer = other.IsServer;
            CertificateContext = other.CertificateContext;
            OwnsCertificateContext = false;
            EnabledSslProtocols = other.EnabledSslProtocols;
            CertificateRevocationCheckMode = other.CertificateRevocationCheckMode;
            EncryptionPolicy = other.EncryptionPolicy;
            RemoteCertRequired = other.RemoteCertRequired;
            CheckCertName = other.CheckCertName;
            CertValidationDelegate = other.CertValidationDelegate;
            CertSelectionDelegate = other.CertSelectionDelegate;
            ServerCertSelectionDelegate = other.ServerCertSelectionDelegate;
            CipherSuitesPolicy = other.CipherSuitesPolicy;
            UserState = other.UserState;
            ServerOptionDelegate = other.ServerOptionDelegate;
            CertificateChainPolicy = other.CertificateChainPolicy;
            AllowTlsResume = other.AllowTlsResume;
            AllowRsaPssPadding = other.AllowRsaPssPadding;
            AllowRsaPkcs1Padding = other.AllowRsaPkcs1Padding;
            ForceSyncPal = other.ForceSyncPal;
        }

        internal bool AllowRenegotiation { get; set; }
        internal string TargetHost { get; set; }
        internal X509CertificateCollection? ClientCertificates { get; set; }
        internal List<SslApplicationProtocol>? ApplicationProtocols { get; set; }
        internal bool IsServer { get; set; }
        internal bool IsClient => !IsServer;
        internal SslStreamCertificateContext? CertificateContext { get; set; }
        // If true, the certificate context was created by the SslStream and
        // certificates inside should be disposed when no longer needed.
        internal bool OwnsCertificateContext { get; set; }
        internal SslProtocols EnabledSslProtocols { get; set; }
        internal X509RevocationMode CertificateRevocationCheckMode { get; set; }
        internal EncryptionPolicy EncryptionPolicy { get; set; }
        internal bool RemoteCertRequired { get; set; }
        internal bool CheckCertName { get; set; }
        internal RemoteCertificateValidationCallback? CertValidationDelegate { get; set; }
        internal LocalCertificateSelectionCallback? CertSelectionDelegate { get; set; }
        internal ServerCertificateSelectionCallback? ServerCertSelectionDelegate { get; set; }
        internal CipherSuitesPolicy? CipherSuitesPolicy { get; set; }
        internal object? UserState { get; set; }
        internal ServerOptionsSelectionCallback? ServerOptionDelegate { get; set; }
        internal X509ChainPolicy? CertificateChainPolicy { get; set; }
        internal bool AllowTlsResume { get; set; }
        internal bool AllowRsaPssPadding { get; set; }
        internal bool AllowRsaPkcs1Padding { get; set; }
        // Set by callers (e.g. TlsSession) whose state machine is intrinsically synchronous
        // and cannot use the async Network Framework PAL path on macOS.
        internal bool ForceSyncPal { get; set; }

#if TARGET_ANDROID
        internal SslStream.JavaProxy? SslStreamProxy { get; set; }
#endif

#if !TARGET_WINDOWS && !SYSNETSECURITY_NO_OPENSSL
        internal SslStream? SslStream { get; set; }
#endif

        public void Dispose()
        {
            if (OwnsCertificateContext && CertificateContext != null)
            {
                CertificateContext.ReleaseResources();
            }

#if TARGET_ANDROID
            SslStreamProxy?.Dispose();
#endif
        }
    }
}