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
;
17 public class X509CertificateClaimSet
: ClaimSet
, IIdentityInfo
, IDisposable
19 X509Certificate2 certificate
;
20 DateTime expirationTime
= SecurityUtils
.MinUtcDateTime
;
22 X509Identity identity
;
23 X509ChainElementCollection elements
;
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
;
49 this.certificate
= elements
[index
].Certificate
;
52 public override Claim
this[int index
]
58 return this.claims
[index
];
62 public override int Count
68 return this.claims
.Count
;
72 IIdentity IIdentityInfo
.Identity
77 if (this.identity
== null)
78 this.identity
= new X509Identity(this.certificate
, false, false);
83 public DateTime ExpirationTime
88 if (this.expirationTime
== SecurityUtils
.MinUtcDateTime
)
89 this.expirationTime
= this.certificate
.NotAfter
.ToUniversalTime();
90 return this.expirationTime
;
94 public override ClaimSet Issuer
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
);
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;
116 else if (StringComparer
.OrdinalIgnoreCase
.Equals(this.certificate
.SubjectName
.Name
, this.certificate
.IssuerName
.Name
))
119 this.issuer
= new X500DistinguishedNameClaimSet(this.certificate
.IssuerName
);
126 public X509Certificate2 X509Certificate
131 return this.certificate
;
135 internal X509CertificateClaimSet
Clone()
138 return new X509CertificateClaimSet(this);
141 public void Dispose()
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)));
196 if (LocalAppContextSwitches
.DisableCngCertificates
)
198 rsa
= this.certificate
.PublicKey
.Key
as RSA
;
202 rsa
= CngLightup
.GetRSAPublicKey(this.certificate
);
205 claims
.Add(Claim
.CreateRsaClaim(rsa
));
212 if (this.claims
!= null)
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
)
235 if (!SupportedClaimType(claimType
) || !ClaimSet
.SupportedRight(right
))
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
))
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
)))
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
))
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()
322 return this.claims
.GetEnumerator();
325 public override string ToString()
327 return this.disposed
? base.ToString() : SecurityUtils
.ClaimSetToString(this);
330 void ThrowIfDisposed()
334 throw DiagnosticUtility
.ExceptionUtility
.ThrowHelperError(new ObjectDisposedException(this.GetType().FullName
));
338 class X500DistinguishedNameClaimSet
: DefaultClaimSet
, IIdentityInfo
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
373 public static char Delimiter
379 public static string Separator
385 public static string[] SeparatorArray
391 public static bool SuccessfullyInitialized
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"
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])
440 Separator
= x509ExtensionFormattedString
.Substring(separatorFirstChar
, separatorLength
);
441 SeparatorArray
= new string[1] { Separator }
;
442 SuccessfullyInitialized
= true;
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
,
456 TraceEventType
.Warning
);
462 class X509Identity
: GenericIdentity
, IDisposable
464 const string X509
= "X509";
465 const string Thumbprint
= "; ";
466 X500DistinguishedName x500DistinguishedName
;
467 X509Certificate2 certificate
;
469 bool disposed
= false;
470 bool disposable
= true;
472 public X509Identity(X509Certificate2 certificate
)
473 : this(certificate
, true, true)
477 public X509Identity(X500DistinguishedName x500DistinguishedName
)
480 this.x500DistinguishedName
= x500DistinguishedName
;
483 internal X509Identity(X509Certificate2 certificate
, bool clone
, bool disposable
)
486 this.certificate
= clone
? new X509Certificate2(certificate
) : certificate
;
487 this.disposable
= clone
|| disposable
;
490 public override string Name
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
502 this.name
= GetName() + Thumbprint
+ this.certificate
.Thumbprint
;
510 if (this.x500DistinguishedName
!= null)
511 return this.x500DistinguishedName
.Name
;
513 string value = this.certificate
.SubjectName
.Name
;
514 if (!string.IsNullOrEmpty(value))
517 value = this.certificate
.GetNameInfo(X509NameType
.DnsName
, false);
518 if (!string.IsNullOrEmpty(value))
521 value = this.certificate
.GetNameInfo(X509NameType
.SimpleName
, false);
522 if (!string.IsNullOrEmpty(value))
525 value = this.certificate
.GetNameInfo(X509NameType
.EmailName
, false);
526 if (!string.IsNullOrEmpty(value))
529 value = this.certificate
.GetNameInfo(X509NameType
.UpnName
, false);
530 if (!string.IsNullOrEmpty(value))
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()
557 throw DiagnosticUtility
.ExceptionUtility
.ThrowHelperError(new ObjectDisposedException(this.GetType().FullName
));