< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.IdentityModel.Claims.X509Identity
Assembly: CoreWCF.Primitives
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.Primitives/src/CoreWCF/IdentityModel/Claims/X509CertificateClaimSet.cs
Line coverage
12%
Covered lines: 5
Uncovered lines: 36
Coverable lines: 41
Total lines: 568
Line coverage: 12.1%
Branch coverage
3%
Covered branches: 1
Total branches: 26
Branch coverage: 3.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%1125%
.ctor(...)100%110%
.ctor(...)50%22100%
GetName()0%12120%
Clone()0%220%
Dispose()0%660%
ThrowIfDisposed()0%220%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.Primitives/src/CoreWCF/IdentityModel/Claims/X509CertificateClaimSet.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.Collections.Generic;
 6using System.Diagnostics;
 7using System.Globalization;
 8using System.Net.Mail;
 9using System.Security.Claims;
 10using System.Security.Cryptography;
 11using System.Security.Cryptography.X509Certificates;
 12using System.Security.Principal;
 13using CoreWCF.IdentityModel.Policy;
 14using CoreWCF.Security;
 15
 16namespace CoreWCF.IdentityModel.Claims
 17{
 18    public class X509CertificateClaimSet : ClaimSet, IIdentityInfo, IDisposable
 19    {
 20        private readonly X509Certificate2 _certificate;
 21        private DateTime _expirationTime = SecurityUtils.MinUtcDateTime;
 22        private ClaimSet _issuer;
 23        private X509Identity _identity;
 24        private X509ChainElementCollection _elements;
 25        private IList<Claim> _claims;
 26        private int _index;
 27        private bool _disposed = false;
 28
 29        public X509CertificateClaimSet(X509Certificate2 certificate)
 30            : this(certificate, true)
 31        {
 32        }
 33
 34        internal X509CertificateClaimSet(X509Certificate2 certificate, bool clone)
 35        {
 36            if (certificate == null)
 37            {
 38                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(certificate));
 39            }
 40
 41            _certificate = clone ? new X509Certificate2(certificate) : certificate;
 42        }
 43
 44        private X509CertificateClaimSet(X509CertificateClaimSet from)
 45            : this(from.X509Certificate, true)
 46        {
 47        }
 48
 49        private X509CertificateClaimSet(X509ChainElementCollection elements, int index)
 50        {
 51            _elements = elements;
 52            _index = index;
 53            _certificate = elements[index].Certificate;
 54        }
 55
 56        public override Claim this[int index]
 57        {
 58            get
 59            {
 60                ThrowIfDisposed();
 61                EnsureClaims();
 62                return _claims[index];
 63            }
 64        }
 65
 66        public override int Count
 67        {
 68            get
 69            {
 70                ThrowIfDisposed();
 71                EnsureClaims();
 72                return _claims.Count;
 73            }
 74        }
 75
 76        IIdentity IIdentityInfo.Identity
 77        {
 78            get
 79            {
 80                ThrowIfDisposed();
 81                if (_identity == null)
 82                {
 83                    _identity = new X509Identity(_certificate, false, false);
 84                }
 85
 86                return _identity;
 87            }
 88        }
 89
 90        public DateTime ExpirationTime
 91        {
 92            get
 93            {
 94                ThrowIfDisposed();
 95                if (_expirationTime == SecurityUtils.MinUtcDateTime)
 96                {
 97                    _expirationTime = _certificate.NotAfter.ToUniversalTime();
 98                }
 99
 100                return _expirationTime;
 101            }
 102        }
 103
 104        public override ClaimSet Issuer
 105        {
 106            get
 107            {
 108                ThrowIfDisposed();
 109                if (_issuer == null)
 110                {
 111                    if (_elements == null)
 112                    {
 113                        X509Chain chain = new X509Chain();
 114                        chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
 115                        chain.Build(_certificate);
 116                        _index = 0;
 117                        _elements = chain.ChainElements;
 118                    }
 119
 120                    if (_index + 1 < _elements.Count)
 121                    {
 122                        _issuer = new X509CertificateClaimSet(_elements, _index + 1);
 123                        _elements = null;
 124                    }
 125                    // SelfSigned?
 126                    else if (StringComparer.OrdinalIgnoreCase.Equals(_certificate.SubjectName.Name, _certificate.IssuerN
 127                    {
 128                        _issuer = this;
 129                    }
 130                    else
 131                    {
 132                        _issuer = new X500DistinguishedNameClaimSet(_certificate.IssuerName);
 133                    }
 134                }
 135                return _issuer;
 136            }
 137        }
 138
 139        public X509Certificate2 X509Certificate
 140        {
 141            get
 142            {
 143                ThrowIfDisposed();
 144                return _certificate;
 145            }
 146        }
 147
 148        internal X509CertificateClaimSet Clone()
 149        {
 150            ThrowIfDisposed();
 151            return new X509CertificateClaimSet(this);
 152        }
 153
 154        public void Dispose()
 155        {
 156            if (!_disposed)
 157            {
 158                _disposed = true;
 159                SecurityUtils.DisposeIfNecessary(_identity);
 160                if (_issuer != null)
 161                {
 162                    if (_issuer != this)
 163                    {
 164                        SecurityUtils.DisposeIfNecessary(_issuer as IDisposable);
 165                    }
 166                }
 167                if (_elements != null)
 168                {
 169                    for (int i = _index + 1; i < _elements.Count; ++i)
 170                    {
 171                        SecurityUtils.ResetCertificate(_elements[i].Certificate);
 172                    }
 173                }
 174                SecurityUtils.ResetCertificate(_certificate);
 175            }
 176        }
 177
 178        private IList<Claim> InitializeClaimsCore()
 179        {
 180            List<Claim> claims = new List<Claim>();
 181            byte[] thumbprint = _certificate.GetCertHash();
 182            claims.Add(new Claim(ClaimTypes.Thumbprint, thumbprint, Rights.Identity));
 183            claims.Add(new Claim(ClaimTypes.Thumbprint, thumbprint, Rights.PossessProperty));
 184
 185            // Ordering SubjectName, Dns, SimpleName, Email, Upn
 186            string value = _certificate.SubjectName.Name;
 187            if (!string.IsNullOrEmpty(value))
 188            {
 189                claims.Add(Claim.CreateX500DistinguishedNameClaim(_certificate.SubjectName));
 190            }
 191
 192            claims.AddRange(GetDnsClaims(_certificate));
 193
 194            value = _certificate.GetNameInfo(X509NameType.SimpleName, false);
 195            if (!string.IsNullOrEmpty(value))
 196            {
 197                claims.Add(Claim.CreateNameClaim(value));
 198            }
 199
 200            value = _certificate.GetNameInfo(X509NameType.EmailName, false);
 201            if (!string.IsNullOrEmpty(value))
 202            {
 203                claims.Add(Claim.CreateMailAddressClaim(new MailAddress(value)));
 204            }
 205
 206            value = _certificate.GetNameInfo(X509NameType.UpnName, false);
 207            if (!string.IsNullOrEmpty(value))
 208            {
 209                claims.Add(Claim.CreateUpnClaim(value));
 210            }
 211
 212            value = _certificate.GetNameInfo(X509NameType.UrlName, false);
 213            if (!string.IsNullOrEmpty(value))
 214            {
 215                claims.Add(Claim.CreateUriClaim(new Uri(value)));
 216            }
 217
 218            if (_certificate.PublicKey.Key is RSA rsa)
 219            {
 220                claims.Add(Claim.CreateRsaClaim(rsa));
 221            }
 222
 223            return claims;
 224        }
 225
 226        private void EnsureClaims()
 227        {
 228            if (_claims != null)
 229            {
 230                return;
 231            }
 232
 233            _claims = InitializeClaimsCore();
 234        }
 235
 236        private static bool SupportedClaimType(string claimType)
 237        {
 238            return claimType == null ||
 239                ClaimTypes.Thumbprint.Equals(claimType) ||
 240                ClaimTypes.X500DistinguishedName.Equals(claimType) ||
 241                ClaimTypes.Dns.Equals(claimType) ||
 242                ClaimTypes.Name.Equals(claimType) ||
 243                ClaimTypes.Email.Equals(claimType) ||
 244                ClaimTypes.Upn.Equals(claimType) ||
 245                ClaimTypes.Uri.Equals(claimType) ||
 246                ClaimTypes.Rsa.Equals(claimType);
 247        }
 248
 249        // Note: null string represents any.
 250        public override IEnumerable<Claim> FindClaims(string claimType, string right)
 251        {
 252            ThrowIfDisposed();
 253            if (!SupportedClaimType(claimType) || !SupportedRight(right))
 254            {
 255                yield break;
 256            }
 257            else if (_claims == null && ClaimTypes.Thumbprint.Equals(claimType))
 258            {
 259                if (right == null || Rights.Identity.Equals(right))
 260                {
 261                    yield return new Claim(ClaimTypes.Thumbprint, _certificate.GetCertHash(), Rights.Identity);
 262                }
 263                if (right == null || Rights.PossessProperty.Equals(right))
 264                {
 265                    yield return new Claim(ClaimTypes.Thumbprint, _certificate.GetCertHash(), Rights.PossessProperty);
 266                }
 267            }
 268            else if (_claims == null && ClaimTypes.Dns.Equals(claimType))
 269            {
 270                if (right == null || Rights.PossessProperty.Equals(right))
 271                {
 272                    foreach (Claim claim in GetDnsClaims(_certificate))
 273                    {
 274                        yield return claim;
 275                    }
 276                }
 277            }
 278            else
 279            {
 280                EnsureClaims();
 281
 282                bool anyClaimType = (claimType == null);
 283                bool anyRight = (right == null);
 284
 285                for (int i = 0; i < _claims.Count; ++i)
 286                {
 287                    Claim claim = _claims[i];
 288                    if ((claim != null) &&
 289                        (anyClaimType || claimType.Equals(claim.ClaimType)) &&
 290                        (anyRight || right.Equals(claim.Right)))
 291                    {
 292                        yield return claim;
 293                    }
 294                }
 295            }
 296        }
 297
 298        private static List<Claim> GetDnsClaims(X509Certificate2 cert)
 299        {
 300            List<Claim> dnsClaimEntries = new List<Claim>();
 301
 302            // old behavior, default for <= 4.6
 303            string value = cert.GetNameInfo(X509NameType.DnsName, false);
 304            if (!string.IsNullOrEmpty(value))
 305            {
 306                dnsClaimEntries.Add(Claim.CreateDnsClaim(value));
 307            }
 308
 309            return dnsClaimEntries;
 310        }
 311
 312        public override IEnumerator<Claim> GetEnumerator()
 313        {
 314            ThrowIfDisposed();
 315            EnsureClaims();
 316            return _claims.GetEnumerator();
 317        }
 318
 319        public override string ToString()
 320        {
 321            return _disposed ? base.ToString() : SecurityUtils.ClaimSetToString(this);
 322        }
 323
 324        private void ThrowIfDisposed()
 325        {
 326            if (_disposed)
 327            {
 328                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ObjectDisposedException(GetType().FullName
 329            }
 330        }
 331
 332        private class X500DistinguishedNameClaimSet : DefaultClaimSet, IIdentityInfo
 333        {
 334            public X500DistinguishedNameClaimSet(X500DistinguishedName x500DistinguishedName)
 335            {
 336                if (x500DistinguishedName == null)
 337                {
 338                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(x500DistinguishedName));
 339                }
 340
 341                Identity = new X509Identity(x500DistinguishedName);
 342                List<Claim> claims = new List<Claim>(2)
 343                {
 344                    new Claim(ClaimTypes.X500DistinguishedName, x500DistinguishedName, Rights.Identity),
 345                    Claim.CreateX500DistinguishedNameClaim(x500DistinguishedName)
 346                };
 347                Initialize(Anonymous, claims);
 348            }
 349
 350            public IIdentity Identity { get; }
 351        }
 352
 353        // We don't have a strongly typed extension to parse Subject Alt Names, so we have to do a workaround
 354        // to figure out what the identifier, delimiter, and separator is by using a well-known extension
 355        private static class X509SubjectAlternativeNameConstants
 356        {
 357            public const string SanOid = "2.5.29.7";
 358            public const string San2Oid = "2.5.29.17";
 359
 360            public static string Identifier
 361            {
 362                get;
 363                private set;
 364            }
 365
 366            public static char Delimiter
 367            {
 368                get;
 369                private set;
 370            }
 371
 372            public static string Separator
 373            {
 374                get;
 375                private set;
 376            }
 377
 378            public static string[] SeparatorArray
 379            {
 380                get;
 381                private set;
 382            }
 383
 384            public static bool SuccessfullyInitialized
 385            {
 386                get;
 387                private set;
 388            }
 389
 390
 391            // static initializer will run before properties are accessed
 392            [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Ju
 393            static X509SubjectAlternativeNameConstants()
 394            {
 395                // Extracted a well-known X509Extension
 396                byte[] x509ExtensionBytes = new byte[] {
 397                    48, 36, 130, 21, 110, 111, 116, 45, 114, 101, 97, 108, 45, 115, 117, 98, 106, 101, 99,
 398                    116, 45, 110, 97, 109, 101, 130, 11, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109
 399                };
 400                const string subjectName = "not-real-subject-name";
 401                string x509ExtensionFormattedString = string.Empty;
 402                try
 403                {
 404                    X509Extension x509Extension = new X509Extension(SanOid, x509ExtensionBytes, true);
 405                    x509ExtensionFormattedString = x509Extension.Format(false);
 406
 407                    // Each OS has a different dNSName identifier and delimiter
 408                    // On Windows, dNSName == "DNS Name" (localizable), on Linux, dNSName == "DNS"
 409                    // e.g.,
 410                    // Windows: x509ExtensionFormattedString is: "DNS Name=not-real-subject-name, DNS Name=example.com"
 411                    // Linux:   x509ExtensionFormattedString is: "DNS:not-real-subject-name, DNS:example.com"
 412                    // Parse: <identifier><delimiter><value><separator(s)>
 413
 414                    int delimiterIndex = x509ExtensionFormattedString.IndexOf(subjectName) - 1;
 415                    Delimiter = x509ExtensionFormattedString[delimiterIndex];
 416
 417                    // Make an assumption that all characters from the the start of string to the delimiter
 418                    // are part of the identifier
 419                    Identifier = x509ExtensionFormattedString.Substring(0, delimiterIndex);
 420
 421                    int separatorFirstChar = delimiterIndex + subjectName.Length + 1;
 422                    int separatorLength = 1;
 423                    for (int i = separatorFirstChar + 1; i < x509ExtensionFormattedString.Length; i++)
 424                    {
 425                        // We advance until the first character of the identifier to determine what the
 426                        // separator is. This assumes that the identifier assumption above is correct
 427                        if (x509ExtensionFormattedString[i] == Identifier[0])
 428                        {
 429                            break;
 430                        }
 431
 432                        separatorLength++;
 433                    }
 434
 435                    Separator = x509ExtensionFormattedString.Substring(separatorFirstChar, separatorLength);
 436                    SeparatorArray = new string[1] { Separator };
 437                    SuccessfullyInitialized = true;
 438                }
 439                catch (Exception ex)
 440                {
 441                    SuccessfullyInitialized = false;
 442                    DiagnosticUtility.TraceHandledException(
 443                        new FormatException(string.Format(CultureInfo.InvariantCulture,
 444                        "There was an error parsing the SubjectAlternativeNames: '{0}'. See inner exception for more det
 445                        x509ExtensionFormattedString,
 446                        Environment.NewLine,
 447                        Identifier,
 448                        Delimiter,
 449                        Separator),
 450                        ex),
 451                        TraceEventType.Warning);
 452                }
 453            }
 454        }
 455    }
 456
 457    internal class X509Identity : GenericIdentity, IDisposable
 458    {
 459        private const string X509 = "X509";
 460        private const string Thumbprint = "; ";
 461        private readonly X500DistinguishedName _x500DistinguishedName;
 462        private readonly X509Certificate2 _certificate;
 463        private string _name;
 464        private bool _disposed = false;
 4465        private readonly bool _disposable = true;
 466
 467        public X509Identity(X509Certificate2 certificate)
 0468            : this(certificate, true, true)
 469        {
 0470        }
 471
 472        public X509Identity(X500DistinguishedName x500DistinguishedName)
 0473            : base(X509, X509)
 474        {
 0475            _x500DistinguishedName = x500DistinguishedName;
 0476        }
 477
 478        internal X509Identity(X509Certificate2 certificate, bool clone, bool disposable)
 4479            : base(X509, X509)
 480        {
 4481            _certificate = clone ? new X509Certificate2(certificate) : certificate;
 4482            _disposable = clone || disposable;
 4483        }
 484
 485        public override string Name
 486        {
 487            get
 488            {
 0489                ThrowIfDisposed();
 0490                if (_name == null)
 491                {
 492                    //
 493                    // DCR 48092: PrincipalPermission authorization using certificates could cause Elevation of Privileg
 494                    // because there could be duplicate subject name.  In order to be more unique, we use SubjectName + 
 495                    // instead
 496                    //
 0497                    _name = GetName() + Thumbprint + _certificate.Thumbprint;
 498                }
 0499                return _name;
 500            }
 501        }
 502
 503        private string GetName()
 504        {
 0505            if (_x500DistinguishedName != null)
 506            {
 0507                return _x500DistinguishedName.Name;
 508            }
 509
 0510            string value = _certificate.SubjectName.Name;
 0511            if (!string.IsNullOrEmpty(value))
 512            {
 0513                return value;
 514            }
 515
 0516            value = _certificate.GetNameInfo(X509NameType.DnsName, false);
 0517            if (!string.IsNullOrEmpty(value))
 518            {
 0519                return value;
 520            }
 521
 0522            value = _certificate.GetNameInfo(X509NameType.SimpleName, false);
 0523            if (!string.IsNullOrEmpty(value))
 524            {
 0525                return value;
 526            }
 527
 0528            value = _certificate.GetNameInfo(X509NameType.EmailName, false);
 0529            if (!string.IsNullOrEmpty(value))
 530            {
 0531                return value;
 532            }
 533
 0534            value = _certificate.GetNameInfo(X509NameType.UpnName, false);
 0535            if (!string.IsNullOrEmpty(value))
 536            {
 0537                return value;
 538            }
 539
 0540            return string.Empty;
 541        }
 542
 543        public override ClaimsIdentity Clone()
 544        {
 0545            return _certificate != null ? new X509Identity(_certificate) : new X509Identity(_x500DistinguishedName);
 546        }
 547
 548        public void Dispose()
 549        {
 0550            if (_disposable && !_disposed)
 551            {
 0552                _disposed = true;
 0553                if (_certificate != null)
 554                {
 0555                    _certificate.Reset();
 556                }
 557            }
 0558        }
 559
 560        private void ThrowIfDisposed()
 561        {
 0562            if (_disposed)
 563            {
 0564                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ObjectDisposedException(GetType().FullName
 565            }
 0566        }
 567    }
 568}