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