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