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