< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Security.SslProtocolsHelper
Assembly: CoreWCF.NetFramingBase
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.NetFramingBase/src/CoreWCF/Security/SecurityUtils.cs
Line coverage
75%
Covered lines: 6
Uncovered lines: 2
Coverable lines: 8
Total lines: 411
Line coverage: 75%
Branch coverage
75%
Covered branches: 3
Total branches: 4
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
IsDefined(...)100%22100%
Validate(...)50%2250%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.NetFramingBase/src/CoreWCF/Security/SecurityUtils.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;
 5using System.ComponentModel;
 6using System.Diagnostics;
 7using System.Globalization;
 8using System.Net;
 9using System.Net.Security;
 10using System.Runtime.InteropServices;
 11using System.Security.Authentication;
 12using System.Security.Cryptography.X509Certificates;
 13using System.Security.Principal;
 14using System.Threading;
 15using System.Threading.Tasks;
 16using CoreWCF.Channels;
 17using CoreWCF.IdentityModel.Claims;
 18using CoreWCF.IdentityModel.Selectors;
 19using CoreWCF.IdentityModel.Tokens;
 20using CoreWCF.Runtime;
 21
 22namespace CoreWCF.Security
 23{
 24    internal static class SecurityUtils
 25    {
 26        internal static void ResetCertificate(X509Certificate2 certificate)
 27        {
 28            certificate.Reset();
 29        }
 30
 31        internal static EndpointIdentity CreateWindowsIdentity(NetworkCredential serverCredential)
 32        {
 33            if (serverCredential != null && !NetworkCredentialHelper.IsDefault(serverCredential))
 34            {
 35                string upn;
 36                if (serverCredential.Domain != null && serverCredential.Domain.Length > 0)
 37                {
 38                    upn = serverCredential.UserName + "@" + serverCredential.Domain;
 39                }
 40                else
 41                {
 42                    upn = serverCredential.UserName;
 43                }
 44                return new UpnEndpointIdentity(upn);
 45            }
 46            else
 47            {
 48                return CreateWindowsIdentity();
 49            }
 50        }
 51
 52        internal static EndpointIdentity CreateWindowsIdentity()
 53        {
 54            return CreateWindowsIdentity(false);
 55        }
 56
 57        internal static EndpointIdentity CreateWindowsIdentity(bool spnOnly)
 58        {
 59            EndpointIdentity identity = null;
 60            WindowsIdentity self = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? WindowsIdentity.GetCurrent() : 
 61            using (self)
 62            {
 63                if (self is null || spnOnly || IsSystemAccount(self))
 64                {
 65                    // If we're running on a non-Windows platform, we can't use the current Windows identity.
 66                    // If we're running on Windows and the current identity is a system account, we also can't use it.
 67                    // In both cases, we create an SPN identity based on the machine name.
 68                    // This is used for Net.Tcp services that don't have a configured identity.
 69                    // The SPN will be used to authenticate the service to clients.
 70                    identity = new SpnEndpointIdentity(string.Format(CultureInfo.InvariantCulture, "host/{0}", DnsCache.
 71                }
 72                else
 73                {
 74                    // This is used when generating the WSDL. It calls GetProperty<EndpointIdentity>
 75                    // on the configured binding and uses the identity in the WSDL. It's also used by
 76                    // SspiNegotiationTokenAuthenticator to calculate the DefaultServiceBinding value.
 77                    // On Linux, we cannot use the current Windows Identity, so we will return an Spn
 78                    // based on the machine name. It's quite a bit of work to get Windows authentication
 79                    // to work on Linux, so if a developer has gone to that kind of effort to use
 80                    // Windows authentication on Linux, they can provide an explicit endpoint identity
 81                    // in the service configuration (or manually set DefaultServiceBinding) and this
 82                    // code won't be needed.
 83                    var upn = GetUpnNameWithFallback(self.Name);
 84                    identity = new UpnEndpointIdentity(upn);
 85                }
 86            }
 87
 88            return identity;
 89        }
 90
 91        private static string GetUpnNameWithFallback(string downlevelName)
 92        {
 93            // There is no managed API to get the UPN name, so we have to P/Invoke. This will only work on Windows.
 94            // If we're not on Windows, we just return the downlevel name. If someone is using Windows auth on Linux
 95            // they should be using an explicit endpoint identity.
 96            if (Interop.Secur32.GetCurrentUpn(out string upnName))
 97            {
 98                return upnName;
 99            }
 100
 101            // If the AD cannot be queried for the fully qualified domain name,
 102            // fall back to the downlevel UPN name
 103            return downlevelName;
 104        }
 105
 106        private static bool IsSystemAccount(WindowsIdentity self)
 107        {
 108            SecurityIdentifier sid = self.User;
 109            if (sid == null)
 110            {
 111                return false;
 112            }
 113            // S-1-5-82 is the prefix for the sid that represents the identity that IIS 7.5 Apppool thread runs under.
 114            return (sid.IsWellKnown(WellKnownSidType.LocalSystemSid)
 115                    || sid.IsWellKnown(WellKnownSidType.NetworkServiceSid)
 116                    || sid.IsWellKnown(WellKnownSidType.LocalServiceSid)
 117                    || sid.Value.StartsWith("S-1-5-82", StringComparison.OrdinalIgnoreCase));
 118        }
 119
 120        internal static WindowsIdentity CloneWindowsIdentityIfNecessary(WindowsIdentity wid)
 121        {
 122            return CloneWindowsIdentityIfNecessary(wid, null);
 123        }
 124
 125        internal static WindowsIdentity CloneWindowsIdentityIfNecessary(WindowsIdentity wid, string authType)
 126        {
 127            if (wid != null)
 128            {
 129                IntPtr token = UnsafeGetWindowsIdentityToken(wid);
 130                if (token != IntPtr.Zero)
 131                {
 132                    return UnsafeCreateWindowsIdentityFromToken(token, authType);
 133                }
 134            }
 135            return wid;
 136        }
 137
 138        private static IntPtr UnsafeGetWindowsIdentityToken(WindowsIdentity wid)
 139        {
 140            return wid.Token;
 141        }
 142
 143        private static WindowsIdentity UnsafeCreateWindowsIdentityFromToken(IntPtr token, string authType)
 144        {
 145            if (authType != null)
 146            {
 147                return new WindowsIdentity(token, authType);
 148            }
 149            else
 150            {
 151                return new WindowsIdentity(token);
 152            }
 153        }
 154
 155        internal static void FixNetworkCredential(ref NetworkCredential credential)
 156        {
 157            if (credential == null)
 158            {
 159                return;
 160            }
 161            string username = NetworkCredentialHelper.UnsafeGetUsername(credential);
 162            string domain = NetworkCredentialHelper.UnsafeGetDomain(credential);
 163            if (!string.IsNullOrEmpty(username) && string.IsNullOrEmpty(domain))
 164            {
 165                // do the splitting only if there is exactly 1 \ or exactly 1 @
 166                string[] partsWithSlashDelimiter = username.Split('\\');
 167                string[] partsWithAtDelimiter = username.Split('@');
 168                if (partsWithSlashDelimiter.Length == 2 && partsWithAtDelimiter.Length == 1)
 169                {
 170                    if (!string.IsNullOrEmpty(partsWithSlashDelimiter[0]) && !string.IsNullOrEmpty(partsWithSlashDelimit
 171                    {
 172                        credential = new NetworkCredential(partsWithSlashDelimiter[1], NetworkCredentialHelper.UnsafeGet
 173                    }
 174                }
 175                else if (partsWithSlashDelimiter.Length == 1 && partsWithAtDelimiter.Length == 2)
 176                {
 177                    if (!string.IsNullOrEmpty(partsWithAtDelimiter[0]) && !string.IsNullOrEmpty(partsWithAtDelimiter[1])
 178                    {
 179                        credential = new NetworkCredential(partsWithAtDelimiter[0], NetworkCredentialHelper.UnsafeGetPas
 180                    }
 181                }
 182            }
 183        }
 184
 185        internal static EndpointIdentity GetServiceCertificateIdentity(X509Certificate2 certificate)
 186        {
 187            using (X509CertificateClaimSet claimSet = new X509CertificateClaimSet(certificate))
 188            {
 189                if (!TryCreateIdentity(claimSet, ClaimTypes.Dns, out EndpointIdentity identity))
 190                {
 191                    TryCreateIdentity(claimSet, ClaimTypes.Rsa, out identity);
 192                }
 193                return identity;
 194            }
 195        }
 196
 197        private static bool TryCreateIdentity(ClaimSet claimSet, string claimType, out EndpointIdentity identity)
 198        {
 199            identity = null;
 200            foreach (Claim claim in claimSet.FindClaims(claimType, null))
 201            {
 202                identity = EndpointIdentity.CreateIdentity(claim);
 203                return true;
 204            }
 205            return false;
 206        }
 207
 208        internal static Task OpenTokenAuthenticatorIfRequiredAsync(SecurityTokenAuthenticator tokenAuthenticator, Cancel
 209        {
 210            return OpenCommunicationObjectAsync(tokenAuthenticator as ICommunicationObject, token);
 211        }
 212
 213        internal static Task OpenTokenProviderIfRequiredAsync(SecurityTokenProvider tokenProvider, CancellationToken tok
 214        {
 215            return OpenCommunicationObjectAsync(tokenProvider as ICommunicationObject, token);
 216        }
 217
 218        internal static Task CloseTokenProviderIfRequiredAsync(SecurityTokenProvider tokenProvider, CancellationToken to
 219        {
 220            return CloseCommunicationObjectAsync(tokenProvider, false, token);
 221        }
 222
 223        internal static void AbortTokenAuthenticatorIfRequired(SecurityTokenAuthenticator tokenAuthenticator)
 224        {
 225            CloseCommunicationObjectAsync(tokenAuthenticator, true, CancellationToken.None).GetAwaiter().GetResult();
 226        }
 227
 228        internal static void AbortTokenProviderIfRequired(SecurityTokenProvider tokenProvider)
 229        {
 230            CloseCommunicationObjectAsync(tokenProvider, true, CancellationToken.None).GetAwaiter().GetResult();
 231        }
 232
 233        internal static Task CloseTokenAuthenticatorIfRequiredAsync(SecurityTokenAuthenticator tokenAuthenticator, Cance
 234        {
 235            return CloseTokenAuthenticatorIfRequiredAsync(tokenAuthenticator, false, token);
 236        }
 237
 238        internal static Task CloseTokenAuthenticatorIfRequiredAsync(SecurityTokenAuthenticator tokenAuthenticator, bool 
 239        {
 240            return CloseCommunicationObjectAsync(tokenAuthenticator, aborted, token);
 241        }
 242
 243        private static Task OpenCommunicationObjectAsync(ICommunicationObject obj, CancellationToken token)
 244        {
 245            if (obj != null)
 246            {
 247                return obj.OpenAsync(token);
 248            }
 249
 250            return Task.CompletedTask;
 251        }
 252
 253        private static Task CloseCommunicationObjectAsync(object obj, bool aborted, CancellationToken token)
 254        {
 255            if (obj != null)
 256            {
 257                if (obj is ICommunicationObject co)
 258                {
 259                    if (aborted)
 260                    {
 261                        try
 262                        {
 263                            co.Abort();
 264                        }
 265                        catch (CommunicationException e)
 266                        {
 267                            DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
 268                        }
 269                    }
 270                    else
 271                    {
 272                        return co.CloseAsync(token);
 273                    }
 274                }
 275                else if (obj is IDisposable disposable)
 276                {
 277                    disposable.Dispose();
 278                }
 279            }
 280
 281            return Task.CompletedTask;
 282        }
 283
 284        public static void ValidateAnonymityConstraint(WindowsIdentity identity, bool allowUnauthenticatedCallers)
 285        {
 286            if (!allowUnauthenticatedCallers && identity.User.IsWellKnown(WellKnownSidType.AnonymousSid))
 287            {
 288                throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(
 289                    new SecurityTokenValidationException(SR.AnonymousLogonsAreNotAllowed));
 290            }
 291        }
 292
 293        private static class NetworkCredentialHelper
 294        {
 295            //internal static bool IsNullOrEmpty(NetworkCredential credential)
 296            //{
 297            //    return credential == null ||
 298            //            (
 299            //                string.IsNullOrEmpty(UnsafeGetUsername(credential)) &&
 300            //                string.IsNullOrEmpty(UnsafeGetDomain(credential)) &&
 301            //                string.IsNullOrEmpty(UnsafeGetPassword(credential))
 302            //            );
 303            //}
 304
 305            internal static bool IsDefault(NetworkCredential credential)
 306            {
 307                return UnsafeGetDefaultNetworkCredentials().Equals(credential);
 308            }
 309
 310            internal static string UnsafeGetUsername(NetworkCredential credential)
 311            {
 312                return credential.UserName;
 313            }
 314
 315            internal static string UnsafeGetPassword(NetworkCredential credential)
 316            {
 317                return credential.Password;
 318            }
 319
 320            internal static string UnsafeGetDomain(NetworkCredential credential)
 321            {
 322                return credential.Domain;
 323            }
 324
 325            private static NetworkCredential UnsafeGetDefaultNetworkCredentials()
 326            {
 327                return CredentialCache.DefaultNetworkCredentials;
 328            }
 329        }
 330    }
 331
 332    internal static class SslProtocolsHelper
 333    {
 334        internal static bool IsDefined(SslProtocols value)
 335        {
 106336            SslProtocols allValues = SslProtocols.None;
 1908337            foreach (object protocol in Enum.GetValues(typeof(SslProtocols)))
 338            {
 848339                allValues |= (SslProtocols)protocol;
 340            }
 106341            return (value & allValues) == value;
 342        }
 343
 344        internal static void Validate(SslProtocols value)
 345        {
 106346            if (!IsDefined(value))
 347            {
 0348                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidEnumArgumentException(nameof(value)
 0349                    typeof(SslProtocols)));
 350            }
 106351        }
 352    }
 353
 354    internal static class ProtectionLevelHelper
 355    {
 356        internal static bool IsDefined(ProtectionLevel value)
 357        {
 358            return (value == ProtectionLevel.None
 359                || value == ProtectionLevel.Sign
 360                || value == ProtectionLevel.EncryptAndSign);
 361        }
 362
 363        internal static void Validate(ProtectionLevel value)
 364        {
 365            if (!IsDefined(value))
 366            {
 367                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidEnumArgumentException(nameof(value)
 368                    typeof(ProtectionLevel)));
 369            }
 370        }
 371
 372        internal static bool IsStronger(ProtectionLevel v1, ProtectionLevel v2)
 373        {
 374            return ((v1 == ProtectionLevel.EncryptAndSign && v2 != ProtectionLevel.EncryptAndSign)
 375                    || (v1 == ProtectionLevel.Sign && v2 == ProtectionLevel.None));
 376        }
 377
 378        internal static bool IsStrongerOrEqual(ProtectionLevel v1, ProtectionLevel v2)
 379        {
 380            return (v1 == ProtectionLevel.EncryptAndSign
 381                    || (v1 == ProtectionLevel.Sign && v2 != ProtectionLevel.EncryptAndSign));
 382        }
 383
 384        internal static ProtectionLevel Max(ProtectionLevel v1, ProtectionLevel v2)
 385        {
 386            return IsStronger(v1, v2) ? v1 : v2;
 387        }
 388
 389        internal static int GetOrdinal(Nullable<ProtectionLevel> p)
 390        {
 391            if (p.HasValue)
 392            {
 393                switch ((ProtectionLevel)p)
 394                {
 395                    default:
 396                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidEnumArgumentException(nameo
 397                    case ProtectionLevel.None:
 398                        return 2;
 399                    case ProtectionLevel.Sign:
 400                        return 3;
 401                    case ProtectionLevel.EncryptAndSign:
 402                        return 4;
 403                }
 404            }
 405            else
 406            {
 407                return 1;
 408            }
 409        }
 410    }
 411}