< Summary

Line coverage
18%
Covered lines: 7
Uncovered lines: 30
Coverable lines: 37
Total lines: 1692
Line coverage: 18.9%
Branch coverage
13%
Covered branches: 3
Total branches: 22
Branch coverage: 13.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
EnsureAvailableSpace(...)0%880%
AsMemory()100%110%
ReleasePayload()50%6670%
GetException()0%220%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs

#LineLine coverage
 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
 4using System.Buffers;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Runtime.CompilerServices;
 8using System.Runtime.ExceptionServices;
 9using System.Security;
 10using System.Security.Authentication;
 11using System.Security.Authentication.ExtendedProtection;
 12using System.Security.Cryptography;
 13using System.Security.Cryptography.X509Certificates;
 14using System.Threading;
 15using System.Threading.Tasks;
 16
 17namespace System.Net.Security
 18{
 19    public partial class SslStream
 20    {
 21
 22
 23        private SafeFreeCredentials? _credentialsHandle;
 24        // Keeps a cache hit alive until SSPI has retained its own credential reference.
 25        private SafeFreeCredentials? _cachedCredentialsHandle;
 26
 27#if TARGET_APPLE
 28        // on OSX, we have two implementations of SafeDeleteContext, so store a reference to the base class
 29        private SafeDeleteContext? _securityContext;
 30#else
 31        internal SafeDeleteSslContext? _securityContext;
 32#endif
 33
 34        private SslConnectionInfo _connectionInfo;
 35        private X509Certificate? _selectedClientCertificate;
 36        private X509Certificate2? _remoteCertificate;
 37        private bool _remoteCertificateExposed;
 38
 39        // -1 for uninitialized, 0 for false, 1 for true, should be accessed via IsLocalClientCertificateUsed property
 40        private int _localClientCertificateUsed = -1;
 41
 42        // These are the MAX encrypt buffer output sizes, not the actual sizes.
 43        private int _headerSize = 5; //ATTN must be set to at least 5 by default
 44        private int _trailerSize = 16;
 45        private int _maxDataSize = 16354;
 46
 47        private static readonly Oid s_serverAuthOid = new Oid("1.3.6.1.5.5.7.3.1", "1.3.6.1.5.5.7.3.1");
 48        private static readonly Oid s_clientAuthOid = new Oid("1.3.6.1.5.5.7.3.2", "1.3.6.1.5.5.7.3.2");
 49
 50        //
 51        // Protocol properties
 52        //
 53        //   LocalServerCertificate - local certificate for server mode channel
 54        //   LocalClientCertificate - selected certificated used in the client channel mode otherwise null
 55        //   IsRemoteCertificateAvailable - true if the remote side has provided a certificate
 56        //   HeaderSize             - Header & trailer sizes used in the TLS stream
 57        //   TrailerSize -
 58        //
 59        internal X509Certificate? LocalServerCertificate
 60        {
 61            get
 62            {
 63                return _sslAuthenticationOptions.CertificateContext?.TargetCertificate;
 64            }
 65        }
 66
 67        // IsLocalCertificateUsed is expensive, but it does not change during the lifetime of the SslStream except for r
 68        // can cache the value.
 69        private bool IsLocalClientCertificateUsed
 70        {
 71            get
 72            {
 73                if (_localClientCertificateUsed == -1)
 74                {
 75                    _localClientCertificateUsed = CertificateValidationPal.IsLocalCertificateUsed(_credentialsHandle, _s
 76                        ? 1
 77                        : 0;
 78                }
 79
 80                return _localClientCertificateUsed == 1;
 81            }
 82        }
 83
 84        internal X509Certificate? LocalClientCertificate
 85        {
 86            get
 87            {
 88                if (_selectedClientCertificate != null && IsLocalClientCertificateUsed)
 89                {
 90                    return _selectedClientCertificate;
 91                }
 92
 93                return null;
 94            }
 95        }
 96
 97        internal bool IsRemoteCertificateAvailable
 98        {
 99            get
 100            {
 101                return _remoteCertificate != null;
 102            }
 103        }
 104
 105        internal ChannelBinding? GetChannelBinding(ChannelBindingKind kind)
 106        {
 107            ChannelBinding? result = null;
 108            if (_securityContext != null)
 109            {
 110                result = SslStreamPal.QueryContextChannelBinding(_securityContext, kind);
 111            }
 112
 113            return result;
 114        }
 115
 116        internal int MaxDataSize
 117        {
 118            get
 119            {
 120                return _maxDataSize;
 121            }
 122        }
 123
 124        internal bool IsValidContext
 125        {
 126            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 127            get
 128            {
 129                return !(_securityContext == null || _securityContext.IsInvalid);
 130            }
 131        }
 132
 133        internal bool RemoteCertRequired
 134        {
 135            get
 136            {
 137                return _sslAuthenticationOptions.RemoteCertRequired;
 138            }
 139        }
 140
 141        internal void CloseContext()
 142        {
 143            if (!_remoteCertificateExposed)
 144            {
 145                _remoteCertificate?.Dispose();
 146                _remoteCertificate = null;
 147            }
 148
 149            _securityContext?.Dispose();
 150            _credentialsHandle?.Dispose();
 151            ReleaseCachedCredentials();
 152
 153            _sslAuthenticationOptions.Dispose();
 154        }
 155
 156        private void ReleaseCachedCredentials() =>
 157            Interlocked.Exchange(ref _cachedCredentialsHandle, null)?.DangerousRelease();
 158
 159        //
 160        // SECURITY: we open a private key container on behalf of the caller
 161        // and we require the caller to have permission associated with that operation.
 162        //
 163        internal static unsafe X509Certificate2? FindCertificateWithPrivateKey(object instance, bool isServer, X509Certi
 164        {
 165            if (certificate == null)
 166            {
 167                return null;
 168            }
 169
 170            if (NetEventSource.Log.IsEnabled())
 171                NetEventSource.Log.LocatingPrivateKey(certificate, instance);
 172
 173            try
 174            {
 175                // Protecting from X509Certificate2 derived classes.
 176                X509Certificate2? certEx = MakeEx(certificate);
 177
 178                if (certEx is null)
 179                {
 180                    return null;
 181                }
 182
 183                if (certEx.HasPrivateKey)
 184                {
 185                    if (NetEventSource.Log.IsEnabled())
 186                        NetEventSource.Log.CertIsType2(instance);
 187
 188                    return certEx;
 189                }
 190
 191                Span<byte> certHash = stackalloc byte[SHA512.HashSizeInBytes];
 192                bool ret = certEx.TryGetCertHash(HashAlgorithmName.SHA512, certHash, out int written);
 193                Debug.Assert(ret && written == certHash.Length);
 194
 195                if (!object.ReferenceEquals(certificate, certEx))
 196                {
 197                    certEx.Dispose();
 198                }
 199
 200                // ELSE Try the MY user and machine stores for private key check.
 201                // For server side mode MY machine store takes priority.
 202                X509Certificate2? found =
 203                    FindCertWithPrivateKey(isServer, certHash) ??
 204                    FindCertWithPrivateKey(!isServer, certHash);
 205                if (found is not null)
 206                {
 207                    return found;
 208                }
 209
 210                X509Certificate2? FindCertWithPrivateKey(bool isServer, ReadOnlySpan<byte> certHash)
 211                {
 212                    if (CertificateValidationPal.EnsureStoreOpened(isServer) is X509Store store)
 213                    {
 214                        X509Certificate2Collection certs = store.Certificates;
 215                        X509Certificate2Collection found = certs.FindByThumbprint(HashAlgorithmName.SHA512, certHash);
 216                        X509Certificate2? cert = null;
 217                        try
 218                        {
 219                            if (found.Count > 0)
 220                            {
 221                                cert = found[0];
 222                                if (cert.HasPrivateKey)
 223                                {
 224                                    if (NetEventSource.Log.IsEnabled())
 225                                    {
 226                                        NetEventSource.Log.FoundCertInStore(isServer, instance);
 227                                    }
 228
 229                                    return cert;
 230                                }
 231                            }
 232                        }
 233                        finally
 234                        {
 235                            for (int i = 0; i < certs.Count; i++)
 236                            {
 237                                X509Certificate2 toDispose = certs[i];
 238                                if (!ReferenceEquals(toDispose, cert))
 239                                {
 240                                    toDispose.Dispose();
 241                                }
 242                            }
 243                        }
 244                    }
 245
 246                    return null;
 247                }
 248            }
 249            catch (CryptographicException)
 250            {
 251            }
 252
 253            if (NetEventSource.Log.IsEnabled())
 254                NetEventSource.Log.NotFoundCertInStore(instance);
 255            return null;
 256        }
 257
 258        private static X509Certificate2? MakeEx(X509Certificate certificate)
 259        {
 260            Debug.Assert(certificate != null);
 261
 262            if (certificate.GetType() == typeof(X509Certificate2))
 263            {
 264                return (X509Certificate2)certificate;
 265            }
 266
 267            X509Certificate2? certificateEx = null;
 268            try
 269            {
 270                if (certificate.Handle != IntPtr.Zero)
 271                {
 272                    certificateEx = new X509Certificate2(certificate);
 273                }
 274            }
 275            catch (SecurityException) { }
 276            catch (CryptographicException) { }
 277
 278            return certificateEx;
 279        }
 280
 281        //
 282        // Get certificate_authorities list, according to RFC 5246, Section 7.4.4.
 283        // Used only by client SSL code, never returns null.
 284        //
 285        private string[] GetRequestCertificateAuthorities()
 286        {
 287            string[] issuers = Array.Empty<string>();
 288
 289            if (IsValidContext)
 290            {
 291                issuers = CertificateValidationPal.GetRequestCertificateAuthorities(_securityContext!);
 292            }
 293            return issuers;
 294        }
 295
 296        internal X509Certificate2? SelectClientCertificate()
 297        {
 298            X509Certificate? clientCertificate = null;        // candidate certificate that can come from the user callb
 299            X509Certificate2? selectedCert = null;            // final selected cert (ensured that it does have private 
 300            List<X509Certificate>? filteredCerts = null;      // This is an intermediate client certs collection that tr
 301            string[] issuers;                                 // This is a list of issuers sent by the server, only vali
 302
 303            if (_sslAuthenticationOptions.CertificateContext != null)
 304            {
 305                if (NetEventSource.Log.IsEnabled())
 306                    NetEventSource.Log.CertificateFromCertContext(this);
 307
 308                //
 309                // SslStreamCertificateContext can only be constructed with a cert with a
 310                // private key, so we don't have to do any further processing.
 311                //
 312
 313                _selectedClientCertificate = _sslAuthenticationOptions.CertificateContext.TargetCertificate;
 314                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Selected cert = {_selectedClientCertific
 315                return _sslAuthenticationOptions.CertificateContext.TargetCertificate;
 316            }
 317            else if (_sslAuthenticationOptions.CertSelectionDelegate != null)
 318            {
 319                if (NetEventSource.Log.IsEnabled())
 320                    NetEventSource.Info(this, "Calling CertificateSelectionCallback");
 321
 322                X509Certificate2? remoteCert = null;
 323                try
 324                {
 325                    issuers = GetRequestCertificateAuthorities();
 326                    remoteCert = CertificateValidationPal.GetRemoteCertificate(_securityContext);
 327                    _sslAuthenticationOptions.ClientCertificates ??= new X509CertificateCollection();
 328                    clientCertificate = _sslAuthenticationOptions.CertSelectionDelegate(this, _sslAuthenticationOptions.
 329                }
 330                finally
 331                {
 332                    remoteCert?.Dispose();
 333                }
 334
 335                if (clientCertificate != null)
 336                {
 337                    EnsureInitialized(ref filteredCerts).Add(clientCertificate);
 338                    if (NetEventSource.Log.IsEnabled())
 339                        NetEventSource.Log.CertificateFromDelegate(this);
 340                }
 341                else
 342                {
 343                    if (_sslAuthenticationOptions.ClientCertificates == null || _sslAuthenticationOptions.ClientCertific
 344                    {
 345                        if (NetEventSource.Log.IsEnabled())
 346                            NetEventSource.Log.NoDelegateNoClientCert(this);
 347                    }
 348                    else
 349                    {
 350                        if (NetEventSource.Log.IsEnabled())
 351                            NetEventSource.Log.NoDelegateButClientCert(this);
 352                    }
 353                }
 354            }
 355            else if (_credentialsHandle == null && _sslAuthenticationOptions.ClientCertificates != null && _sslAuthentic
 356            {
 357                // This is where we attempt to restart a session by picking the FIRST cert from the collection.
 358                // Otherwise it is either server sending a client cert request or the session is renegotiated.
 359                clientCertificate = _sslAuthenticationOptions.ClientCertificates[0];
 360                if (clientCertificate != null)
 361                {
 362                    EnsureInitialized(ref filteredCerts).Add(clientCertificate);
 363                }
 364
 365                if (NetEventSource.Log.IsEnabled())
 366                    NetEventSource.Log.AttemptingRestartUsingCert(clientCertificate, this);
 367            }
 368            else if (_sslAuthenticationOptions.ClientCertificates != null && _sslAuthenticationOptions.ClientCertificate
 369            {
 370                //
 371                // This should be a server request for the client cert sent over currently anonymous sessions.
 372                //
 373                issuers = GetRequestCertificateAuthorities();
 374
 375                if (NetEventSource.Log.IsEnabled())
 376                {
 377                    if (issuers == null || issuers.Length == 0)
 378                    {
 379                        NetEventSource.Log.NoIssuersTryAllCerts(this);
 380                    }
 381                    else
 382                    {
 383                        NetEventSource.Log.LookForMatchingCerts(issuers.Length, this);
 384                    }
 385                }
 386
 387                for (int i = 0; i < _sslAuthenticationOptions.ClientCertificates.Count; ++i)
 388                {
 389                    //
 390                    // Make sure we add only if the cert matches one of the issuers.
 391                    // If no issuers were sent and then try all client certs starting with the first one.
 392                    //
 393                    if (issuers != null && issuers.Length != 0)
 394                    {
 395                        X509Certificate2? certificateEx = null;
 396                        X509Chain? chain = null;
 397                        try
 398                        {
 399                            certificateEx = MakeEx(_sslAuthenticationOptions.ClientCertificates[i]);
 400                            if (certificateEx == null)
 401                            {
 402                                continue;
 403                            }
 404
 405                            if (NetEventSource.Log.IsEnabled())
 406                                NetEventSource.Info(this, $"Root cert: {certificateEx}");
 407
 408                            chain = new X509Chain();
 409
 410                            chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
 411                            chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreInvalidName;
 412                            chain.Build(certificateEx);
 413                            bool found = false;
 414
 415                            //
 416                            // We ignore any errors happened with chain.
 417                            //
 418                            if (chain.ChainElements.Count > 0)
 419                            {
 420                                int elementsCount = chain.ChainElements.Count;
 421                                for (int ii = 0; ii < elementsCount; ++ii)
 422                                {
 423                                    string issuer = chain.ChainElements[ii].Certificate!.Issuer;
 424                                    found = Array.IndexOf(issuers, issuer) >= 0;
 425                                    if (found)
 426                                    {
 427                                        if (NetEventSource.Log.IsEnabled())
 428                                            NetEventSource.Info(this, $"Matched {issuer}");
 429                                        break;
 430                                    }
 431                                    if (NetEventSource.Log.IsEnabled())
 432                                        NetEventSource.Info(this, $"No match: {issuer}");
 433                                }
 434                            }
 435
 436                            if (!found)
 437                            {
 438                                continue;
 439                            }
 440                        }
 441                        finally
 442                        {
 443                            if (chain != null)
 444                            {
 445                                int elementsCount = chain.ChainElements.Count;
 446                                for (int element = 0; element < elementsCount; element++)
 447                                {
 448                                    chain.ChainElements[element].Certificate.Dispose();
 449                                }
 450
 451                                chain.Dispose();
 452                            }
 453
 454                            if (certificateEx != null && (object)certificateEx != (object)_sslAuthenticationOptions.Clie
 455                            {
 456                                certificateEx.Dispose();
 457                            }
 458                        }
 459                    }
 460
 461                    if (NetEventSource.Log.IsEnabled())
 462                        NetEventSource.Log.SelectedCert(_sslAuthenticationOptions.ClientCertificates[i], this);
 463
 464                    EnsureInitialized(ref filteredCerts).Add(_sslAuthenticationOptions.ClientCertificates[i]);
 465                }
 466            }
 467
 468            clientCertificate = null;
 469
 470            if (NetEventSource.Log.IsEnabled())
 471            {
 472                if (filteredCerts != null && filteredCerts.Count != 0)
 473                {
 474                    NetEventSource.Log.CertsAfterFiltering(filteredCerts.Count, this);
 475                    NetEventSource.Log.FindingMatchingCerts(this);
 476                }
 477                else
 478                {
 479                    NetEventSource.Log.CertsAfterFiltering(0, this);
 480                    NetEventSource.Info(this, "No client certificate to choose from");
 481                }
 482            }
 483
 484            //
 485            // ATTN: When the client cert was returned by the user callback OR it was guessed AND it has no private key,
 486            //       THEN anonymous (no client cert) credential will be used.
 487            //
 488            // SECURITY: Accessing X509 cert Credential is disabled for semitrust.
 489            // We no longer need to demand for unmanaged code permissions.
 490            // FindCertificateWithPrivateKey should do the right demand for us.
 491            if (filteredCerts != null)
 492            {
 493                for (int i = 0; i < filteredCerts.Count; ++i)
 494                {
 495                    clientCertificate = filteredCerts[i];
 496                    if ((selectedCert = FindCertificateWithPrivateKey(this, _sslAuthenticationOptions.IsServer, clientCe
 497                    {
 498                        break;
 499                    }
 500
 501                    clientCertificate = null;
 502                    selectedCert = null;
 503                }
 504            }
 505
 506            Debug.Assert((object?)clientCertificate == (object?)selectedCert || clientCertificate!.Equals(selectedCert),
 507
 508            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Selected cert = {selectedCert}");
 509
 510            _selectedClientCertificate = clientCertificate;
 511
 512            return selectedCert;
 513        }
 514
 515        /*++
 516            AcquireCredentials - Attempts to find Client Credential
 517            Information, that can be sent to the server.  In our case,
 518            this is only Client Certificates, that we have Credential Info.
 519
 520            How it works:
 521                case 0: Cert Selection delegate is present
 522                        Always use its result as the client cert answer.
 523                        Try to use cached credential handle whenever feasible.
 524                        Do not use cached anonymous creds if the delegate has returned null
 525                        and the collection is not empty (allow responding with the cert later).
 526
 527                case 1: Certs collection is empty
 528                        Always use the same statically acquired anonymous SSL Credential
 529
 530                case 2: Before our Connection with the Server
 531                        If we have a cached credential handle keyed by first X509Certificate
 532                        **content** in the passed collection, then we use that cached
 533                        credential and hoping to restart a session.
 534
 535                        Otherwise create a new anonymous (allow responding with the cert later).
 536
 537                case 3: After our Connection with the Server (i.e. during handshake or re-handshake)
 538                        The server has requested that we send it a Certificate then
 539                        we Enumerate a list of server sent Issuers trying to match against
 540                        our list of Certificates, the first match is sent to the server.
 541
 542                        Once we got a cert we again try to match cached credential handle if possible.
 543                        This will not restart a session but helps minimizing the number of handles we create.
 544
 545                In the case of an error getting a Certificate or checking its private Key we fall back
 546                to the behavior of having no certs, case 1.
 547
 548            Returns: True if cached creds were used, false otherwise.
 549
 550        --*/
 551
 552        internal bool AcquireClientCredentials(ref byte[]? thumbPrint, bool newCredentialsRequested = false)
 553        {
 554            ReleaseCachedCredentials();
 555
 556            // Acquire possible Client Certificate information and set it on the handle.
 557            bool cachedCred = false;                   // this is a return result from this method.
 558
 559            X509Certificate2? selectedCert = SelectClientCertificate();
 560
 561            if (newCredentialsRequested)
 562            {
 563                UpdateCertificateContext(selectedCert);
 564
 565                if (SslStreamPal.TryUpdateClintCertificate(_credentialsHandle, _securityContext, _sslAuthenticationOptio
 566                {
 567                    // If the certificate was updated we do not need to deal with the credential handle.
 568                    return false;
 569                }
 570            }
 571
 572            SslStreamCertificateContext? certificateContextToRestore = null;
 573            try
 574            {
 575                // Try to locate cached creds first.
 576                //
 577                // SECURITY: selectedCert ref if not null is a safe object that does not depend on possible **user** inh
 578                //
 579                byte[]? guessedThumbPrint = selectedCert?.GetCertHash(HashAlgorithmName.SHA512);
 580                SafeFreeCredentials? cachedCredentialHandle = SslSessionsCache.TryCachedCredential(
 581                    guessedThumbPrint,
 582                    _sslAuthenticationOptions.EnabledSslProtocols,
 583                    _sslAuthenticationOptions.IsServer,
 584                    _sslAuthenticationOptions.EncryptionPolicy,
 585                    _sslAuthenticationOptions.CertificateRevocationCheckMode != X509RevocationMode.NoCheck,
 586                    _sslAuthenticationOptions.AllowTlsResume,
 587                    sendTrustList: false,
 588                    _sslAuthenticationOptions.AllowRsaPssPadding,
 589                    _sslAuthenticationOptions.AllowRsaPkcs1Padding);
 590                Volatile.Write(ref _cachedCredentialsHandle, cachedCredentialHandle);
 591
 592                // We can probably do some optimization here. If the selectedCert is returned by the delegate
 593                // we can always go ahead and use the certificate to create our credential
 594                // (instead of going anonymous as we do here).
 595                if (!newCredentialsRequested &&
 596                    cachedCredentialHandle == null &&
 597                    selectedCert != null &&
 598                    SslStreamPal.StartMutualAuthAsAnonymous)
 599                {
 600                    if (NetEventSource.Log.IsEnabled())
 601                        NetEventSource.Info(this, "Reset to anonymous session.");
 602
 603                    // IIS does not renegotiate a restarted session if client cert is needed.
 604                    // So we don't want to reuse **anonymous** cached credential for a new SSL connection if the client 
 605                    // The following block happens if client did specify a certificate but no cached creds were found in
 606                    // Since we don't restart a session the server side can still challenge for a client cert.
 607                    if ((object?)_selectedClientCertificate != (object?)selectedCert)
 608                    {
 609                        selectedCert.Dispose();
 610                    }
 611
 612                    guessedThumbPrint = null;
 613                    selectedCert = null;
 614                    _selectedClientCertificate = null;
 615                    certificateContextToRestore = _sslAuthenticationOptions.CertificateContext;
 616                    _sslAuthenticationOptions.CertificateContext = null;
 617                }
 618
 619                if (cachedCredentialHandle != null)
 620                {
 621                    if (NetEventSource.Log.IsEnabled())
 622                        NetEventSource.Log.UsingCachedCredential(this);
 623                    _credentialsHandle = cachedCredentialHandle;
 624                    cachedCred = true;
 625                    UpdateCertificateContext(selectedCert);
 626                }
 627                else
 628                {
 629                    UpdateCertificateContext(selectedCert);
 630
 631                    _credentialsHandle = AcquireCredentialsHandle(_sslAuthenticationOptions, newCredentialsRequested);
 632                    thumbPrint = guessedThumbPrint; // Delay until here in case something above threw.
 633                }
 634            }
 635            finally
 636            {
 637                UpdateCertificateContext(selectedCert);
 638                if (certificateContextToRestore is not null)
 639                {
 640                    Debug.Assert(_sslAuthenticationOptions.CertificateContext is null);
 641                    _sslAuthenticationOptions.CertificateContext = certificateContextToRestore;
 642                }
 643            }
 644
 645            return cachedCred;
 646
 647            void UpdateCertificateContext(X509Certificate2? cert)
 648            {
 649                if (cert != null && _sslAuthenticationOptions.CertificateContext == null)
 650                {
 651                    _sslAuthenticationOptions.SetCertificateContextFromCert(cert);
 652                }
 653            }
 654        }
 655
 656        private static List<T> EnsureInitialized<T>(ref List<T>? list) => list ??= new List<T>();
 657
 658        //
 659        // Acquire Server Side Certificate information and set it on the class.
 660        //
 661        private bool AcquireServerCredentials(ref byte[]? thumbPrint)
 662        {
 663            ReleaseCachedCredentials();
 664
 665            X509Certificate? localCertificate = null;
 666            X509Certificate2? selectedCert = null;
 667            bool cachedCred = false;
 668
 669            // There are three options for selecting the server certificate. When
 670            // selecting which to use, we prioritize the new ServerCertSelectionDelegate
 671            // API. If the new API isn't used we call LocalCertSelectionCallback (for compat
 672            // with .NET Framework), and if neither is set we fall back to using CertificateContext.
 673            if (_sslAuthenticationOptions.ServerCertSelectionDelegate != null)
 674            {
 675                localCertificate = _sslAuthenticationOptions.ServerCertSelectionDelegate(this, _sslAuthenticationOptions
 676                if (localCertificate == null)
 677                {
 678                    if (NetEventSource.Log.IsEnabled())
 679                        NetEventSource.Error(this, $"ServerCertSelectionDelegate returned no certificate for '{_sslAuthe
 680                    throw new AuthenticationException(SR.net_ssl_io_no_server_cert);
 681                }
 682
 683                if (NetEventSource.Log.IsEnabled())
 684                    NetEventSource.Info(this, "ServerCertSelectionDelegate selected Cert");
 685            }
 686            else if (_sslAuthenticationOptions.CertSelectionDelegate != null)
 687            {
 688                X509CertificateCollection tempCollection = new X509CertificateCollection();
 689                tempCollection.Add(_sslAuthenticationOptions.CertificateContext!.TargetCertificate!);
 690                // We pass string.Empty here to maintain strict compatibility with .NET Framework.
 691                localCertificate = _sslAuthenticationOptions.CertSelectionDelegate(this, string.Empty, tempCollection, n
 692                if (localCertificate == null)
 693                {
 694                    if (NetEventSource.Log.IsEnabled())
 695                        NetEventSource.Error(this, $"CertSelectionDelegate returned no certificaete for '{_sslAuthentica
 696                    throw new NotSupportedException(SR.net_ssl_io_no_server_cert);
 697                }
 698
 699                if (NetEventSource.Log.IsEnabled())
 700                    NetEventSource.Info(this, "CertSelectionDelegate selected Cert");
 701            }
 702            else if (_sslAuthenticationOptions.CertificateContext != null)
 703            {
 704                selectedCert = _sslAuthenticationOptions.CertificateContext.TargetCertificate;
 705            }
 706
 707            if (selectedCert == null)
 708            {
 709                // We will get here if certificate was selected via legacy callback using X509Certificate
 710                // Fail immediately if no certificate was given.
 711                if (localCertificate == null)
 712                {
 713                    if (NetEventSource.Log.IsEnabled())
 714                        NetEventSource.Error(this, "Certiticate callback returned no certificaete.");
 715                    throw new NotSupportedException(SR.net_ssl_io_no_server_cert);
 716                }
 717
 718                // SECURITY: Accessing X509 cert Credential is disabled for semitrust.
 719                // We no longer need to demand for unmanaged code permissions.
 720                // EnsurePrivateKey should do the right demand for us.
 721                selectedCert = FindCertificateWithPrivateKey(this, _sslAuthenticationOptions.IsServer, localCertificate)
 722
 723                if (selectedCert == null)
 724                {
 725                    throw new NotSupportedException(SR.net_ssl_io_no_server_cert);
 726                }
 727
 728                Debug.Assert(localCertificate.Equals(selectedCert), "'selectedCert' does not match 'localCertificate'.")
 729                _sslAuthenticationOptions.SetCertificateContextFromCert(selectedCert);
 730            }
 731
 732            Debug.Assert(_sslAuthenticationOptions.CertificateContext != null);
 733            //
 734            // Note selectedCert is a safe ref possibly cloned from the user passed Cert object
 735            //
 736            byte[] guessedThumbPrint = selectedCert.GetCertHash(HashAlgorithmName.SHA512); bool sendTrustedList = _sslAu
 737            SafeFreeCredentials? cachedCredentialHandle = SslSessionsCache.TryCachedCredential(guessedThumbPrint,
 738                                                                _sslAuthenticationOptions.EnabledSslProtocols,
 739                                                                _sslAuthenticationOptions.IsServer,
 740                                                                _sslAuthenticationOptions.EncryptionPolicy,
 741                                                                _sslAuthenticationOptions.CertificateRevocationCheckMode
 742                                                                _sslAuthenticationOptions.AllowTlsResume,
 743                                                                sendTrustedList,
 744                                                                _sslAuthenticationOptions.AllowRsaPssPadding,
 745                                                                _sslAuthenticationOptions.AllowRsaPkcs1Padding);
 746            Volatile.Write(ref _cachedCredentialsHandle, cachedCredentialHandle);
 747            if (cachedCredentialHandle != null)
 748            {
 749                _credentialsHandle = cachedCredentialHandle;
 750                cachedCred = true;
 751            }
 752            else
 753            {
 754                _credentialsHandle = AcquireCredentialsHandle(_sslAuthenticationOptions);
 755                thumbPrint = guessedThumbPrint;
 756            }
 757
 758            return cachedCred;
 759        }
 760
 761        private static SafeFreeCredentials? AcquireCredentialsHandle(SslAuthenticationOptions sslAuthenticationOptions, 
 762        {
 763            SafeFreeCredentials? cred = SslStreamPal.AcquireCredentialsHandle(sslAuthenticationOptions, newCredentialsRe
 764
 765            if (sslAuthenticationOptions.CertificateContext != null && cred != null)
 766            {
 767                //
 768                // Since the SafeFreeCredentials can be cached and reused, it may happen on long running processes that 
 769                // the chain expires and all subsequent connections would send expired intermediate certificates. Find t
 770                // NotAfter timestamp on the chain and use it as expiration timestamp for the credentials.
 771                // This provides an opportunity to recreate the credentials with an alternative (and still valid)
 772                // certificate chain.
 773                //
 774                SslStreamCertificateContext certificateContext = sslAuthenticationOptions.CertificateContext;
 775                cred._expiry = GetExpiryTimestamp(certificateContext);
 776
 777                if (cred._expiry < DateTime.UtcNow)
 778                {
 779                    //
 780                    // The CertificateContext from auth options is recreated just before creating the SafeFreeCredential
 781                    // it was provided by the user code, it may still contain the (now expired) certificate chain. Such 
 782                    // effectively disable caching as it would lead to creating new credentials for each connection. We 
 783                    // a temporary certificate context (which builds a new chain with hopefully more recent chain).
 784                    //
 785                    certificateContext = certificateContext.Duplicate();
 786                    cred._expiry = GetExpiryTimestamp(certificateContext);
 787                }
 788
 789                static DateTime GetExpiryTimestamp(SslStreamCertificateContext certificateContext)
 790                {
 791                    DateTime expiry = certificateContext.TargetCertificate.NotAfter;
 792
 793                    foreach (X509Certificate2 cert in certificateContext.IntermediateCertificates)
 794                    {
 795                        if (cert.NotAfter < expiry)
 796                        {
 797                            expiry = cert.NotAfter;
 798                        }
 799                    }
 800
 801                    return expiry.ToUniversalTime();
 802                }
 803            }
 804
 805            return cred;
 806        }
 807
 808        //
 809        internal ProtocolToken NextMessage(ReadOnlySpan<byte> incomingBuffer, out int consumed)
 810        {
 811            if (!LocalAppContextSwitches.UseLegacySslStreamHandshake &&
 812                TryNextMessageViaTlsSession(incomingBuffer, out ProtocolToken wedged, out consumed))
 813            {
 814                if (NetEventSource.Log.IsEnabled() && wedged.Failed)
 815                {
 816                    NetEventSource.Error(this, $"Authentication failed. Status: {wedged.Status}, Exception message: {wed
 817                }
 818                return wedged;
 819            }
 820
 821            ProtocolToken token = GenerateToken(incomingBuffer, out consumed);
 822            if (NetEventSource.Log.IsEnabled())
 823            {
 824                if (token.Failed)
 825                {
 826                    NetEventSource.Error(this, $"Authentication failed. Status: {token.Status}, Exception message: {toke
 827                }
 828            }
 829
 830            return token;
 831        }
 832
 833        private partial bool TryNextMessageViaTlsSession(ReadOnlySpan<byte> incomingBuffer, out ProtocolToken token, out
 834
 835        /*++
 836            GenerateToken - Called after each successive state
 837            in the Client - Server handshake.  This function
 838            generates a set of bytes that will be sent next to
 839            the server.  The server responds, each response,
 840            is pass then into this function, again, and the cycle
 841            repeats until successful connection, or failure.
 842
 843            Input:
 844                input  - bytes from the wire
 845            Return:
 846                token - ProtocolToken with status and optionally buffer.
 847        --*/
 848        private ProtocolToken GenerateToken(ReadOnlySpan<byte> inputBuffer, out int consumed)
 849        {
 850            bool cachedCreds = false;
 851            bool sendTrustList = false;
 852            byte[]? thumbPrint = null;
 853
 854            ProtocolToken token = default;
 855            token.RentBuffer = true;
 856
 857            // We need to try get credentials at the beginning.
 858            // _credentialsHandle may be always null on some platforms but
 859            // _securityContext will be allocated on first call.
 860            bool refreshCredentialNeeded = _securityContext == null;
 861            try
 862            {
 863                do
 864                {
 865                    thumbPrint = null;
 866                    if (refreshCredentialNeeded)
 867                    {
 868                        cachedCreds = _sslAuthenticationOptions.IsServer
 869                                        ? AcquireServerCredentials(ref thumbPrint)
 870                                        : AcquireClientCredentials(ref thumbPrint);
 871                    }
 872
 873                    if (_sslAuthenticationOptions.IsServer)
 874                    {
 875                        sendTrustList = _sslAuthenticationOptions.CertificateContext?.Trust?._sendTrustInHandshake ?? fa
 876
 877                        token = SslStreamPal.AcceptSecurityContext(
 878                                      ref _credentialsHandle!,
 879                                      ref _securityContext,
 880                                      inputBuffer,
 881                                      out consumed,
 882                                      _sslAuthenticationOptions);
 883                        if (token.Status.ErrorCode == SecurityStatusPalErrorCode.HandshakeStarted)
 884                        {
 885                            token.Status = SslStreamPal.SelectApplicationProtocol(
 886                                        _credentialsHandle!,
 887                                        _securityContext!,
 888                                        _sslAuthenticationOptions,
 889                                        _lastFrame.RawApplicationProtocols);
 890
 891                            if (token.Status.ErrorCode == SecurityStatusPalErrorCode.OK)
 892                            {
 893                                token = SslStreamPal.AcceptSecurityContext(
 894                                        ref _credentialsHandle!,
 895                                        ref _securityContext,
 896                                        ReadOnlySpan<byte>.Empty,
 897                                        out _,
 898                                        _sslAuthenticationOptions);
 899                            }
 900                        }
 901                    }
 902                    else
 903                    {
 904                        string hostName = TargetHostNameHelper.NormalizeHostName(_sslAuthenticationOptions.TargetHost);
 905                        token = SslStreamPal.InitializeSecurityContext(
 906                                       ref _credentialsHandle!,
 907                                       ref _securityContext,
 908                                       hostName,
 909                                       inputBuffer,
 910                                       out consumed,
 911                                       _sslAuthenticationOptions);
 912
 913                        if (token.Status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded)
 914                        {
 915                            if (NetEventSource.Log.IsEnabled())
 916                                NetEventSource.Info(this, "InitializeSecurityContext() returned 'CredentialsNeeded'.");
 917
 918                            refreshCredentialNeeded = true;
 919                            cachedCreds = AcquireClientCredentials(ref thumbPrint, newCredentialsRequested: true);
 920
 921                            token = SslStreamPal.InitializeSecurityContext(
 922                                       ref _credentialsHandle!,
 923                                       ref _securityContext,
 924                                       hostName,
 925                                       ReadOnlySpan<byte>.Empty,
 926                                       out _,
 927                                       _sslAuthenticationOptions);
 928                        }
 929                    }
 930
 931#if TARGET_APPLE
 932                    if (token.Status.ErrorCode == SecurityStatusPalErrorCode.CertValidationNeeded)
 933                    {
 934                        token = VerifyRemoteCertificateAndGenerateNextToken(token);
 935                    }
 936#endif
 937                } while (cachedCreds && _credentialsHandle == null);
 938            }
 939            finally
 940            {
 941                ReleaseCachedCredentials();
 942                if (refreshCredentialNeeded)
 943                {
 944                    //
 945                    // Assuming the ISC or ASC has referenced the credential,
 946                    // we want to call dispose so to decrement the effective ref count.
 947                    //
 948                    _credentialsHandle?.Dispose();
 949
 950                    //
 951                    // This call may bump up the credential reference count further.
 952                    // Note that thumbPrint is retrieved from a safe cert object that was possible cloned from the user 
 953                    //
 954                    if (!cachedCreds && _securityContext != null && !_securityContext.IsInvalid && _credentialsHandle !=
 955                    {
 956                        SslSessionsCache.CacheCredential(
 957                            _credentialsHandle,
 958                            thumbPrint,
 959                            _sslAuthenticationOptions.EnabledSslProtocols,
 960                            _sslAuthenticationOptions.IsServer,
 961                            _sslAuthenticationOptions.EncryptionPolicy,
 962                            _sslAuthenticationOptions.CertificateRevocationCheckMode != X509RevocationMode.NoCheck,
 963                            _sslAuthenticationOptions.AllowTlsResume,
 964                            sendTrustList,
 965                            _sslAuthenticationOptions.AllowRsaPssPadding,
 966                            _sslAuthenticationOptions.AllowRsaPkcs1Padding);
 967                    }
 968                }
 969            }
 970
 971            return token;
 972        }
 973
 974#if TARGET_APPLE
 975        private ProtocolToken VerifyRemoteCertificateAndGenerateNextToken(ProtocolToken token)
 976        {
 977            // SecureTransport pauses the handshake (errSSL{Server,Client}AuthCompleted) before
 978            // any bytes are produced for the next handshake flight, so the pending-writes buffer
 979            // drained into token should be empty here. Assert to catch any future regression
 980            // that would silently drop handshake bytes.
 981            Debug.Assert(token.Size == 0, "Expected empty payload at CertValidationNeeded pause; dropping non-empty payl
 982            token.ReleasePayload();
 983
 984            ProtocolToken alertToken = default;
 985            SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
 986
 987            if (!VerifyRemoteCertificate(_sslAuthenticationOptions.CertificateContext?.Trust, ref alertToken, ref sslPol
 988            {
 989                alertToken.Status = new SecurityStatusPal(SecurityStatusPalErrorCode.CertValidationFailed, CreateCertifi
 990                return alertToken;
 991            }
 992
 993            return GenerateToken(ReadOnlySpan<byte>.Empty, out _);
 994        }
 995#endif
 996
 997        internal ProtocolToken Renegotiate()
 998        {
 999            Debug.Assert(_securityContext != null);
 1000
 1001            return SslStreamPal.Renegotiate(
 1002                                      ref _credentialsHandle!,
 1003                                      ref _securityContext,
 1004                                      _sslAuthenticationOptions);
 1005        }
 1006
 1007        /*++
 1008            ProcessHandshakeSuccess -
 1009               Called on successful completion of Handshake -
 1010               used to set header/trailer sizes for encryption use
 1011
 1012            Fills in the information about established protocol
 1013        --*/
 1014        internal void ProcessHandshakeSuccess()
 1015        {
 1016            SslStreamPal.QueryContextStreamSizes(_securityContext!, out StreamSizes streamSizes);
 1017
 1018            _headerSize = streamSizes.Header;
 1019            _trailerSize = streamSizes.Trailer;
 1020            _maxDataSize = streamSizes.MaximumMessage;
 1021            Debug.Assert(_maxDataSize > 0);
 1022
 1023            SslStreamPal.QueryContextConnectionInfo(_securityContext!, ref _connectionInfo);
 1024#if DEBUG
 1025            if (NetEventSource.Log.IsEnabled())
 1026            {
 1027                // This keeps the property alive only for tests via reflection
 1028                // Otherwise it could be optimized out as it is not used by production code.
 1029                NetEventSource.Info(this, $"TLS resumed {_connectionInfo.TlsResumed}");
 1030            }
 1031#endif
 1032        }
 1033
 1034        private ProtocolToken EncryptData(ReadOnlyMemory<byte> buffer)
 1035        {
 1036            ThrowIfExceptionalOrNotAuthenticated();
 1037
 1038            lock (_handshakeLock)
 1039            {
 1040                if (_handshakeWaiter != null)
 1041                {
 1042                    ProtocolToken waitToken = default;
 1043                    // avoid waiting under lock.
 1044                    waitToken.Status = new SecurityStatusPal(SecurityStatusPalErrorCode.TryAgain);
 1045                    return waitToken;
 1046                }
 1047
 1048                if (NetEventSource.Log.IsEnabled()) NetEventSource.DumpBuffer(this, buffer.Span);
 1049
 1050                ProtocolToken token = SslStreamPal.EncryptMessage(
 1051                    _securityContext!,
 1052                    buffer,
 1053                    _headerSize,
 1054                    _trailerSize);
 1055
 1056                if (token.Status.ErrorCode != SecurityStatusPalErrorCode.OK)
 1057                {
 1058                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"ERROR {token.Status}");
 1059                }
 1060
 1061                return token;
 1062            }
 1063        }
 1064
 1065        // On some platforms, the platform APIs decrypt in-place via single
 1066        // call (Schannel), while others have separate write-ciphertext +
 1067        // read-plaintext primitives. To allow the most efficient thing (copying
 1068        // plaintext straight to the `destination` buffer provided by the
 1069        // SslStream caller) on platforms that support it, the contract of this
 1070        // method is as follows:
 1071        //  - After the call, first `bytesWritten` bytes of `destination` contain decrypted plaintext
 1072        //  - Rest of the decrypted plaintext, if any, is stored in `_buffer.DecryptedSpan`.
 1073        private SecurityStatusPal DecryptData(int frameSize, Span<byte> destination, out int bytesWritten)
 1074        {
 1075            SecurityStatusPal status;
 1076
 1077            lock (_handshakeLock)
 1078            {
 1079                ThrowIfExceptionalOrNotAuthenticated();
 1080
 1081                status = SslStreamPal.DecryptMessage(
 1082                    _securityContext!,
 1083                    _buffer.EncryptedSpanSliced(frameSize),
 1084                    destination,
 1085                    out bytesWritten,
 1086                    out int leftoverOffset,
 1087                    out int leftoverLength);
 1088
 1089                _buffer.OnDecrypted(leftoverOffset, leftoverLength, frameSize);
 1090
 1091                if (NetEventSource.Log.IsEnabled() && status.ErrorCode == SecurityStatusPalErrorCode.OK)
 1092                {
 1093                    if (bytesWritten > 0)
 1094                    {
 1095                        NetEventSource.DumpBuffer(this, destination.Slice(0, bytesWritten));
 1096                    }
 1097
 1098                    if (_buffer.DecryptedSpan.Length > 0)
 1099                    {
 1100                        NetEventSource.DumpBuffer(this, _buffer.DecryptedSpan);
 1101                    }
 1102                }
 1103
 1104                if (status.ErrorCode == SecurityStatusPalErrorCode.Renegotiate)
 1105                {
 1106                    // The status indicates that the peer or TLS implementation requires additional
 1107                    // handshake/session processing. In practice, there can be other reasons too,
 1108                    // like TLS1.3 session creation or alert handling. We need to pass the data to
 1109                    // the underlying security provider and it is not safe to do parallel write any
 1110                    // more as that can change TLS state and the EncryptData() can fail in strange ways.
 1111
 1112                    // To handle this we call DecryptData() under lock and we create TCS waiter.
 1113                    // EncryptData() checks that under same lock and if it exist it will not call low-level crypto.
 1114                    // Instead it will wait synchronously or asynchronously and it will try again after the wait.
 1115                    // The result will be set when ReplyOnReAuthenticationAsync() is finished e.g. lsass business is ove
 1116                    // If that happen before EncryptData() runs, _handshakeWaiter will be set to null
 1117                    // and EncryptData() will work normally e.g. no waiting, just exclusion with DecryptData()
 1118
 1119                    if (_sslAuthenticationOptions.AllowRenegotiation || SslProtocol == SslProtocols.Tls13 || _nestedAuth
 1120                    {
 1121                        // create TCS only if we plan to proceed. If not, we will throw later outside of the lock.
 1122                        // Tls1.3 does not have renegotiation. However on Windows this error code is used
 1123                        // for session management e.g. anything lsass needs to see.
 1124                        // We also allow it when explicitly requested using RenegotiateAsync().
 1125                        _handshakeWaiter = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchrono
 1126                    }
 1127                }
 1128            }
 1129
 1130            return status;
 1131        }
 1132
 1133        /*++
 1134            VerifyRemoteCertificate - Validates the content of a Remote Certificate
 1135
 1136            checkCRL if true, checks the certificate revocation list for validity.
 1137            checkCertName, if true checks the CN field of the certificate
 1138        --*/
 1139
 1140        //This method validates a remote certificate.
 1141        internal bool VerifyRemoteCertificate(SslCertificateTrust? trust, ref ProtocolToken alertToken, ref SslPolicyErr
 1142        {
 1143            // We need to note the number of certs in ExtraStore that were
 1144            // provided (by the user), we will add more from the received peer
 1145            // chain and we want to dispose only these after we perform the
 1146            // validation.
 1147            // TODO: this forces allocation of X509Certificate2Collection
 1148            int preexistingExtraCertsCount = _sslAuthenticationOptions.CertificateChainPolicy?.ExtraStore?.Count ?? 0;
 1149
 1150            X509Chain? chain = null;
 1151            bool certificateValidationSkippedOnResume = false;
 1152
 1153            try
 1154            {
 1155                X509Certificate2? certificate = CertificateValidationPal.GetRemoteCertificate(_securityContext, ref chai
 1156
 1157                return VerifyRemoteCertificateCore(
 1158                    this,
 1159                    !_isRenego && !_isReAuthentication,
 1160                    _sslAuthenticationOptions,
 1161                    _securityContext,
 1162                    ref _remoteCertificate,
 1163                    ref _connectionInfo,
 1164                    certificate,
 1165                    chain,
 1166                    trust,
 1167                    ref alertToken,
 1168                    ref sslPolicyErrors,
 1169                    out chainStatus,
 1170                    out certificateValidationSkippedOnResume,
 1171                    peerCertificateChain: null,
 1172                    cloneCertificateChainPolicy: false);
 1173            }
 1174            finally
 1175            {
 1176                // At least on Win2k server the chain is found to have dependencies on the original cert context.
 1177                // So it should be closed first.
 1178
 1179                if (chain != null)
 1180                {
 1181                    // Only cleanup certificates if no user callback was provided.
 1182                    // When a callback is provided, users might add their own certificates to ExtraStore
 1183                    // or keep references to certificates from ChainElements.
 1184                    // On a resumed handshake we skip the callback entirely (see the resumption shortcut
 1185                    // in VerifyRemoteCertificateCore), so nothing else adopts the peer-sent intermediates
 1186                    // GetRemoteCertificate appended; dispose them here even when a callback is configured
 1187                    // to avoid leaking X509Certificate2 handles across repeated resumptions.
 1188                    if (_sslAuthenticationOptions.CertValidationDelegate == null || certificateValidationSkippedOnResume
 1189                    {
 1190                        // Dispose only the certificates that were added by GetRemoteCertificate
 1191                        for (int i = preexistingExtraCertsCount; i < chain.ChainPolicy.ExtraStore.Count; i++)
 1192                        {
 1193                            chain.ChainPolicy.ExtraStore[i].Dispose();
 1194                        }
 1195
 1196                        int elementsCount = chain.ChainElements.Count;
 1197                        for (int i = 0; i < elementsCount; i++)
 1198                        {
 1199                            chain.ChainElements[i].Certificate.Dispose();
 1200                        }
 1201                    }
 1202
 1203                    chain.Dispose();
 1204                }
 1205            }
 1206        }
 1207
 1208        internal bool VerifyRemoteCertificate(
 1209            X509Certificate2? certificate,
 1210            X509Chain? chain,
 1211            SslCertificateTrust? trust,
 1212            ref ProtocolToken alertToken,
 1213            ref SslPolicyErrors sslPolicyErrors,
 1214            out X509ChainStatusFlags chainStatus)
 1215        {
 1216            return VerifyRemoteCertificateCore(
 1217                this,
 1218                !_isRenego && !_isReAuthentication,
 1219                _sslAuthenticationOptions,
 1220                _securityContext,
 1221                ref _remoteCertificate,
 1222                ref _connectionInfo,
 1223                certificate,
 1224                chain,
 1225                trust,
 1226                ref alertToken,
 1227                ref sslPolicyErrors,
 1228                out chainStatus,
 1229                out _,
 1230                peerCertificateChain: null,
 1231                cloneCertificateChainPolicy: false);
 1232        }
 1233
 1234        internal static bool VerifyRemoteCertificateCore(
 1235            object sender,
 1236            bool isInitialHandshake,
 1237            SslAuthenticationOptions sslAuthenticationOptions,
 1238#if TARGET_APPLE
 1239            SafeDeleteContext? securityContext,
 1240#else
 1241            SafeDeleteSslContext? securityContext,
 1242#endif
 1243            ref X509Certificate2? remoteCertificateSlot,
 1244            ref SslConnectionInfo connectionInfo,
 1245            X509Certificate2? certificate,
 1246            X509Chain? chain,
 1247            SslCertificateTrust? trust,
 1248            ref ProtocolToken alertToken,
 1249            ref SslPolicyErrors sslPolicyErrors,
 1250            out X509ChainStatusFlags chainStatus,
 1251            out bool certificateValidationSkippedOnResume,
 1252            X509Certificate2Collection? peerCertificateChain,
 1253            bool cloneCertificateChainPolicy)
 1254        {
 1255            chainStatus = X509ChainStatusFlags.NoError;
 1256            certificateValidationSkippedOnResume = false;
 1257
 1258            bool success = false;
 1259
 1260            RemoteCertificateValidationCallback? remoteCertValidationCallback = sslAuthenticationOptions.CertValidationD
 1261
 1262            if (remoteCertificateSlot != null &&
 1263                certificate != null &&
 1264                certificate.RawDataMemory.Span.SequenceEqual(remoteCertificateSlot.RawDataMemory.Span))
 1265            {
 1266                // This is renegotiation or TLS 1.3 post-handshake auth and the (remote) certificate did not change.
 1267                // Revalidating the same certificate MAY fail for a couple of reasons (expiration, revocation,
 1268                // change in system trust, ...), but we have already established trust on this particular
 1269                // connection to even get this far.
 1270                certificate.Dispose();
 1271                return true;
 1272            }
 1273
 1274            if (certificate != null &&
 1275                isInitialHandshake &&
 1276                connectionInfo.TlsResumed &&
 1277                !LocalAppContextSwitches.RevalidateCertificateOnTlsResume)
 1278            {
 1279                // The initial TLS handshake was a resumption via an abbreviated handshake. The
 1280                // peer did not send its certificate again; its identity was established and
 1281                // validated during the original full handshake that produced the session ticket
 1282                // / session id. Common TLS stacks (e.g. OpenSSL, SChannel) do not re-run
 1283                // certificate verification on resumption, so by default neither do we: adopt the
 1284                // cached peer certificate for the RemoteCertificate property but skip rebuilding
 1285                // the chain and invoking the user validation callback. Set the
 1286                // System.Net.Security.RevalidateCertificateOnTlsResume switch to opt back into
 1287                // re-validating the peer certificate on every resumption.
 1288                //
 1289                // This shortcut is gated on the initial handshake: during renegotiation or
 1290                // TLS 1.3 post-handshake authentication the peer can present a new certificate,
 1291                // which must always be validated (the identical-certificate case above is handled
 1292                // separately).
 1293                remoteCertificateSlot = certificate;
 1294                certificateValidationSkippedOnResume = true;
 1295                if (NetEventSource.Log.IsEnabled())
 1296                {
 1297                    NetEventSource.Info(sender, "Skipping remote certificate validation on resumed TLS session.");
 1298                }
 1299                return true;
 1300            }
 1301
 1302            // don't assign to remoteCertificateSlot yet, this prevents weird exceptions if SslStream is disposed in par
 1303
 1304            if (certificate == null)
 1305            {
 1306                if (NetEventSource.Log.IsEnabled() && sslAuthenticationOptions.RemoteCertRequired)
 1307                {
 1308                    NetEventSource.Error(sender, $"Remote certificate required, but no remote certificate received");
 1309                }
 1310                sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNotAvailable;
 1311            }
 1312            else
 1313            {
 1314                chain ??= new X509Chain();
 1315
 1316                if (sslAuthenticationOptions.CertificateChainPolicy != null)
 1317                {
 1318                    chain.ChainPolicy = cloneCertificateChainPolicy
 1319                        ? sslAuthenticationOptions.CertificateChainPolicy.Clone()
 1320                        : sslAuthenticationOptions.CertificateChainPolicy;
 1321                }
 1322                else
 1323                {
 1324                    chain.ChainPolicy.RevocationMode = sslAuthenticationOptions.CertificateRevocationCheckMode;
 1325                    chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
 1326
 1327                    if (sslAuthenticationOptions.IsServer && !LocalAppContextSwitches.EnableServerAiaDownloads)
 1328                    {
 1329                        chain.ChainPolicy.DisableCertificateDownloads = true;
 1330                    }
 1331
 1332                    if (trust != null)
 1333                    {
 1334                        chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
 1335                        if (trust._store != null)
 1336                        {
 1337                            chain.ChainPolicy.CustomTrustStore.AddRange(trust._store.Certificates);
 1338                        }
 1339                        if (trust._trustList != null)
 1340                        {
 1341                            chain.ChainPolicy.CustomTrustStore.AddRange(trust._trustList);
 1342                        }
 1343                    }
 1344                }
 1345
 1346                if (peerCertificateChain is { Count: > 0 })
 1347                {
 1348                    chain.ChainPolicy.ExtraStore.AddRange(peerCertificateChain);
 1349                }
 1350
 1351                // set ApplicationPolicy unless already provided.
 1352                if (chain.ChainPolicy.ApplicationPolicy.Count == 0)
 1353                {
 1354                    // Authenticate the remote party: (e.g. when operating in server mode, authenticate the client).
 1355                    chain.ChainPolicy.ApplicationPolicy.Add(sslAuthenticationOptions.IsServer ? s_clientAuthOid : s_serv
 1356                }
 1357
 1358                sslPolicyErrors |= CertificateValidationPal.VerifyCertificateProperties(
 1359                    securityContext!,
 1360                    chain,
 1361                    certificate,
 1362                    sslAuthenticationOptions.CheckCertName,
 1363                    sslAuthenticationOptions.IsServer,
 1364                    TargetHostNameHelper.NormalizeHostName(sslAuthenticationOptions.TargetHost));
 1365            }
 1366
 1367            remoteCertificateSlot = certificate;
 1368
 1369            if (remoteCertValidationCallback != null)
 1370            {
 1371                // Ensure connection info is populated before calling the user callback,
 1372                // which may access properties like SslProtocol or CipherAlgorithm.
 1373                // During inline cert validation the handshake hasn't completed yet, so
 1374                // connectionInfo may not have been set by ProcessHandshakeSuccess.
 1375                if (connectionInfo.Protocol == 0 && securityContext is not null)
 1376                {
 1377                    SslStreamPal.QueryContextConnectionInfo(securityContext, ref connectionInfo);
 1378                }
 1379
 1380                success = remoteCertValidationCallback(sender, certificate, chain, sslPolicyErrors);
 1381            }
 1382            else
 1383            {
 1384                if (!sslAuthenticationOptions.RemoteCertRequired)
 1385                {
 1386                    sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateNotAvailable;
 1387                }
 1388
 1389                success = sslPolicyErrors == SslPolicyErrors.None;
 1390            }
 1391
 1392            if (NetEventSource.Log.IsEnabled())
 1393            {
 1394                LogCertificateValidation(sender, remoteCertValidationCallback, sslPolicyErrors, success, chain);
 1395                NetEventSource.Info(sender, $"Cert validation, remote cert = {remoteCertificateSlot}");
 1396            }
 1397
 1398            if (!success)
 1399            {
 1400#pragma warning disable CS0162 // unreachable code detected (compile time const)
 1401                if (SslStreamPal.CanGenerateCustomAlertsForContext(securityContext) && !SslStreamPal.CertValidationInCal
 1402                {
 1403                    sslStream.CreateFatalHandshakeAlertToken(sslPolicyErrors, chain!, ref alertToken);
 1404                }
 1405#pragma warning restore CS0162 // unreachable code detected (compile time const)
 1406
 1407                if (chain != null)
 1408                {
 1409                    foreach (X509ChainStatus status in chain.ChainStatus)
 1410                    {
 1411                        chainStatus |= status.Status;
 1412                    }
 1413                }
 1414            }
 1415
 1416            return success;
 1417        }
 1418
 1419        private void CreateFatalHandshakeAlertToken(SslPolicyErrors sslPolicyErrors, X509Chain? chain, ref ProtocolToken
 1420        {
 1421            TlsAlertMessage alertMessage;
 1422
 1423            switch (sslPolicyErrors)
 1424            {
 1425                case SslPolicyErrors.RemoteCertificateChainErrors:
 1426                    Debug.Assert(chain != null);
 1427                    alertMessage = GetAlertMessageFromChain(chain!);
 1428                    break;
 1429                case SslPolicyErrors.RemoteCertificateNameMismatch:
 1430                    alertMessage = TlsAlertMessage.BadCertificate;
 1431                    break;
 1432                case SslPolicyErrors.RemoteCertificateNotAvailable:
 1433                default:
 1434                    alertMessage = TlsAlertMessage.CertificateUnknown;
 1435                    break;
 1436            }
 1437
 1438            if (NetEventSource.Log.IsEnabled())
 1439                NetEventSource.Info(this, $"alertMessage:{alertMessage}");
 1440
 1441            SecurityStatusPal status;
 1442            status = SslStreamPal.ApplyAlertToken(_securityContext, TlsAlertType.Fatal, alertMessage);
 1443
 1444            if (status.ErrorCode != SecurityStatusPalErrorCode.OK)
 1445            {
 1446                if (NetEventSource.Log.IsEnabled())
 1447                    NetEventSource.Info(this, $"ApplyAlertToken() returned {status.ErrorCode}");
 1448
 1449                if (status.Exception != null)
 1450                {
 1451                    ExceptionDispatchInfo.Throw(status.Exception);
 1452                }
 1453            }
 1454
 1455#if TARGET_APPLE
 1456            if (_securityContext is not null && !SslStreamPal.IsAsyncSecurityContext(_securityContext))
 1457            {
 1458                byte[] alertFrame = TlsFrameHelper.CreateAlertFrame(_lastFrame.Header.Version, (TlsAlertDescription)aler
 1459                if (alertFrame.Length != 0)
 1460                {
 1461                    alertToken.SetPayload(alertFrame);
 1462                    return;
 1463                }
 1464            }
 1465#endif
 1466            alertToken = GenerateAlertToken();
 1467        }
 1468
 1469        private ProtocolToken CreateShutdownToken()
 1470        {
 1471            SecurityStatusPal status;
 1472            status = SslStreamPal.ApplyShutdownToken(_securityContext!);
 1473
 1474            if (status.ErrorCode != SecurityStatusPalErrorCode.OK)
 1475            {
 1476                if (NetEventSource.Log.IsEnabled())
 1477                    NetEventSource.Info(this, $"ApplyAlertToken() returned {status.ErrorCode}");
 1478
 1479                if (status.Exception != null)
 1480                {
 1481                    ExceptionDispatchInfo.Throw(status.Exception);
 1482                }
 1483
 1484                return default;
 1485            }
 1486
 1487            return GenerateToken(default, out _);
 1488        }
 1489
 1490        private ProtocolToken GenerateAlertToken()
 1491        {
 1492            return GenerateToken(default, out _);
 1493        }
 1494
 1495        internal static TlsAlertMessage GetAlertMessageFromChain(X509Chain chain)
 1496        {
 1497            foreach (X509ChainStatus chainStatus in chain.ChainStatus)
 1498            {
 1499                if (chainStatus.Status == X509ChainStatusFlags.NoError)
 1500                {
 1501                    continue;
 1502                }
 1503
 1504                if ((chainStatus.Status &
 1505                    (X509ChainStatusFlags.UntrustedRoot | X509ChainStatusFlags.PartialChain |
 1506                     X509ChainStatusFlags.Cyclic)) != 0)
 1507                {
 1508                    return TlsAlertMessage.UnknownCA;
 1509                }
 1510
 1511                if ((chainStatus.Status &
 1512                    (X509ChainStatusFlags.Revoked | X509ChainStatusFlags.OfflineRevocation)) != 0)
 1513                {
 1514                    return TlsAlertMessage.CertificateRevoked;
 1515                }
 1516
 1517                if ((chainStatus.Status &
 1518                    (X509ChainStatusFlags.CtlNotTimeValid | X509ChainStatusFlags.NotTimeNested |
 1519                     X509ChainStatusFlags.NotTimeValid)) != 0)
 1520                {
 1521                    return TlsAlertMessage.CertificateExpired;
 1522                }
 1523
 1524                if ((chainStatus.Status & X509ChainStatusFlags.CtlNotValidForUsage) != 0)
 1525                {
 1526                    return TlsAlertMessage.UnsupportedCert;
 1527                }
 1528
 1529                if ((chainStatus.Status &
 1530                    (X509ChainStatusFlags.CtlNotSignatureValid | X509ChainStatusFlags.InvalidExtension |
 1531                     X509ChainStatusFlags.NotSignatureValid | X509ChainStatusFlags.InvalidPolicyConstraints |
 1532                     X509ChainStatusFlags.NoIssuanceChainPolicy | X509ChainStatusFlags.NotValidForUsage)) != 0)
 1533                {
 1534                    return TlsAlertMessage.BadCertificate;
 1535                }
 1536
 1537                // All other errors:
 1538                return TlsAlertMessage.CertificateUnknown;
 1539            }
 1540
 1541            return TlsAlertMessage.BadCertificate;
 1542        }
 1543
 1544        private static void LogCertificateValidation(object sender, RemoteCertificateValidationCallback? remoteCertValid
 1545        {
 1546            if (!NetEventSource.Log.IsEnabled())
 1547                return;
 1548
 1549            if (sslPolicyErrors != SslPolicyErrors.None)
 1550            {
 1551                NetEventSource.Log.RemoteCertificateError(sender, SR.net_log_remote_cert_has_errors);
 1552                if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNotAvailable) != 0)
 1553                {
 1554                    NetEventSource.Log.RemoteCertificateError(sender, SR.net_log_remote_cert_not_available);
 1555                }
 1556
 1557                if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch) != 0)
 1558                {
 1559                    NetEventSource.Log.RemoteCertificateError(sender, SR.net_log_remote_cert_name_mismatch);
 1560                }
 1561
 1562                if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) != 0)
 1563                {
 1564                    Debug.Assert(chain != null);
 1565                    string chainStatusString = "ChainStatus: ";
 1566                    foreach (X509ChainStatus chainStatus in chain!.ChainStatus)
 1567                    {
 1568                        chainStatusString += "\t" + chainStatus.StatusInformation;
 1569                    }
 1570                    NetEventSource.Log.RemoteCertificateError(sender, chainStatusString);
 1571                }
 1572            }
 1573
 1574            if (success)
 1575            {
 1576                if (remoteCertValidationCallback != null)
 1577                {
 1578                    NetEventSource.Log.RemoteCertDeclaredValid(sender);
 1579                }
 1580                else
 1581                {
 1582                    NetEventSource.Log.RemoteCertHasNoErrors(sender);
 1583                }
 1584            }
 1585            else
 1586            {
 1587                if (remoteCertValidationCallback != null)
 1588                {
 1589                    NetEventSource.Log.RemoteCertUserDeclaredInvalid(sender);
 1590                }
 1591            }
 1592        }
 1593    }
 1594
 1595    // ProtocolToken - used to process and handle the return codes from the SSPI wrapper
 1596    internal struct ProtocolToken
 1597    {
 1598        internal SecurityStatusPal Status;
 1599        internal byte[]? Payload;
 1600        internal int Size;
 1601        internal bool RentBuffer;
 1602
 1603        internal bool Failed
 1604        {
 1605            get
 01606            {
 01607                return ((Status.ErrorCode != SecurityStatusPalErrorCode.OK) && (Status.ErrorCode != SecurityStatusPalErr
 01608            }
 1609        }
 1610
 1611        internal bool Done
 1612        {
 1613            get
 01614            {
 01615                return (Status.ErrorCode == SecurityStatusPalErrorCode.OK);
 01616            }
 1617        }
 1618
 1619        internal bool Renegotiate
 1620        {
 1621            get
 1622            {
 1623                return (Status.ErrorCode == SecurityStatusPalErrorCode.Renegotiate);
 1624            }
 1625        }
 1626
 1627        internal bool CloseConnection
 1628        {
 1629            get
 1630            {
 1631                return (Status.ErrorCode == SecurityStatusPalErrorCode.ContextExpired);
 1632            }
 1633        }
 1634        internal void SetPayload(ReadOnlySpan<byte> payload)
 1635        {
 1636            Debug.Assert(Payload == null);
 1637            Size = payload.Length;
 1638
 1639            if (Size > 0)
 1640            {
 1641                Payload = RentBuffer ? ArrayPool<byte>.Shared.Rent(Size) : new byte[Size];
 1642                payload.CopyTo(new Span<byte>(Payload, 0, Size));
 1643            }
 1644        }
 1645
 1646        internal void EnsureAvailableSpace(int size)
 01647        {
 01648            if (Available >= size)
 01649            {
 01650                return;
 1651            }
 1652
 01653            var oldPayload = Payload;
 1654
 01655            Payload = RentBuffer ? ArrayPool<byte>.Shared.Rent(Size + size) : new byte[Size + size];
 01656            if (oldPayload != null)
 01657            {
 01658                oldPayload.AsSpan<byte>().CopyTo(Payload);
 01659                if (RentBuffer)
 01660                {
 01661                    ArrayPool<byte>.Shared.Return(oldPayload);
 01662                }
 01663            }
 01664        }
 1665
 01666        internal int Available => Payload == null ? 0 : Payload.Length - Size;
 01667        internal Span<byte> AvailableSpan => Payload == null ? Span<byte>.Empty : new Span<byte>(Payload, Size, Availabl
 1668
 01669        internal ReadOnlyMemory<byte> AsMemory() => new ReadOnlyMemory<byte>(Payload, 0, Size);
 1670
 1671        internal void ReleasePayload()
 311672        {
 311673            Debug.Assert(Payload != null || Size == 0);
 1674
 311675            byte[]? toReturn = Payload;
 311676            Payload = null;
 311677            Size = 0;
 311678            if (RentBuffer && toReturn != null)
 01679            {
 01680                ArrayPool<byte>.Shared.Return(toReturn);
 01681            }
 311682        }
 1683
 1684        internal Exception? GetException()
 01685        {
 1686            // If it's not done, then there's got to be an error, even if it's
 1687            // a Handshake message up, and we only have a Warning message.
 01688            return Done ? null : SslStreamPal.GetException(Status);
 01689        }
 1690    }
 1691}
 1692