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