Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.IdentityModel / System / IdentityModel / Claims / X509CertificateClaimSet.cs
blobcf6bf7762e7f480ef04fa5120a849a4ebc7035b0
1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //------------------------------------------------------------
5 namespace System.IdentityModel.Claims
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.IdentityModel.Policy;
10 using System.Net.Mail;
11 using System.Security.Claims;
12 using System.Security.Cryptography;
13 using System.Security.Cryptography.X509Certificates;
14 using System.Security.Principal;
15 using Globalization;
17 public class X509CertificateClaimSet : ClaimSet, IIdentityInfo, IDisposable
19 X509Certificate2 certificate;
20 DateTime expirationTime = SecurityUtils.MinUtcDateTime;
21 ClaimSet issuer;
22 X509Identity identity;
23 X509ChainElementCollection elements;
24 IList<Claim> claims;
25 int index;
26 bool disposed = false;
28 public X509CertificateClaimSet(X509Certificate2 certificate)
29 : this(certificate, true)
33 internal X509CertificateClaimSet(X509Certificate2 certificate, bool clone)
35 if (certificate == null)
36 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("certificate");
37 this.certificate = clone ? new X509Certificate2(certificate) : certificate;
40 X509CertificateClaimSet(X509CertificateClaimSet from)
41 : this(from.X509Certificate, true)
45 X509CertificateClaimSet(X509ChainElementCollection elements, int index)
47 this.elements = elements;
48 this.index = index;
49 this.certificate = elements[index].Certificate;
52 public override Claim this[int index]
54 get
56 ThrowIfDisposed();
57 EnsureClaims();
58 return this.claims[index];
62 public override int Count
64 get
66 ThrowIfDisposed();
67 EnsureClaims();
68 return this.claims.Count;
72 IIdentity IIdentityInfo.Identity
74 get
76 ThrowIfDisposed();
77 if (this.identity == null)
78 this.identity = new X509Identity(this.certificate, false, false);
79 return this.identity;
83 public DateTime ExpirationTime
85 get
87 ThrowIfDisposed();
88 if (this.expirationTime == SecurityUtils.MinUtcDateTime)
89 this.expirationTime = this.certificate.NotAfter.ToUniversalTime();
90 return this.expirationTime;
94 public override ClaimSet Issuer
96 get
98 ThrowIfDisposed();
99 if (this.issuer == null)
101 if (this.elements == null)
103 X509Chain chain = new X509Chain();
104 chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
105 chain.Build(certificate);
106 this.index = 0;
107 this.elements = chain.ChainElements;
110 if (this.index + 1 < this.elements.Count)
112 this.issuer = new X509CertificateClaimSet(this.elements, this.index + 1);
113 this.elements = null;
115 // SelfSigned?
116 else if (StringComparer.OrdinalIgnoreCase.Equals(this.certificate.SubjectName.Name, this.certificate.IssuerName.Name))
117 this.issuer = this;
118 else
119 this.issuer = new X500DistinguishedNameClaimSet(this.certificate.IssuerName);
122 return this.issuer;
126 public X509Certificate2 X509Certificate
130 ThrowIfDisposed();
131 return this.certificate;
135 internal X509CertificateClaimSet Clone()
137 ThrowIfDisposed();
138 return new X509CertificateClaimSet(this);
141 public void Dispose()
143 if (!this.disposed)
145 this.disposed = true;
146 SecurityUtils.DisposeIfNecessary(this.identity);
147 if (this.issuer != null)
149 if (this.issuer != this)
151 SecurityUtils.DisposeIfNecessary(this.issuer as IDisposable);
154 if (this.elements != null)
156 for (int i = this.index + 1; i < this.elements.Count; ++i)
158 SecurityUtils.ResetCertificate(this.elements[i].Certificate);
161 SecurityUtils.ResetCertificate(this.certificate);
165 IList<Claim> InitializeClaimsCore()
167 List<Claim> claims = new List<Claim>();
168 byte[] thumbprint = this.certificate.GetCertHash();
169 claims.Add(new Claim(ClaimTypes.Thumbprint, thumbprint, Rights.Identity));
170 claims.Add(new Claim(ClaimTypes.Thumbprint, thumbprint, Rights.PossessProperty));
172 // Ordering SubjectName, Dns, SimpleName, Email, Upn
173 string value = this.certificate.SubjectName.Name;
174 if (!string.IsNullOrEmpty(value))
175 claims.Add(Claim.CreateX500DistinguishedNameClaim(this.certificate.SubjectName));
177 claims.AddRange(GetDnsClaims(this.certificate));
179 value = this.certificate.GetNameInfo(X509NameType.SimpleName, false);
180 if (!string.IsNullOrEmpty(value))
181 claims.Add(Claim.CreateNameClaim(value));
183 value = this.certificate.GetNameInfo(X509NameType.EmailName, false);
184 if (!string.IsNullOrEmpty(value))
185 claims.Add(Claim.CreateMailAddressClaim(new MailAddress(value)));
187 value = this.certificate.GetNameInfo(X509NameType.UpnName, false);
188 if (!string.IsNullOrEmpty(value))
189 claims.Add(Claim.CreateUpnClaim(value));
191 value = this.certificate.GetNameInfo(X509NameType.UrlName, false);
192 if (!string.IsNullOrEmpty(value))
193 claims.Add(Claim.CreateUriClaim(new Uri(value)));
195 RSA rsa;
196 if (LocalAppContextSwitches.DisableCngCertificates)
198 rsa = this.certificate.PublicKey.Key as RSA;
200 else
202 rsa = CngLightup.GetRSAPublicKey(this.certificate);
204 if (rsa != null)
205 claims.Add(Claim.CreateRsaClaim(rsa));
207 return claims;
210 void EnsureClaims()
212 if (this.claims != null)
213 return;
215 this.claims = InitializeClaimsCore();
218 static bool SupportedClaimType(string claimType)
220 return claimType == null ||
221 ClaimTypes.Thumbprint.Equals(claimType) ||
222 ClaimTypes.X500DistinguishedName.Equals(claimType) ||
223 ClaimTypes.Dns.Equals(claimType) ||
224 ClaimTypes.Name.Equals(claimType) ||
225 ClaimTypes.Email.Equals(claimType) ||
226 ClaimTypes.Upn.Equals(claimType) ||
227 ClaimTypes.Uri.Equals(claimType) ||
228 ClaimTypes.Rsa.Equals(claimType);
231 // Note: null string represents any.
232 public override IEnumerable<Claim> FindClaims(string claimType, string right)
234 ThrowIfDisposed();
235 if (!SupportedClaimType(claimType) || !ClaimSet.SupportedRight(right))
237 yield break;
239 else if (this.claims == null && ClaimTypes.Thumbprint.Equals(claimType))
241 if (right == null || Rights.Identity.Equals(right))
243 yield return new Claim(ClaimTypes.Thumbprint, this.certificate.GetCertHash(), Rights.Identity);
245 if (right == null || Rights.PossessProperty.Equals(right))
247 yield return new Claim(ClaimTypes.Thumbprint, this.certificate.GetCertHash(), Rights.PossessProperty);
250 else if (this.claims == null && ClaimTypes.Dns.Equals(claimType))
252 if (right == null || Rights.PossessProperty.Equals(right))
254 foreach (var claim in GetDnsClaims(certificate))
255 yield return claim;
258 else
260 EnsureClaims();
262 bool anyClaimType = (claimType == null);
263 bool anyRight = (right == null);
265 for (int i = 0; i < this.claims.Count; ++i)
267 Claim claim = this.claims[i];
268 if ((claim != null) &&
269 (anyClaimType || claimType.Equals(claim.ClaimType)) &&
270 (anyRight || right.Equals(claim.Right)))
272 yield return claim;
278 private static List<Claim> GetDnsClaims(X509Certificate2 cert)
280 List<Claim> dnsClaimEntries = new List<Claim>();
282 // old behavior, default for <= 4.6
283 string value = cert.GetNameInfo(X509NameType.DnsName, false);
284 if (!string.IsNullOrEmpty(value))
285 dnsClaimEntries.Add(Claim.CreateDnsClaim(value));
287 // App context switch for disabling support for multiple dns entries in a SAN certificate
288 // If we can't dynamically parse the alt subject names, we will not add any dns claims ONLY for the alt subject names.
289 // In this way, if the X509NameType.DnsName was enough to succeed for the out-bound-message. We would have a success.
290 if (!LocalAppContextSwitches.DisableMultipleDNSEntriesInSANCertificate && X509SubjectAlternativeNameConstants.SuccessfullyInitialized)
292 foreach (X509Extension ext in cert.Extensions)
294 // Extension is SAN or SAN2
295 if (ext.Oid.Value == X509SubjectAlternativeNameConstants.SanOid || ext.Oid.Value == X509SubjectAlternativeNameConstants.San2Oid)
297 string asnString = ext.Format(false);
298 if (string.IsNullOrWhiteSpace(asnString))
299 break;
301 // SubjectAlternativeNames might contain something other than a dNSName,
302 // so we have to parse through and only use the dNSNames
303 // <identifier><delimiter><value><separator(s)>
304 string[] rawDnsEntries = asnString.Split(X509SubjectAlternativeNameConstants.SeparatorArray, StringSplitOptions.RemoveEmptyEntries);
305 for (int i = 0; i < rawDnsEntries.Length; i++)
307 string[] keyval = rawDnsEntries[i].Split(X509SubjectAlternativeNameConstants.Delimiter);
308 if (string.Equals(keyval[0], X509SubjectAlternativeNameConstants.Identifier))
309 dnsClaimEntries.Add(Claim.CreateDnsClaim(keyval[1]));
315 return dnsClaimEntries;
318 public override IEnumerator<Claim> GetEnumerator()
320 ThrowIfDisposed();
321 EnsureClaims();
322 return this.claims.GetEnumerator();
325 public override string ToString()
327 return this.disposed ? base.ToString() : SecurityUtils.ClaimSetToString(this);
330 void ThrowIfDisposed()
332 if (this.disposed)
334 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ObjectDisposedException(this.GetType().FullName));
338 class X500DistinguishedNameClaimSet : DefaultClaimSet, IIdentityInfo
340 IIdentity identity;
342 public X500DistinguishedNameClaimSet(X500DistinguishedName x500DistinguishedName)
344 if (x500DistinguishedName == null)
345 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("x500DistinguishedName");
347 this.identity = new X509Identity(x500DistinguishedName);
348 List<Claim> claims = new List<Claim>(2);
349 claims.Add(new Claim(ClaimTypes.X500DistinguishedName, x500DistinguishedName, Rights.Identity));
350 claims.Add(Claim.CreateX500DistinguishedNameClaim(x500DistinguishedName));
351 Initialize(ClaimSet.Anonymous, claims);
354 public IIdentity Identity
356 get { return this.identity; }
360 // We don't have a strongly typed extension to parse Subject Alt Names, so we have to do a workaround
361 // to figure out what the identifier, delimiter, and separator is by using a well-known extension
362 private static class X509SubjectAlternativeNameConstants
364 public const string SanOid = "2.5.29.7";
365 public const string San2Oid = "2.5.29.17";
367 public static string Identifier
369 get;
370 private set;
373 public static char Delimiter
375 get;
376 private set;
379 public static string Separator
381 get;
382 private set;
385 public static string[] SeparatorArray
387 get;
388 private set;
391 public static bool SuccessfullyInitialized
393 get;
394 private set;
397 // static initializer will run before properties are accessed
398 static X509SubjectAlternativeNameConstants()
400 // Extracted a well-known X509Extension
401 byte[] x509ExtensionBytes = new byte[] {
402 48, 36, 130, 21, 110, 111, 116, 45, 114, 101, 97, 108, 45, 115, 117, 98, 106, 101, 99,
403 116, 45, 110, 97, 109, 101, 130, 11, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109
405 const string subjectName = "not-real-subject-name";
406 string x509ExtensionFormattedString = string.Empty;
409 X509Extension x509Extension = new X509Extension(SanOid, x509ExtensionBytes, true);
410 x509ExtensionFormattedString = x509Extension.Format(false);
412 // Each OS has a different dNSName identifier and delimiter
413 // On Windows, dNSName == "DNS Name" (localizable), on Linux, dNSName == "DNS"
414 // e.g.,
415 // Windows: x509ExtensionFormattedString is: "DNS Name=not-real-subject-name, DNS Name=example.com"
416 // Linux: x509ExtensionFormattedString is: "DNS:not-real-subject-name, DNS:example.com"
417 // Parse: <identifier><delimiter><value><separator(s)>
419 int delimiterIndex = x509ExtensionFormattedString.IndexOf(subjectName) - 1;
420 Delimiter = x509ExtensionFormattedString[delimiterIndex];
422 // Make an assumption that all characters from the the start of string to the delimiter
423 // are part of the identifier
424 Identifier = x509ExtensionFormattedString.Substring(0, delimiterIndex);
426 int separatorFirstChar = delimiterIndex + subjectName.Length + 1;
427 int separatorLength = 1;
428 for (int i = separatorFirstChar + 1; i < x509ExtensionFormattedString.Length; i++)
430 // We advance until the first character of the identifier to determine what the
431 // separator is. This assumes that the identifier assumption above is correct
432 if (x509ExtensionFormattedString[i] == Identifier[0])
434 break;
437 separatorLength++;
440 Separator = x509ExtensionFormattedString.Substring(separatorFirstChar, separatorLength);
441 SeparatorArray = new string[1] { Separator };
442 SuccessfullyInitialized = true;
444 catch (Exception ex)
446 SuccessfullyInitialized = false;
447 DiagnosticUtility.TraceHandledException(
448 new FormatException(string.Format(CultureInfo.InvariantCulture,
449 "There was an error parsing the SubjectAlternativeNames: '{0}'. See inner exception for more details.{1}Detected values were: Identifier: '{2}'; Delimiter:'{3}'; Separator:'{4}'",
450 x509ExtensionFormattedString,
451 Environment.NewLine,
452 Identifier,
453 Delimiter,
454 Separator),
455 ex),
456 TraceEventType.Warning);
462 class X509Identity : GenericIdentity, IDisposable
464 const string X509 = "X509";
465 const string Thumbprint = "; ";
466 X500DistinguishedName x500DistinguishedName;
467 X509Certificate2 certificate;
468 string name;
469 bool disposed = false;
470 bool disposable = true;
472 public X509Identity(X509Certificate2 certificate)
473 : this(certificate, true, true)
477 public X509Identity(X500DistinguishedName x500DistinguishedName)
478 : base(X509, X509)
480 this.x500DistinguishedName = x500DistinguishedName;
483 internal X509Identity(X509Certificate2 certificate, bool clone, bool disposable)
484 : base(X509, X509)
486 this.certificate = clone ? new X509Certificate2(certificate) : certificate;
487 this.disposable = clone || disposable;
490 public override string Name
494 ThrowIfDisposed();
495 if (this.name == null)
498 // DCR 48092: PrincipalPermission authorization using certificates could cause Elevation of Privilege.
499 // because there could be duplicate subject name. In order to be more unique, we use SubjectName + Thumbprint
500 // instead
502 this.name = GetName() + Thumbprint + this.certificate.Thumbprint;
504 return this.name;
508 string GetName()
510 if (this.x500DistinguishedName != null)
511 return this.x500DistinguishedName.Name;
513 string value = this.certificate.SubjectName.Name;
514 if (!string.IsNullOrEmpty(value))
515 return value;
517 value = this.certificate.GetNameInfo(X509NameType.DnsName, false);
518 if (!string.IsNullOrEmpty(value))
519 return value;
521 value = this.certificate.GetNameInfo(X509NameType.SimpleName, false);
522 if (!string.IsNullOrEmpty(value))
523 return value;
525 value = this.certificate.GetNameInfo(X509NameType.EmailName, false);
526 if (!string.IsNullOrEmpty(value))
527 return value;
529 value = this.certificate.GetNameInfo(X509NameType.UpnName, false);
530 if (!string.IsNullOrEmpty(value))
531 return value;
533 return String.Empty;
536 public override ClaimsIdentity Clone()
538 return this.certificate != null ? new X509Identity(this.certificate) : new X509Identity(this.x500DistinguishedName);
541 public void Dispose()
543 if (this.disposable && !this.disposed)
545 this.disposed = true;
546 if (this.certificate != null)
548 this.certificate.Reset();
553 void ThrowIfDisposed()
555 if (this.disposed)
557 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ObjectDisposedException(this.GetType().FullName));