[boehm] Put *_freelists into thread_local_freelists (as in BDWGC v7)
[mono.git] / mcs / class / System / Mono.Net.Security / SystemCertificateValidator.cs
1 #if SECURITY_DEP
2
3 #if MONO_SECURITY_ALIAS
4 extern alias MonoSecurity;
5 #endif
6 #if MONO_X509_ALIAS
7 extern alias PrebuiltSystem;
8 #endif
9
10 #if MONO_SECURITY_ALIAS
11 using MonoSecurity::Mono.Security.Interface;
12 using MSX = MonoSecurity::Mono.Security.X509;
13 using MonoSecurity::Mono.Security.X509.Extensions;
14 #else
15 using Mono.Security.Interface;
16 using MSX = Mono.Security.X509;
17 using Mono.Security.X509.Extensions;
18 #endif
19 #if MONO_X509_ALIAS
20 using XX509CertificateCollection = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509CertificateCollection;
21 #else
22 using XX509CertificateCollection = System.Security.Cryptography.X509Certificates.X509CertificateCollection;
23 #endif
24
25 using System;
26 using System.Net;
27 using System.Threading;
28 using System.Collections;
29 using System.Collections.Generic;
30 using System.Collections.Specialized;
31 using System.Configuration;
32 using System.Net.Configuration;
33 using System.Text.RegularExpressions;
34 using System.Security.Cryptography.X509Certificates;
35
36 using System.Globalization;
37 using System.Net.Security;
38 using System.Diagnostics;
39
40 namespace Mono.Net.Security
41 {
42         internal static class SystemCertificateValidator
43         {
44                 static bool is_macosx;
45                 static bool is_mobile;
46 #if !MOBILE
47                 static X509RevocationMode revocation_mode;
48 #endif
49
50                 static SystemCertificateValidator ()
51                 {
52 #if MONOTOUCH
53                         is_macosx = true;
54                         is_mobile = true;
55 #elif MONODROID
56                         is_macosx = false;
57                         is_mobile = true;
58 #else
59                         is_macosx = System.IO.File.Exists (OSX509Certificates.SecurityLibrary);
60                         is_mobile = false;
61 #endif
62
63 #if !MOBILE
64                         revocation_mode = X509RevocationMode.NoCheck;
65                         try {
66                                 string str = Environment.GetEnvironmentVariable ("MONO_X509_REVOCATION_MODE");
67                                 if (String.IsNullOrEmpty (str))
68                                         return;
69                                 revocation_mode = (X509RevocationMode)Enum.Parse (typeof(X509RevocationMode), str, true);
70                         } catch {
71                         }
72 #endif
73                 }
74
75                 static X509Chain ComputeX509Chain (XX509CertificateCollection certs, ref SslPolicyErrors errors, ref int status11)
76                 {
77 #if MOBILE
78                         return null;
79 #else
80                         if (is_macosx)
81                                 return null;
82
83                         var chain = new X509Chain ();
84                         chain.ChainPolicy = new X509ChainPolicy ();
85
86                         chain.ChainPolicy.RevocationMode = revocation_mode;
87
88                         for (int i = 1; i < certs.Count; i++) {
89                                 chain.ChainPolicy.ExtraStore.Add (certs [i]);
90                         }
91
92                         var leaf = (X509Certificate2)certs [0];
93
94                         try {
95                                 if (!chain.Build (leaf))
96                                         errors |= GetErrorsFromChain (chain);
97                         } catch (Exception e) {
98                                 Console.Error.WriteLine ("ERROR building certificate chain: {0}", e);
99                                 Console.Error.WriteLine ("Please, report this problem to the Mono team");
100                                 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
101                         }
102
103                         status11 = GetStatusFromChain (chain);
104
105                         return chain;
106 #endif
107                 }
108
109                 static void CheckUsage (XX509CertificateCollection certs, string host, ref SslPolicyErrors errors, ref int status11)
110                 {
111 #if !MONOTOUCH
112                         var leaf = (X509Certificate2)certs[0];
113                         // for OSX and iOS we're using the native API to check for the SSL server policy and host names
114                         if (!is_macosx) {
115                                 if (!CheckCertificateUsage (leaf)) {
116                                         errors |= SslPolicyErrors.RemoteCertificateChainErrors;
117                                         status11 = -2146762490; //CERT_E_PURPOSE 0x800B0106
118                                 }
119
120                                 if (host != null && !CheckServerIdentity (leaf, host)) {
121                                         errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
122                                         status11 = -2146762481; // CERT_E_CN_NO_MATCH 0x800B010F
123                                 }
124                         }
125 #endif
126                 }
127
128                 static bool EvaluateSystem (XX509CertificateCollection certs, XX509CertificateCollection anchors, string host, X509Chain chain, ref SslPolicyErrors errors, ref int status11)
129                 {
130                         var leaf = certs [0];
131                         var result = false;
132
133 #if MONODROID
134                         result = AndroidPlatform.TrustEvaluateSsl (certs);
135                         if (result) {
136                                 // chain.Build() + GetErrorsFromChain() (above) will ALWAYS fail on
137                                 // Android (there are no mozroots or preinstalled root certificates),
138                                 // thus `errors` will ALWAYS have RemoteCertificateChainErrors.
139                                 // Android just verified the chain; clear RemoteCertificateChainErrors.
140                                 errors  &= ~SslPolicyErrors.RemoteCertificateChainErrors;
141                         }
142 #else
143                         if (is_macosx) {
144                                 // Attempt to use OSX certificates
145                                 // Ideally we should return the SecTrustResult
146                                 OSX509Certificates.SecTrustResult trustResult = OSX509Certificates.SecTrustResult.Deny;
147                                 try {
148                                         trustResult = OSX509Certificates.TrustEvaluateSsl (certs, anchors, host);
149                                         // We could use the other values of trustResult to pass this extra information
150                                         // to the .NET 2 callback for values like SecTrustResult.Confirm
151                                         result = (trustResult == OSX509Certificates.SecTrustResult.Proceed ||
152                                                 trustResult == OSX509Certificates.SecTrustResult.Unspecified);
153                                 } catch {
154                                         // Ignore
155                                 }
156
157                                 if (result) {
158                                         // TrustEvaluateSsl was successful so there's no trust error
159                                         // IOW we discard our own chain (since we trust OSX one instead)
160                                         errors = 0;
161                                 } else {
162                                         // callback and DefaultCertificatePolicy needs this since 'result' is not specified
163                                         status11 = (int)trustResult;
164                                         errors |= SslPolicyErrors.RemoteCertificateChainErrors;
165                                 }
166                         }
167 #endif
168
169                         return result;
170                 }
171
172                 public static bool Evaluate (
173                         MonoTlsSettings settings, string host, XX509CertificateCollection certs,
174                         ref X509Chain chain, ref SslPolicyErrors errors, ref int status11)
175                 {
176 #if !MOBILE
177                         if (NeedsChain (settings) && chain == null)
178                                 chain = ComputeX509Chain (certs, ref errors, ref status11);
179 #endif
180
181                         CheckUsage (certs, host, ref errors, ref status11);
182
183                         if (settings != null && settings.SkipSystemValidators)
184                                 return false;
185
186                         var anchors = settings != null ? settings.TrustAnchors : null;
187                         return EvaluateSystem (certs, anchors, host, chain, ref errors, ref status11);
188                 }
189
190                 internal static bool NeedsChain (MonoTlsSettings settings)
191                 {
192 #if MOBILE
193                         return false;
194 #else
195                         if (!CertificateValidationHelper.SupportsX509Chain)
196                                 return false;
197                         if (settings != null)
198                                 return !settings.SkipSystemValidators || settings.CallbackNeedsCertificateChain;
199                         else
200                                 return true;
201 #endif
202                 }
203
204 #if !MOBILE
205                 static int GetStatusFromChain (X509Chain chain)
206                 {
207                         long result = 0;
208                         foreach (var status in chain.ChainStatus) {
209                                 X509ChainStatusFlags flags = status.Status;
210                                 if (flags == X509ChainStatusFlags.NoError)
211                                         continue;
212
213                                 // CERT_E_EXPIRED
214                                 if ((flags & X509ChainStatusFlags.NotTimeValid) != 0)
215                                         result = 0x800B0101;
216                                 // CERT_E_VALIDITYPERIODNESTING
217                                 else if ((flags & X509ChainStatusFlags.NotTimeNested) != 0)
218                                         result = 0x800B0102;
219                                 // CERT_E_REVOKED
220                                 else if ((flags & X509ChainStatusFlags.Revoked) != 0)
221                                         result = 0x800B010C;
222                                 // TRUST_E_CERT_SIGNATURE
223                                 else if ((flags & X509ChainStatusFlags.NotSignatureValid) != 0)
224                                         result = 0x80096004;
225                                 // CERT_E_WRONG_USAGE
226                                 else if ((flags & X509ChainStatusFlags.NotValidForUsage) != 0)
227                                         result = 0x800B0110;
228                                 // CERT_E_UNTRUSTEDROOT
229                                 else if ((flags & X509ChainStatusFlags.UntrustedRoot) != 0)
230                                         result = 0x800B0109;
231                                 // CRYPT_E_NO_REVOCATION_CHECK
232                                 else if ((flags & X509ChainStatusFlags.RevocationStatusUnknown) != 0)
233                                         result = 0x80092012;
234                                 // CERT_E_CHAINING
235                                 else if ((flags & X509ChainStatusFlags.Cyclic) != 0)
236                                         result = 0x800B010A;
237                                 // TRUST_E_FAIL - generic
238                                 else if ((flags & X509ChainStatusFlags.InvalidExtension) != 0)
239                                         result = 0x800B010B;
240                                 // CERT_E_UNTRUSTEDROOT
241                                 else if ((flags & X509ChainStatusFlags.InvalidPolicyConstraints) != 0)
242                                         result = 0x800B010D;
243                                 // TRUST_E_BASIC_CONSTRAINTS
244                                 else if ((flags & X509ChainStatusFlags.InvalidBasicConstraints) != 0)
245                                         result = 0x80096019;
246                                 // CERT_E_INVALID_NAME
247                                 else if ((flags & X509ChainStatusFlags.InvalidNameConstraints) != 0)
248                                         result = 0x800B0114;
249                                 // CERT_E_INVALID_NAME
250                                 else if ((flags & X509ChainStatusFlags.HasNotSupportedNameConstraint) != 0)
251                                         result = 0x800B0114;
252                                 // CERT_E_INVALID_NAME
253                                 else if ((flags & X509ChainStatusFlags.HasNotDefinedNameConstraint) != 0)
254                                         result = 0x800B0114;
255                                 // CERT_E_INVALID_NAME
256                                 else if ((flags & X509ChainStatusFlags.HasNotPermittedNameConstraint) != 0)
257                                         result = 0x800B0114;
258                                 // CERT_E_INVALID_NAME
259                                 else if ((flags & X509ChainStatusFlags.HasExcludedNameConstraint) != 0)
260                                         result = 0x800B0114;
261                                 // CERT_E_CHAINING
262                                 else if ((flags & X509ChainStatusFlags.PartialChain) != 0)
263                                         result = 0x800B010A;
264                                 // CERT_E_EXPIRED
265                                 else if ((flags & X509ChainStatusFlags.CtlNotTimeValid) != 0)
266                                         result = 0x800B0101;
267                                 // TRUST_E_CERT_SIGNATURE
268                                 else if ((flags & X509ChainStatusFlags.CtlNotSignatureValid) != 0)
269                                         result = 0x80096004;
270                                 // CERT_E_WRONG_USAGE
271                                 else if ((flags & X509ChainStatusFlags.CtlNotValidForUsage) != 0)
272                                         result = 0x800B0110;
273                                 // CRYPT_E_NO_REVOCATION_CHECK
274                                 else if ((flags & X509ChainStatusFlags.OfflineRevocation) != 0)
275                                         result = 0x80092012;
276                                 // CERT_E_ISSUERCHAINING
277                                 else if ((flags & X509ChainStatusFlags.NoIssuanceChainPolicy) != 0)
278                                         result = 0x800B0107;
279                                 else
280                                         result = 0x800B010B; // TRUST_E_FAIL - generic
281
282                                 break; // Exit the loop on the first error
283                         }
284                         return (int)result;
285                 }
286
287                 static SslPolicyErrors GetErrorsFromChain (X509Chain chain)
288                 {
289                         SslPolicyErrors errors = SslPolicyErrors.None;
290                         foreach (var status in chain.ChainStatus) {
291                                 if (status.Status == X509ChainStatusFlags.NoError)
292                                         continue;
293                                 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
294                                 break;
295                         }
296                         return errors;
297                 }
298 #endif
299
300 #if !MONOTOUCH
301                 static X509KeyUsageFlags s_flags = X509KeyUsageFlags.DigitalSignature |
302                         X509KeyUsageFlags.KeyAgreement |
303                         X509KeyUsageFlags.KeyEncipherment;
304                 // Adapted to System 2.0+ from TlsServerCertificate.cs
305                 //------------------------------
306                 // Note: this method only works for RSA certificates
307                 // DH certificates requires some changes - does anyone use one ?
308                 static bool CheckCertificateUsage (X509Certificate2 cert)
309                 {
310                         try {
311                                 // certificate extensions are required for this
312                                 // we "must" accept older certificates without proofs
313                                 if (cert.Version < 3)
314                                         return true;
315
316                                 X509KeyUsageExtension kux = (cert.Extensions ["2.5.29.15"] as X509KeyUsageExtension);
317                                 X509EnhancedKeyUsageExtension eku = (cert.Extensions ["2.5.29.37"] as X509EnhancedKeyUsageExtension);
318                                 if (kux != null && eku != null) {
319                                         // RFC3280 states that when both KeyUsageExtension and 
320                                         // ExtendedKeyUsageExtension are present then BOTH should
321                                         // be valid
322                                         if ((kux.KeyUsages & s_flags) == 0)
323                                                 return false;
324                                         return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
325                                                 eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
326                                 } else if (kux != null) {
327                                         return ((kux.KeyUsages & s_flags) != 0);
328                                 } else if (eku != null) {
329                                         // Server Authentication (1.3.6.1.5.5.7.3.1) or
330                                         // Netscape Server Gated Crypto (2.16.840.1.113730.4)
331                                         return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
332                                                 eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
333                                 }
334
335                                 // last chance - try with older (deprecated) Netscape extensions
336                                 X509Extension ext = cert.Extensions ["2.16.840.1.113730.1.1"];
337                                 if (ext != null) {
338                                         string text = ext.NetscapeCertType (false);
339                                         return text.IndexOf ("SSL Server Authentication", StringComparison.Ordinal) != -1;
340                                 }
341                                 return true;
342                         } catch (Exception e) {
343                                 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
344                                 Console.Error.WriteLine ("Please, report this problem to the Mono team");
345                                 return false;
346                         }
347                 }
348
349                 // RFC2818 - HTTP Over TLS, Section 3.1
350                 // http://www.ietf.org/rfc/rfc2818.txt
351                 //
352                 // 1.   if present MUST use subjectAltName dNSName as identity
353                 // 1.1.         if multiples entries a match of any one is acceptable
354                 // 1.2.         wildcard * is acceptable
355                 // 2.   URI may be an IP address -> subjectAltName.iPAddress
356                 // 2.1.         exact match is required
357                 // 3.   Use of the most specific Common Name (CN=) in the Subject
358                 // 3.1          Existing practice but DEPRECATED
359                 static bool CheckServerIdentity (X509Certificate2 cert, string targetHost)
360                 {
361                         try {
362                                 var mcert = new MSX.X509Certificate (cert.RawData);
363                                 MSX.X509Extension ext = mcert.Extensions ["2.5.29.17"];
364                                 // 1. subjectAltName
365                                 if (ext != null) {
366                                         SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext);
367                                         // 1.1 - multiple dNSName
368                                         foreach (string dns in subjectAltName.DNSNames) {
369                                                 // 1.2 TODO - wildcard support
370                                                 if (Match (targetHost, dns))
371                                                         return true;
372                                         }
373                                         // 2. ipAddress
374                                         foreach (string ip in subjectAltName.IPAddresses) {
375                                                 // 2.1. Exact match required
376                                                 if (ip == targetHost)
377                                                         return true;
378                                         }
379                                 }
380                                 // 3. Common Name (CN=)
381                                 return CheckDomainName (mcert.SubjectName, targetHost);
382                         } catch (Exception e) {
383                                 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
384                                 Console.Error.WriteLine ("Please, report this problem to the Mono team");
385                                 return false;
386                         }
387                 }
388
389                 static bool CheckDomainName (string subjectName, string targetHost)
390                 {
391                         string  domainName = String.Empty;
392                         Regex search = new Regex (@"CN\s*=\s*([^,]*)");
393                         MatchCollection elements = search.Matches (subjectName);
394                         if (elements.Count == 1) {
395                                 if (elements [0].Success)
396                                         domainName = elements [0].Groups [1].Value.ToString ();
397                         }
398
399                         return Match (targetHost, domainName);
400                 }
401
402                 // ensure the pattern is valid wrt to RFC2595 and RFC2818
403                 // http://www.ietf.org/rfc/rfc2595.txt
404                 // http://www.ietf.org/rfc/rfc2818.txt
405                 static bool Match (string hostname, string pattern)
406                 {
407                         // check if this is a pattern
408                         int index = pattern.IndexOf ('*');
409                         if (index == -1) {
410                                 // not a pattern, do a direct case-insensitive comparison
411                                 return (String.Compare (hostname, pattern, true, CultureInfo.InvariantCulture) == 0);
412                         }
413
414                         // check pattern validity
415                         // A "*" wildcard character MAY be used as the left-most name component in the certificate.
416
417                         // unless this is the last char (valid)
418                         if (index != pattern.Length - 1) {
419                                 // then the next char must be a dot .'.
420                                 if (pattern [index + 1] != '.')
421                                         return false;
422                         }
423
424                         // only one (A) wildcard is supported
425                         int i2 = pattern.IndexOf ('*', index + 1);
426                         if (i2 != -1)
427                                 return false;
428
429                         // match the end of the pattern
430                         string end = pattern.Substring (index + 1);
431                         int length = hostname.Length - end.Length;
432                         // no point to check a pattern that is longer than the hostname
433                         if (length <= 0)
434                                 return false;
435
436                         if (String.Compare (hostname, length, end, 0, end.Length, true, CultureInfo.InvariantCulture) != 0)
437                                 return false;
438
439                         // special case, we start with the wildcard
440                         if (index == 0) {
441                                 // ensure we hostname non-matched part (start) doesn't contain a dot
442                                 int i3 = hostname.IndexOf ('.');
443                                 return ((i3 == -1) || (i3 >= (hostname.Length - end.Length)));
444                         }
445
446                         // match the start of the pattern
447                         string start = pattern.Substring (0, index);
448                         return (String.Compare (hostname, 0, start, 0, start.Length, true, CultureInfo.InvariantCulture) == 0);
449                 }
450 #endif
451         }
452 }
453 #endif
454