[build] Fixes Mono.Security dependency
[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                 public static bool BuildX509Chain (XX509CertificateCollection certs, X509Chain chain, ref SslPolicyErrors errors, ref int status11)
90                 {
91 #if MOBILE
92                         return true;
93 #else
94                         if (is_macosx)
95                                 return true;
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 (host != null && !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                         var result = false;
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                                         // Ignore
170                                 }
171
172                                 if (result) {
173                                         // TrustEvaluateSsl was successful so there's no trust error
174                                         // IOW we discard our own chain (since we trust OSX one instead)
175                                         errors = 0;
176                                 } else {
177                                         // callback and DefaultCertificatePolicy needs this since 'result' is not specified
178                                         status11 = (int)trustResult;
179                                         errors |= SslPolicyErrors.RemoteCertificateChainErrors;
180                                 }
181                         }
182 #endif
183
184                         return result;
185                 }
186
187                 public static bool Evaluate (
188                         MonoTlsSettings settings, string host, XX509CertificateCollection certs,
189                         X509Chain chain, ref SslPolicyErrors errors, ref int status11)
190                 {
191                         if (!CheckUsage (certs, host, ref errors, ref status11))
192                                 return false;
193
194                         if (settings != null && settings.SkipSystemValidators)
195                                 return false;
196
197                         var anchors = settings != null ? settings.TrustAnchors : null;
198                         return EvaluateSystem (certs, anchors, host, chain, ref errors, ref status11);
199                 }
200
201                 internal static bool NeedsChain (MonoTlsSettings settings)
202                 {
203 #if MOBILE
204                         return false;
205 #else
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