< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 205
Coverable lines: 205
Total lines: 532
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 128
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
.ctor(...)0%220%
.ctor(...)0%660%
Dispose()0%660%
GetOutgoingBlob(...)0%22220%
GetOutgoingBlob(...)0%10100%
Wrap(...)0%440%
Unwrap(...)0%440%
UnwrapInPlace(...)0%440%
ComputeIntegrityCheck(...)0%440%
VerifyIntegrityCheck(...)0%440%
CheckSpn()0%36360%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.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.ComponentModel;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.Security.Authentication.ExtendedProtection;
 9using System.Security.Principal;
 10
 11namespace System.Net.Security
 12{
 13    /// <summary>
 14    /// Represents a stateful authentication exchange that uses the Negotiate, NTLM or Kerberos security protocols
 15    /// to authenticate the client or server, in client-server communication.
 16    /// </summary>
 17    public sealed class NegotiateAuthentication : IDisposable
 18    {
 19        private readonly NegotiateAuthenticationPal _pal;
 20        private readonly string _requestedPackage;
 21        private readonly bool _isServer;
 22        private readonly TokenImpersonationLevel _requiredImpersonationLevel;
 23        private readonly ProtectionLevel _requiredProtectionLevel;
 24        private readonly bool _requiredMutualAuthentication;
 25        private readonly ExtendedProtectionPolicy? _extendedProtectionPolicy;
 26        private readonly bool _isSecureConnection;
 27        private bool _isDisposed;
 28        private IIdentity? _remoteIdentity;
 29
 30        /// <summary>
 31        /// Initializes a new instance of the <see cref="NegotiateAuthentication"/>
 32        /// for client-side authentication session.
 33        /// </summary>
 34        /// <param name="clientOptions">The property bag for the authentication options.</param>
 35        public NegotiateAuthentication(NegotiateAuthenticationClientOptions clientOptions) :
 036            this(clientOptions, enforceMutualAuthentication: true)
 037        {
 038        }
 39
 040        internal NegotiateAuthentication(NegotiateAuthenticationClientOptions clientOptions, bool enforceMutualAuthentic
 041        {
 042            ArgumentNullException.ThrowIfNull(clientOptions);
 43
 044            _isServer = false;
 045            _requestedPackage = clientOptions.Package;
 046            _requiredImpersonationLevel = TokenImpersonationLevel.None;
 047            _requiredProtectionLevel = clientOptions.RequiredProtectionLevel;
 048            _requiredMutualAuthentication = enforceMutualAuthentication && clientOptions.RequireMutualAuthentication;
 049            _pal = NegotiateAuthenticationPal.Create(clientOptions);
 050        }
 51
 52        /// <summary>
 53        /// Initializes a new instance of the <see cref="NegotiateAuthentication"/>
 54        /// for server-side authentication session.
 55        /// </summary>
 56        /// <param name="serverOptions">The property bag for the authentication options.</param>
 057        public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOptions)
 058        {
 059            ArgumentNullException.ThrowIfNull(serverOptions);
 60
 061            if (serverOptions.Policy?.PolicyEnforcement == PolicyEnforcement.Always &&
 062                !ExtendedProtectionPolicy.OSSupportsExtendedProtection)
 063            {
 064                throw new PlatformNotSupportedException(SR.net_extprotection_not_supported);
 65            }
 66
 067            _isServer = true;
 068            _requestedPackage = serverOptions.Package;
 069            _requiredImpersonationLevel = serverOptions.RequiredImpersonationLevel;
 070            _requiredProtectionLevel = serverOptions.RequiredProtectionLevel;
 071            _extendedProtectionPolicy = serverOptions.Policy;
 072            _isSecureConnection = serverOptions.Binding != null;
 073            _pal = NegotiateAuthenticationPal.Create(serverOptions);
 074        }
 75
 76        /// <summary>
 77        /// Releases the unmanaged resources used by the <see cref="NegotiateAuthentication"/>
 78        /// and optionally releases the managed resources.
 79        /// </summary>
 80        public void Dispose()
 081        {
 082            if (!_isDisposed)
 083            {
 084                _isDisposed = true;
 085                _pal?.Dispose();
 086                if (_remoteIdentity is IDisposable disposableRemoteIdentity)
 087                {
 088                    disposableRemoteIdentity.Dispose();
 089                }
 090            }
 091        }
 92
 93        /// <summary>
 94        /// Gets a value that indicates whether the authentication exchange has completed.
 95        /// </summary>
 96        /// <value>
 97        /// <see langword="true" /> if the authentication exchange has completed; otherwise, <see langword="false" />.
 98        /// </value>
 99        /// <remarks>
 100        /// This property indicates whether the authentication exchange has completed, not whether authentication
 101        /// succeeded. A <see langword="true" /> value can be returned after either successful authentication or a
 102        /// terminal authentication failure.
 103        ///
 104        /// To determine whether authentication actually succeeded, inspect the <see cref="NegotiateAuthenticationStatus
 105        /// returned by the most recent call to <see cref="GetOutgoingBlob(ReadOnlySpan{byte}, out NegotiateAuthenticati
 106        /// or <see cref="GetOutgoingBlob(string, out NegotiateAuthenticationStatusCode)" />. The status is
 107        /// <see cref="NegotiateAuthenticationStatusCode.Completed" /> on success; any other value indicates that
 108        /// authentication didn't complete successfully.
 109        /// </remarks>
 0110        public bool IsAuthenticated => _isDisposed ? false : _pal.IsAuthenticated;
 111
 112        /// <summary>
 113        /// Indicates the negotiated level of protection.
 114        /// </summary>
 115        /// <remarks>
 116        /// The negotiated level of protection is only available when the session
 117        /// authentication was finished (see <see cref="IsAuthenticated" />). The
 118        /// protection level can be higher than the initially requested protection
 119        /// level specified by <see cref="NegotiateAuthenticationClientOptions.RequiredProtectionLevel" /> or
 120        /// <see cref="NegotiateAuthenticationServerOptions.RequiredProtectionLevel" />.
 121        /// </remarks>
 122        public ProtectionLevel ProtectionLevel =>
 0123            !IsSigned ? ProtectionLevel.None :
 0124            !IsEncrypted ? ProtectionLevel.Sign :
 0125            ProtectionLevel.EncryptAndSign;
 126
 127        /// <summary>
 128        /// Indicates whether data signing was negotiated.
 129        /// </summary>
 0130        public bool IsSigned => _isDisposed ? false : _pal.IsSigned;
 131
 132        /// <summary>
 133        /// Indicates whether data encryption was negotiated.
 134        /// </summary>
 0135        public bool IsEncrypted => _isDisposed ? false : _pal.IsEncrypted;
 136
 137        /// <summary>
 138        /// Indicates whether both server and client have been authenticated.
 139        /// </summary>
 140        public bool IsMutuallyAuthenticated =>
 0141            !_isDisposed &&
 0142            !string.Equals(Package, NegotiationInfoClass.NTLM) &&
 0143            _pal.IsMutuallyAuthenticated;
 144
 145        /// <summary>
 146        /// Indicates whether the local side of the authentication is representing
 147        /// the server.
 148        /// </summary>
 0149        public bool IsServer => _isServer;
 150
 151        /// <summary>
 152        /// Name of the negotiated authentication package.
 153        /// </summary>
 154        /// <remarks>
 155        /// The negotiated authentication package is only available when the session
 156        /// authentication was finished (see <see cref="IsAuthenticated" />). For
 157        /// unfinished authentication sessions the value is undefined and usually
 158        /// returns the initial authentication package name specified in
 159        /// <see cref="NegotiateAuthenticationClientOptions.Package" /> or
 160        /// <see cref="NegotiateAuthenticationServerOptions.Package" />.
 161        ///
 162        /// If the Negotiate package was used for authentication the value of this
 163        /// property will be Kerberos, NTLM, or any other specific protocol that was
 164        /// negotiated between both sides of the authentication.
 165        /// </remarks>
 0166        public string Package => _pal.Package ?? _requestedPackage;
 167
 168        /// <summary>
 169        /// Gets target name (service principal name) of the server.
 170        /// </summary>
 171        /// <remarks>
 172        /// For server-side of the authentication the property returns the target name
 173        /// specified by the client after authentication completes successfully.
 174        ///
 175        /// For client-side of the authentication the property returns the target name
 176        /// specified in <see cref="NegotiateAuthenticationClientOptions.TargetName" />.
 177        /// </remarks>
 0178        public string? TargetName => _pal.TargetName;
 179
 180        /// <summary>
 181        /// Gets information about the identity of the remote party.
 182        /// </summary>
 183        /// <returns>
 184        /// An <see cref="IIdentity" /> object that describes the identity of the remote endpoint.
 185        /// </returns>
 186        /// <exception cref="InvalidOperationException">Authentication failed or has not occurred.</exception>
 187        /// <exception cref="Win32Exception">System error occurred when trying to retrieve the identity.</exception>
 188        public IIdentity RemoteIdentity
 189        {
 190            get
 0191            {
 0192                IIdentity? identity = _remoteIdentity;
 0193                if (identity is null)
 0194                {
 0195                    if (!IsAuthenticated || _isDisposed)
 0196                    {
 0197                        throw new InvalidOperationException(SR.net_auth_noauth);
 198                    }
 199
 0200                    if (IsServer)
 0201                    {
 0202                        Debug.Assert(!OperatingSystem.IsTvOS(), "Server authentication is not supported on tvOS");
 0203                        _remoteIdentity = identity = _pal.RemoteIdentity;
 0204                    }
 205                    else
 0206                    {
 0207                        return new GenericIdentity(TargetName ?? string.Empty, Package);
 208                    }
 0209                }
 0210                return identity;
 0211            }
 212        }
 213
 214        /// <summary>
 215        /// One of the <see cref="TokenImpersonationLevel" /> values, indicating the negotiated
 216        /// level of impresonation.
 217        /// </summary>
 0218        public System.Security.Principal.TokenImpersonationLevel ImpersonationLevel => _pal.ImpersonationLevel;
 219
 220        /// <summary>
 221        /// Evaluates an authentication token sent by the other party and returns a token in response.
 222        /// </summary>
 223        /// <param name="incomingBlob">Incoming authentication token, or empty value when initiating the authentication 
 224        /// <param name="statusCode">Status code returned by the authentication provider.</param>
 225        /// <returns>Outgoing authentication token to be sent to the other party.</returns>
 226        /// <remarks>
 227        /// When initiating the authentication exchange, one of the parties starts
 228        /// with an empty incomingBlob parameter.
 229        ///
 230        /// Successful step of the authentication returns either <see cref="NegotiateAuthenticationStatusCode.Completed"
 231        /// or <see cref="NegotiateAuthenticationStatusCode.ContinueNeeded" /> status codes.
 232        /// Any other status code indicates an unrecoverable error.
 233        ///
 234        /// When <see cref="NegotiateAuthenticationStatusCode.ContinueNeeded" /> is returned the
 235        /// return value is an authentication token to be transported to the other party.
 236        /// </remarks>
 237        public byte[]? GetOutgoingBlob(ReadOnlySpan<byte> incomingBlob, out NegotiateAuthenticationStatusCode statusCode
 0238        {
 0239            if (_isDisposed)
 0240            {
 0241                throw new InvalidOperationException(SR.net_auth_noauth);
 242            }
 243
 0244            byte[]? blob = _pal.GetOutgoingBlob(incomingBlob, out statusCode);
 245
 246            // Additional policy validation
 0247            if (statusCode == NegotiateAuthenticationStatusCode.Completed)
 0248            {
 0249                if (IsServer && _extendedProtectionPolicy != null && !CheckSpn())
 0250                {
 0251                    statusCode = NegotiateAuthenticationStatusCode.TargetUnknown;
 0252                }
 0253                else if (_requiredImpersonationLevel != TokenImpersonationLevel.None && ImpersonationLevel < _requiredIm
 0254                {
 0255                    statusCode = NegotiateAuthenticationStatusCode.ImpersonationValidationFailed;
 0256                }
 0257                else if (_requiredProtectionLevel != ProtectionLevel.None && ProtectionLevel < _requiredProtectionLevel)
 0258                {
 0259                    statusCode = NegotiateAuthenticationStatusCode.SecurityQosFailed;
 0260                }
 0261                else if (_requiredMutualAuthentication && !IsMutuallyAuthenticated)
 0262                {
 0263                    statusCode = NegotiateAuthenticationStatusCode.SecurityQosFailed;
 0264                }
 0265            }
 266
 0267            return blob;
 0268        }
 269
 270        /// <summary>
 271        /// Evaluates an authentication token sent by the other party and returns a token in response.
 272        /// </summary>
 273        /// <param name="incomingBlob">Incoming authentication token, or empty value when initiating the authentication 
 274        /// <param name="statusCode">Status code returned by the authentication provider.</param>
 275        /// <returns>Outgoing authentication token to be sent to the other party, encoded as base64.</returns>
 276        /// <remarks>
 277        /// When initiating the authentication exchange, one of the parties starts
 278        /// with an empty incomingBlob parameter.
 279        ///
 280        /// Successful step of the authentication returns either <see cref="NegotiateAuthenticationStatusCode.Completed"
 281        /// or <see cref="NegotiateAuthenticationStatusCode.ContinueNeeded" /> status codes.
 282        /// Any other status code indicates an unrecoverable error.
 283        ///
 284        /// When <see cref="NegotiateAuthenticationStatusCode.ContinueNeeded" /> is returned the
 285        /// return value is an authentication token to be transported to the other party.
 286        /// </remarks>
 287        public string? GetOutgoingBlob(string? incomingBlob, out NegotiateAuthenticationStatusCode statusCode)
 0288        {
 0289            byte[]? rentedBuffer = null;
 290            try
 0291            {
 0292                ReadOnlySpan<byte> decodedIncomingBlob = default;
 0293                if (!string.IsNullOrEmpty(incomingBlob))
 0294                {
 0295                    rentedBuffer = ArrayPool<byte>.Shared.Rent((incomingBlob.Length / 4) * 3);
 0296                    if (!Convert.TryFromBase64String(incomingBlob, rentedBuffer, out int decodedLength))
 0297                    {
 0298                        statusCode = NegotiateAuthenticationStatusCode.InvalidToken;
 0299                        return null;
 300                    }
 301
 0302                    decodedIncomingBlob = rentedBuffer.AsSpan(0, decodedLength);
 0303                }
 304
 0305                byte[]? decodedOutgoingBlob = GetOutgoingBlob(decodedIncomingBlob, out statusCode);
 306
 0307                string? outgoingBlob = null;
 0308                if (decodedOutgoingBlob != null && decodedOutgoingBlob.Length > 0)
 0309                {
 0310                    outgoingBlob = Convert.ToBase64String(decodedOutgoingBlob);
 0311                }
 312
 0313                return outgoingBlob;
 314            }
 315            finally
 0316            {
 0317                if (rentedBuffer is not null)
 0318                {
 0319                    ArrayPool<byte>.Shared.Return(rentedBuffer, clearArray: true);
 0320                }
 0321            }
 0322        }
 323
 324        /// <summary>
 325        /// Wrap an input message with signature and optionally with an encryption.
 326        /// </summary>
 327        /// <param name="input">Input message to be wrapped.</param>
 328        /// <param name="outputWriter">Buffer writer where the wrapped message is written.</param>
 329        /// <param name="requestEncryption">Specifies whether encryption is requested.</param>
 330        /// <param name="isEncrypted">Specifies whether encryption was applied in the wrapping.</param>
 331        /// <returns>
 332        /// <see cref="NegotiateAuthenticationStatusCode.Completed" /> on success, other
 333        /// <see cref="NegotiateAuthenticationStatusCode" /> values on failure.
 334        /// </returns>
 335        /// <remarks>
 336        /// Like the <see href="https://datatracker.ietf.org/doc/html/rfc2743#page-65">GSS_Wrap</see> API
 337        /// the authentication protocol implementation may choose to override the requested value in the
 338        /// requestEncryption parameter. This may result in either downgrade or upgrade of the protection
 339        /// level.
 340        /// </remarks>
 341        /// <exception cref="InvalidOperationException">Authentication failed or has not occurred.</exception>
 342        public NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, bool r
 0343        {
 0344            if (!IsAuthenticated || _isDisposed)
 0345            {
 0346                throw new InvalidOperationException(SR.net_auth_noauth);
 347            }
 348
 0349            return _pal.Wrap(input, outputWriter, requestEncryption, out isEncrypted);
 0350        }
 351
 352        /// <summary>
 353        /// Unwrap an input message with signature or encryption applied by the other party.
 354        /// </summary>
 355        /// <param name="input">Input message to be unwrapped.</param>
 356        /// <param name="outputWriter">Buffer writer where the unwrapped message is written.</param>
 357        /// <param name="wasEncrypted">
 358        /// On output specifies whether the wrapped message had encryption applied.
 359        /// </param>
 360        /// <returns>
 361        /// <see cref="NegotiateAuthenticationStatusCode.Completed" /> on success.
 362        /// <see cref="NegotiateAuthenticationStatusCode.MessageAltered" /> if the message signature was
 363        /// invalid.
 364        /// <see cref="NegotiateAuthenticationStatusCode.InvalidToken" /> if the wrapped message was
 365        /// in invalid format.
 366        /// Other <see cref="NegotiateAuthenticationStatusCode" /> values on failure.
 367        /// </returns>
 368        /// <exception cref="InvalidOperationException">Authentication failed or has not occurred.</exception>
 369        public NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, out 
 0370        {
 0371            if (!IsAuthenticated || _isDisposed)
 0372            {
 0373                throw new InvalidOperationException(SR.net_auth_noauth);
 374            }
 375
 0376            return _pal.Unwrap(input, outputWriter, out wasEncrypted);
 0377        }
 378
 379        /// <summary>
 380        /// Unwrap an input message with signature or encryption applied by the other party.
 381        /// </summary>
 382        /// <param name="input">Input message to be unwrapped. On output contains the decoded data.</param>
 383        /// <param name="unwrappedOffset">Offset in the input buffer where the unwrapped message was written.</param>
 384        /// <param name="unwrappedLength">Length of the unwrapped message.</param>
 385        /// <param name="wasEncrypted">
 386        /// On output specifies whether the wrapped message had encryption applied.
 387        /// </param>
 388        /// <returns>
 389        /// <see cref="NegotiateAuthenticationStatusCode.Completed" /> on success.
 390        /// <see cref="NegotiateAuthenticationStatusCode.MessageAltered" /> if the message signature was
 391        /// invalid.
 392        /// <see cref="NegotiateAuthenticationStatusCode.InvalidToken" /> if the wrapped message was
 393        /// in invalid format.
 394        /// Other <see cref="NegotiateAuthenticationStatusCode" /> values on failure.
 395        /// </returns>
 396        /// <exception cref="InvalidOperationException">Authentication failed or has not occurred.</exception>
 397        public NegotiateAuthenticationStatusCode UnwrapInPlace(Span<byte> input, out int unwrappedOffset, out int unwrap
 0398        {
 0399            if (!IsAuthenticated || _isDisposed)
 0400            {
 0401                throw new InvalidOperationException(SR.net_auth_noauth);
 402            }
 403
 0404            return _pal.UnwrapInPlace(input, out unwrappedOffset, out unwrappedLength, out wasEncrypted);
 0405        }
 406
 407        /// <summary>
 408        /// Computes the integrity check of a given message.
 409        /// </summary>
 410        /// <param name="message">Input message for MIC calculation.</param>
 411        /// <param name="signatureWriter">Buffer writer where the MIC is written.</param>
 412        /// <remarks>
 413        /// Implements the GSSAPI GetMIC operation.
 414        ///
 415        /// The method modifies the internal state and may update sequence numbers depending on the
 416        /// selected algorithm. Two successive invocations thus don't produce the same result and
 417        /// it's important to carefully pair GetMIC and VerifyMIC calls on the both sides of the
 418        /// authenticated session.
 419        /// </remarks>
 420        /// <exception cref="InvalidOperationException">Authentication failed or has not occurred.</exception>
 421        public void ComputeIntegrityCheck(ReadOnlySpan<byte> message, IBufferWriter<byte> signatureWriter)
 0422        {
 0423            if (!IsAuthenticated || _isDisposed)
 0424            {
 0425                throw new InvalidOperationException(SR.net_auth_noauth);
 426            }
 427
 0428            _pal.GetMIC(message, signatureWriter);
 0429        }
 430
 431        /// <summary>
 432        /// Verifies the message integrity check of a given message.
 433        /// </summary>
 434        /// <param name="message">Input message for MIC calculation.</param>
 435        /// <param name="signature">MIC to be verified.</param>
 436        /// <returns>For successfully verified MIC, the method returns true.</returns>
 437        /// <remarks>
 438        /// Implements the GSSAPI VerifyMIC operation.
 439        ///
 440        /// The method modifies the internal state and may update sequence numbers depending on the
 441        /// selected algorithm. Two successive invocations thus don't produce the same result and
 442        /// it's important to carefully pair GetMIC and VerifyMIC calls on the both sides of the
 443        /// authenticated session.
 444        /// </remarks>
 445        /// <exception cref="InvalidOperationException">Authentication failed or has not occurred.</exception>
 446        public bool VerifyIntegrityCheck(ReadOnlySpan<byte> message, ReadOnlySpan<byte> signature)
 0447        {
 0448            if (!IsAuthenticated || _isDisposed)
 0449            {
 0450                throw new InvalidOperationException(SR.net_auth_noauth);
 451            }
 452
 0453            return _pal.VerifyMIC(message, signature);
 0454        }
 455
 456        private bool CheckSpn()
 0457        {
 0458            Debug.Assert(_extendedProtectionPolicy != null);
 459
 0460            if (_pal.Package == NegotiationInfoClass.Kerberos)
 0461            {
 0462                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_no_spn_kerberos);
 0463                return true;
 464            }
 465
 0466            if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.Never)
 0467            {
 0468                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_no_spn_disabled);
 0469                return true;
 470            }
 471
 0472            if (_isSecureConnection && _extendedProtectionPolicy.ProtectionScenario == ProtectionScenario.TransportSelec
 0473            {
 0474                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_no_spn_cbt);
 0475                return true;
 476            }
 477
 0478            if (_extendedProtectionPolicy.CustomServiceNames == null)
 0479            {
 0480                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_no_spns);
 0481                return true;
 482            }
 483
 0484            string? clientSpn = _pal.TargetName;
 485
 0486            if (string.IsNullOrEmpty(clientSpn))
 0487            {
 0488                if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported)
 0489                {
 0490                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_no_spn_whensupport
 0491                    return true;
 492                }
 493                else
 0494                {
 0495                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_spn_failed_always)
 0496                    return false;
 497                }
 498            }
 499
 0500            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_spn, clientSpn);
 0501            bool found = _extendedProtectionPolicy.CustomServiceNames.Contains(clientSpn);
 502
 0503            if (NetEventSource.Log.IsEnabled())
 0504            {
 0505                if (found)
 0506                {
 0507                    NetEventSource.Info(this, SR.net_log_listener_spn_passed);
 0508                }
 509                else
 0510                {
 0511                    NetEventSource.Info(this, SR.net_log_listener_spn_failed);
 512
 0513                    if (_extendedProtectionPolicy.CustomServiceNames.Count == 0)
 0514                    {
 0515                        NetEventSource.Info(this, SR.net_log_listener_spn_failed_empty);
 0516                    }
 517                    else
 0518                    {
 0519                        NetEventSource.Info(this, SR.net_log_listener_spn_failed_dump);
 0520                        foreach (string serviceName in _extendedProtectionPolicy.CustomServiceNames)
 0521                        {
 0522                            NetEventSource.Info(this, "\t" + serviceName);
 0523                        }
 0524                    }
 0525                }
 0526            }
 527
 0528            return found;
 0529        }
 530    }
 531}
 532