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