Fix to checked-build reference auditing
[mono.git] / mcs / class / System / Mono.Net.Security / ChainValidationHelper.cs
1 //
2 // System.Net.ServicePointManager
3 //
4 // Authors:
5 //   Lawrence Pit (loz@cable.a2000.nl)
6 //   Gonzalo Paniagua Javier (gonzalo@novell.com)
7 //
8 // Copyright (c) 2003-2010 Novell, Inc (http://www.novell.com)
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 #if SECURITY_DEP
33
34 #if MONO_SECURITY_ALIAS
35 extern alias MonoSecurity;
36 #endif
37 #if MONO_X509_ALIAS
38 extern alias PrebuiltSystem;
39 #endif
40
41 #if MONO_SECURITY_ALIAS
42 using MonoSecurity::Mono.Security.Interface;
43 using MSX = MonoSecurity::Mono.Security.X509;
44 using MonoSecurity::Mono.Security.X509.Extensions;
45 #else
46 using Mono.Security.Interface;
47 using MSX = Mono.Security.X509;
48 using Mono.Security.X509.Extensions;
49 #endif
50 #if MONO_X509_ALIAS
51 using XX509CertificateCollection = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509CertificateCollection;
52 #else
53 using XX509CertificateCollection = System.Security.Cryptography.X509Certificates.X509CertificateCollection;
54 #endif
55
56 using System;
57 using System.Net;
58 using System.Threading;
59 using System.Collections;
60 using System.Collections.Generic;
61 using System.Collections.Specialized;
62 using System.Configuration;
63 using System.Net.Configuration;
64 using System.Text.RegularExpressions;
65 using System.Security.Cryptography.X509Certificates;
66
67 using System.Globalization;
68 using System.Net.Security;
69 using System.Diagnostics;
70
71 namespace Mono.Net.Security
72 {
73         internal delegate bool ServerCertValidationCallbackWrapper (ServerCertValidationCallback callback, X509Certificate certificate, X509Chain chain, MonoSslPolicyErrors sslPolicyErrors);
74
75         internal class ChainValidationHelper : ICertificateValidator
76         {
77                 readonly object sender;
78                 readonly MonoTlsSettings settings;
79                 readonly ServerCertValidationCallback certValidationCallback;
80                 readonly LocalCertSelectionCallback certSelectionCallback;
81                 readonly ServerCertValidationCallbackWrapper callbackWrapper;
82                 readonly MonoTlsStream tlsStream;
83                 readonly HttpWebRequest request;
84
85                 static bool is_macosx;
86                 static bool is_mobile;
87 #if !MONOTOUCH
88                 static X509RevocationMode revocation_mode;
89 #endif
90
91                 static ChainValidationHelper ()
92                 {
93 #if MONOTOUCH
94                         is_macosx = true;
95                         is_mobile = true;
96 #elif MONODROID
97                         is_macosx = false;
98                         is_mobile = true;
99 #else
100                         is_macosx = System.IO.File.Exists (OSX509Certificates.SecurityLibrary);
101                         is_mobile = false;
102 #endif
103
104 #if !MONOTOUCH
105                         revocation_mode = X509RevocationMode.NoCheck;
106                         try {
107                                 string str = Environment.GetEnvironmentVariable ("MONO_X509_REVOCATION_MODE");
108                                 if (String.IsNullOrEmpty (str))
109                                         return;
110                                 revocation_mode = (X509RevocationMode)Enum.Parse (typeof(X509RevocationMode), str, true);
111                         } catch {
112                         }
113 #endif
114                 }
115
116                 internal static ICertificateValidator GetDefaultValidator (MonoTlsSettings settings)
117                 {
118                         if (settings.CertificateValidator == null)
119                                 settings.CertificateValidator = new ChainValidationHelper (settings, false, null, null);
120                         return settings.CertificateValidator;
121                 }
122
123 #region SslStream support
124
125                 /*
126                  * This is a hack which is used in SslStream - see ReferenceSources/SslStream.cs for details.
127                  */
128                 internal static ChainValidationHelper CloneWithCallbackWrapper (ref MonoTlsSettings settings, ServerCertValidationCallbackWrapper wrapper)
129                 {
130                         var helper = (ChainValidationHelper)settings.CertificateValidator;
131                         if (helper == null)
132                                 helper = new ChainValidationHelper (settings, true, null, wrapper);
133                         else
134                                 helper = new ChainValidationHelper (helper, settings, wrapper);
135                         settings = helper.settings;
136                         return helper;
137                 }
138
139                 internal static bool InvokeCallback (ServerCertValidationCallback callback, object sender, X509Certificate certificate, X509Chain chain, MonoSslPolicyErrors sslPolicyErrors)
140                 {
141                         return callback.Invoke (sender, certificate, chain, (SslPolicyErrors)sslPolicyErrors);
142                 }
143
144 #endregion
145
146                 ChainValidationHelper (ChainValidationHelper other, MonoTlsSettings settings, ServerCertValidationCallbackWrapper callbackWrapper = null)
147                 {
148                         sender = other.sender;
149                         certValidationCallback = other.certValidationCallback;
150                         certSelectionCallback = other.certSelectionCallback;
151                         tlsStream = other.tlsStream;
152                         request = other.request;
153
154                         this.settings = settings = settings.CloneWithValidator (this);
155                         this.callbackWrapper = callbackWrapper;
156                 }
157
158                 internal static ChainValidationHelper Create (ref MonoTlsSettings settings, MonoTlsStream stream)
159                 {
160                         var helper = new ChainValidationHelper (settings, true, stream, null);
161                         settings = helper.settings;
162                         return helper;
163                 }
164
165                 ChainValidationHelper (MonoTlsSettings settings, bool cloneSettings, MonoTlsStream stream, ServerCertValidationCallbackWrapper callbackWrapper)
166                 {
167                         if (cloneSettings)
168                                 settings = settings.CloneWithValidator (this);
169
170                         this.settings = settings;
171                         this.tlsStream = stream;
172                         this.callbackWrapper = callbackWrapper;
173
174                         var fallbackToSPM = false;
175
176                         if (settings != null) {
177                                 if (settings.ServerCertificateValidationCallback != null) {
178                                         var callback = Private.CallbackHelpers.MonoToPublic (settings.ServerCertificateValidationCallback);
179                                         certValidationCallback = new ServerCertValidationCallback (callback);
180                                 }
181                                 certSelectionCallback = Private.CallbackHelpers.MonoToInternal (settings.ClientCertificateSelectionCallback);
182                                 fallbackToSPM = settings.UseServicePointManagerCallback;
183                         }
184
185                         if (stream != null) {
186                                 this.request = stream.Request;
187                                 this.sender = request;
188
189                                 if (certValidationCallback == null)
190                                         certValidationCallback = request.ServerCertValidationCallback;
191                                 if (certSelectionCallback == null)
192                                         certSelectionCallback = new LocalCertSelectionCallback (DefaultSelectionCallback);
193
194                                 if (settings == null)
195                                         fallbackToSPM = true;
196                         }
197
198                         if (fallbackToSPM && certValidationCallback == null)
199                                 certValidationCallback = ServicePointManager.ServerCertValidationCallback;
200                 }
201
202                 static X509Certificate DefaultSelectionCallback (string targetHost, XX509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
203                 {
204                         X509Certificate clientCertificate;
205                         if (localCertificates == null || localCertificates.Count == 0)
206                                 clientCertificate = null;
207                         else
208                                 clientCertificate = localCertificates [0];
209                         return clientCertificate;
210                 }
211
212                 public MonoTlsSettings Settings {
213                         get { return settings; }
214                 }
215
216                 public bool HasCertificateSelectionCallback {
217                         get { return certSelectionCallback != null; }
218                 }
219
220                 public X509Certificate SelectClientCertificate (
221                         string targetHost, XX509CertificateCollection localCertificates, X509Certificate remoteCertificate,
222                         string[] acceptableIssuers)
223                 {
224                         if (certSelectionCallback == null)
225                                 return null;
226                         return certSelectionCallback (targetHost, localCertificates, remoteCertificate, acceptableIssuers);
227                 }
228
229                 internal bool ValidateClientCertificate (X509Certificate certificate, MonoSslPolicyErrors errors)
230                 {
231                         var certs2 = new X509Certificate2Collection ();
232                         certs2.Add (new X509Certificate2 (certificate.GetRawCertData ()));
233
234                         var result = ValidateChain (null, certs2, (SslPolicyErrors)errors);
235                         if (result == null)
236                                 return false;
237
238                         return result.Trusted && !result.UserDenied;
239                 }
240
241                 static X509Certificate2Collection Convert (MSX.X509CertificateCollection certificates)
242                 {
243                         if (certificates == null)
244                                 return null;
245
246                         var certs2 = new X509Certificate2Collection ();
247                         for (int i = 0; i < certificates.Count; i++)
248                                 certs2.Add (new X509Certificate2 (certificates [i].RawData));
249                         return certs2;
250                 }
251
252                 static X509Certificate2Collection Convert (XX509CertificateCollection certificates)
253                 {
254                         var certs2 = (object)certificates as X509Certificate2Collection;
255                         if (certs2 != null || certificates == null)
256                                 return certs2;
257
258                         certs2 = new X509Certificate2Collection ();
259                         for (int i = 0; i < certificates.Count; i++)
260                                 certs2.Add ((X509Certificate2)certificates [i]);
261                         return certs2;
262                 }
263
264                 public ValidationResult ValidateClientCertificate (XX509CertificateCollection certs)
265                 {
266                         var certs2 = Convert (certs);
267                         return ValidateChain (null, certs2, 0);
268                 }
269
270                 public ValidationResult ValidateChain (string host, XX509CertificateCollection certs)
271                 {
272                         try {
273                                 var certs2 = Convert (certs);
274                                 var result = ValidateChain (host, certs2, 0);
275                                 if (tlsStream != null)
276                                         tlsStream.CertificateValidationFailed = result == null || !result.Trusted || result.UserDenied;
277                                 return result;
278                         } catch {
279                                 if (tlsStream != null)
280                                         tlsStream.CertificateValidationFailed = true;
281                                 throw;
282                         }
283                 }
284
285                 internal ValidationResult ValidateChain (string host, MSX.X509CertificateCollection certs)
286                 {
287                         try {
288                                 var certs2 = Convert (certs);
289                                 var result = ValidateChain (host, certs2, 0);
290                                 if (tlsStream != null)
291                                         tlsStream.CertificateValidationFailed = result == null || !result.Trusted || result.UserDenied;
292                                 return result;
293                         } catch {
294                                 if (tlsStream != null)
295                                         tlsStream.CertificateValidationFailed = true;
296                                 throw;
297                         }
298                 }
299
300                 ValidationResult ValidateChain (string host, X509Certificate2Collection certs, SslPolicyErrors errors)
301                 {
302                         // user_denied is true if the user callback is called and returns false
303                         bool user_denied = false;
304                         bool result = false;
305
306                         var hasCallback = certValidationCallback != null || callbackWrapper != null;
307
308                         X509Certificate2 leaf;
309                         if (certs == null || certs.Count == 0)
310                                 leaf = null;
311                         else
312                                 leaf = certs [0];
313
314                         if (tlsStream != null)
315                                 request.ServicePoint.SetServerCertificate (leaf);
316
317                         if (leaf == null) {
318                                 errors |= SslPolicyErrors.RemoteCertificateNotAvailable;
319                                 if (hasCallback) {
320                                         if (callbackWrapper != null)
321                                                 result = callbackWrapper.Invoke (certValidationCallback, leaf, null, (MonoSslPolicyErrors)errors);
322                                         else
323                                                 result = certValidationCallback.Invoke (sender, leaf, null, errors);
324                                         user_denied = !result;
325                                 }
326                                 return new ValidationResult (result, user_denied, 0, (MonoSslPolicyErrors)errors);
327                         }
328
329                         bool needsChain;
330                         bool skipSystemValidators = false;
331                         if (!CertificateValidationHelper.SupportsX509Chain || is_mobile || is_macosx) {
332                                 needsChain = false;
333                         } else if (settings != null) {
334                                 skipSystemValidators = settings.SkipSystemValidators;
335                                 needsChain = !settings.SkipSystemValidators || settings.CallbackNeedsCertificateChain;
336                         } else {
337                                 needsChain = true;
338                         }
339
340                         ICertificatePolicy policy = ServicePointManager.GetLegacyCertificatePolicy ();
341
342                         int status11 = 0; // Error code passed to the obsolete ICertificatePolicy callback
343                         X509Chain chain = null;
344
345                         if (needsChain) {
346                                 chain = new X509Chain ();
347                                 chain.ChainPolicy = new X509ChainPolicy ();
348
349
350 #if !MONOTOUCH
351                                 chain.ChainPolicy.RevocationMode = revocation_mode;
352 #endif
353                                 for (int i = 1; i < certs.Count; i++) {
354                                         chain.ChainPolicy.ExtraStore.Add (certs [i]);
355                                 }
356                         }
357
358 #if !MONOTOUCH
359                         if (needsChain) {
360                                 try {
361                                         if (!chain.Build (leaf))
362                                                 errors |= GetErrorsFromChain (chain);
363                                 } catch (Exception e) {
364                                         Console.Error.WriteLine ("ERROR building certificate chain: {0}", e);
365                                         Console.Error.WriteLine ("Please, report this problem to the Mono team");
366                                         errors |= SslPolicyErrors.RemoteCertificateChainErrors;
367                                 }
368                         }
369
370                         // for OSX and iOS we're using the native API to check for the SSL server policy and host names
371                         if (!is_macosx) {
372                                 if (!CheckCertificateUsage (leaf)) {
373                                         errors |= SslPolicyErrors.RemoteCertificateChainErrors;
374                                         status11 = -2146762490; //CERT_E_PURPOSE 0x800B0106
375                                 }
376
377                                 if (host != null && !CheckServerIdentity (leaf, host)) {
378                                         errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
379                                         status11 = -2146762481; // CERT_E_CN_NO_MATCH 0x800B010F
380                                 }
381                         }
382 #endif
383
384                         if (is_macosx && !skipSystemValidators) {
385                                 // Attempt to use OSX certificates
386                                 // Ideally we should return the SecTrustResult
387                                 OSX509Certificates.SecTrustResult trustResult = OSX509Certificates.SecTrustResult.Deny;
388                                 try {
389                                         trustResult = OSX509Certificates.TrustEvaluateSsl (certs, host);
390                                         // We could use the other values of trustResult to pass this extra information
391                                         // to the .NET 2 callback for values like SecTrustResult.Confirm
392                                         result = (trustResult == OSX509Certificates.SecTrustResult.Proceed ||
393                                         trustResult == OSX509Certificates.SecTrustResult.Unspecified);
394                                 } catch {
395                                         // Ignore
396                                 }
397                                         
398                                 if (result) {
399                                         // TrustEvaluateSsl was successful so there's no trust error
400                                         // IOW we discard our own chain (since we trust OSX one instead)
401                                         errors = 0;
402                                 } else {
403                                         // callback and DefaultCertificatePolicy needs this since 'result' is not specified
404                                         status11 = (int)trustResult;
405                                         errors |= SslPolicyErrors.RemoteCertificateChainErrors;
406                                 }
407                         }
408
409
410 #if MONODROID && SECURITY_DEP
411                         if (!skipSystemValidators) {
412                                 result = AndroidPlatform.TrustEvaluateSsl (certs, sender, leaf, chain, errors);
413                                 if (result) {
414                                         // chain.Build() + GetErrorsFromChain() (above) will ALWAYS fail on
415                                         // Android (there are no mozroots or preinstalled root certificates),
416                                         // thus `errors` will ALWAYS have RemoteCertificateChainErrors.
417                                         // Android just verified the chain; clear RemoteCertificateChainErrors.
418                                         errors  &= ~SslPolicyErrors.RemoteCertificateChainErrors;
419                                 }
420                         }
421 #endif
422         
423                         if (policy != null && (!(policy is DefaultCertificatePolicy) || certValidationCallback == null)) {
424                                 ServicePoint sp = null;
425                                 if (request != null)
426                                         sp = request.ServicePointNoLock;
427                                 if (status11 == 0 && errors != 0)
428                                         status11 = GetStatusFromChain (chain);
429
430                                 // pre 2.0 callback
431                                 result = policy.CheckValidationResult (sp, leaf, request, status11);
432                                 user_denied = !result && !(policy is DefaultCertificatePolicy);
433                         }
434                         // If there's a 2.0 callback, it takes precedence
435                         if (hasCallback) {
436                                 if (callbackWrapper != null)
437                                         result = callbackWrapper.Invoke (certValidationCallback, leaf, chain, (MonoSslPolicyErrors)errors);
438                                 else
439                                         result = certValidationCallback.Invoke (sender, leaf, chain, errors);
440                                 user_denied = !result;
441                         }
442                         return new ValidationResult (result, user_denied, status11, (MonoSslPolicyErrors)errors);
443                 }
444
445                 static int GetStatusFromChain (X509Chain chain)
446                 {
447                         long result = 0;
448                         foreach (var status in chain.ChainStatus) {
449                                 X509ChainStatusFlags flags = status.Status;
450                                 if (flags == X509ChainStatusFlags.NoError)
451                                         continue;
452
453                                 // CERT_E_EXPIRED
454                                 if ((flags & X509ChainStatusFlags.NotTimeValid) != 0)
455                                         result = 0x800B0101;
456                                         // CERT_E_VALIDITYPERIODNESTING
457                                         else if ((flags & X509ChainStatusFlags.NotTimeNested) != 0)
458                                         result = 0x800B0102;
459                                         // CERT_E_REVOKED
460                                         else if ((flags & X509ChainStatusFlags.Revoked) != 0)
461                                         result = 0x800B010C;
462                                         // TRUST_E_CERT_SIGNATURE
463                                         else if ((flags & X509ChainStatusFlags.NotSignatureValid) != 0)
464                                         result = 0x80096004;
465                                         // CERT_E_WRONG_USAGE
466                                         else if ((flags & X509ChainStatusFlags.NotValidForUsage) != 0)
467                                         result = 0x800B0110;
468                                         // CERT_E_UNTRUSTEDROOT
469                                         else if ((flags & X509ChainStatusFlags.UntrustedRoot) != 0)
470                                         result = 0x800B0109;
471                                         // CRYPT_E_NO_REVOCATION_CHECK
472                                         else if ((flags & X509ChainStatusFlags.RevocationStatusUnknown) != 0)
473                                         result = 0x80092012;
474                                         // CERT_E_CHAINING
475                                         else if ((flags & X509ChainStatusFlags.Cyclic) != 0)
476                                         result = 0x800B010A;
477                                         // TRUST_E_FAIL - generic
478                                         else if ((flags & X509ChainStatusFlags.InvalidExtension) != 0)
479                                         result = 0x800B010B;
480                                         // CERT_E_UNTRUSTEDROOT
481                                         else if ((flags & X509ChainStatusFlags.InvalidPolicyConstraints) != 0)
482                                         result = 0x800B010D;
483                                         // TRUST_E_BASIC_CONSTRAINTS
484                                         else if ((flags & X509ChainStatusFlags.InvalidBasicConstraints) != 0)
485                                         result = 0x80096019;
486                                         // CERT_E_INVALID_NAME
487                                         else if ((flags & X509ChainStatusFlags.InvalidNameConstraints) != 0)
488                                         result = 0x800B0114;
489                                         // CERT_E_INVALID_NAME
490                                         else if ((flags & X509ChainStatusFlags.HasNotSupportedNameConstraint) != 0)
491                                         result = 0x800B0114;
492                                         // CERT_E_INVALID_NAME
493                                         else if ((flags & X509ChainStatusFlags.HasNotDefinedNameConstraint) != 0)
494                                         result = 0x800B0114;
495                                         // CERT_E_INVALID_NAME
496                                         else if ((flags & X509ChainStatusFlags.HasNotPermittedNameConstraint) != 0)
497                                         result = 0x800B0114;
498                                         // CERT_E_INVALID_NAME
499                                         else if ((flags & X509ChainStatusFlags.HasExcludedNameConstraint) != 0)
500                                         result = 0x800B0114;
501                                         // CERT_E_CHAINING
502                                         else if ((flags & X509ChainStatusFlags.PartialChain) != 0)
503                                         result = 0x800B010A;
504                                         // CERT_E_EXPIRED
505                                         else if ((flags & X509ChainStatusFlags.CtlNotTimeValid) != 0)
506                                         result = 0x800B0101;
507                                         // TRUST_E_CERT_SIGNATURE
508                                         else if ((flags & X509ChainStatusFlags.CtlNotSignatureValid) != 0)
509                                         result = 0x80096004;
510                                         // CERT_E_WRONG_USAGE
511                                         else if ((flags & X509ChainStatusFlags.CtlNotValidForUsage) != 0)
512                                         result = 0x800B0110;
513                                         // CRYPT_E_NO_REVOCATION_CHECK
514                                         else if ((flags & X509ChainStatusFlags.OfflineRevocation) != 0)
515                                         result = 0x80092012;
516                                         // CERT_E_ISSUERCHAINING
517                                         else if ((flags & X509ChainStatusFlags.NoIssuanceChainPolicy) != 0)
518                                         result = 0x800B0107;
519                                 else
520                                         result = 0x800B010B; // TRUST_E_FAIL - generic
521
522                                 break; // Exit the loop on the first error
523                         }
524                         return (int)result;
525                 }
526
527
528 #if !MONOTOUCH
529                 static SslPolicyErrors GetErrorsFromChain (X509Chain chain)
530                 {
531                         SslPolicyErrors errors = SslPolicyErrors.None;
532                         foreach (var status in chain.ChainStatus) {
533                                 if (status.Status == X509ChainStatusFlags.NoError)
534                                         continue;
535                                 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
536                                 break;
537                         }
538                         return errors;
539                 }
540
541                 static X509KeyUsageFlags s_flags = X509KeyUsageFlags.DigitalSignature |
542                                                     X509KeyUsageFlags.KeyAgreement |
543                                                     X509KeyUsageFlags.KeyEncipherment;
544                 // Adapted to System 2.0+ from TlsServerCertificate.cs
545                 //------------------------------
546                 // Note: this method only works for RSA certificates
547                 // DH certificates requires some changes - does anyone use one ?
548                 static bool CheckCertificateUsage (X509Certificate2 cert)
549                 {
550                         try {
551                                 // certificate extensions are required for this
552                                 // we "must" accept older certificates without proofs
553                                 if (cert.Version < 3)
554                                         return true;
555
556                                 X509KeyUsageExtension kux = (cert.Extensions ["2.5.29.15"] as X509KeyUsageExtension);
557                                 X509EnhancedKeyUsageExtension eku = (cert.Extensions ["2.5.29.37"] as X509EnhancedKeyUsageExtension);
558                                 if (kux != null && eku != null) {
559                                         // RFC3280 states that when both KeyUsageExtension and 
560                                         // ExtendedKeyUsageExtension are present then BOTH should
561                                         // be valid
562                                         if ((kux.KeyUsages & s_flags) == 0)
563                                                 return false;
564                                         return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
565                                         eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
566                                 } else if (kux != null) {
567                                         return ((kux.KeyUsages & s_flags) != 0);
568                                 } else if (eku != null) {
569                                         // Server Authentication (1.3.6.1.5.5.7.3.1) or
570                                         // Netscape Server Gated Crypto (2.16.840.1.113730.4)
571                                         return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
572                                         eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
573                                 }
574
575                                 // last chance - try with older (deprecated) Netscape extensions
576                                 X509Extension ext = cert.Extensions ["2.16.840.1.113730.1.1"];
577                                 if (ext != null) {
578                                         string text = ext.NetscapeCertType (false);
579                                         return text.IndexOf ("SSL Server Authentication", StringComparison.Ordinal) != -1;
580                                 }
581                                 return true;
582                         } catch (Exception e) {
583                                 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
584                                 Console.Error.WriteLine ("Please, report this problem to the Mono team");
585                                 return false;
586                         }
587                 }
588
589                 // RFC2818 - HTTP Over TLS, Section 3.1
590                 // http://www.ietf.org/rfc/rfc2818.txt
591                 //
592                 // 1.   if present MUST use subjectAltName dNSName as identity
593                 // 1.1.         if multiples entries a match of any one is acceptable
594                 // 1.2.         wildcard * is acceptable
595                 // 2.   URI may be an IP address -> subjectAltName.iPAddress
596                 // 2.1.         exact match is required
597                 // 3.   Use of the most specific Common Name (CN=) in the Subject
598                 // 3.1          Existing practice but DEPRECATED
599                 static bool CheckServerIdentity (X509Certificate2 cert, string targetHost)
600                 {
601                         try {
602                                 var mcert = new MSX.X509Certificate (cert.RawData);
603                                 MSX.X509Extension ext = mcert.Extensions ["2.5.29.17"];
604                                 // 1. subjectAltName
605                                 if (ext != null) {
606                                         SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext);
607                                         // 1.1 - multiple dNSName
608                                         foreach (string dns in subjectAltName.DNSNames) {
609                                                 // 1.2 TODO - wildcard support
610                                                 if (Match (targetHost, dns))
611                                                         return true;
612                                         }
613                                         // 2. ipAddress
614                                         foreach (string ip in subjectAltName.IPAddresses) {
615                                                 // 2.1. Exact match required
616                                                 if (ip == targetHost)
617                                                         return true;
618                                         }
619                                 }
620                                 // 3. Common Name (CN=)
621                                 return CheckDomainName (mcert.SubjectName, targetHost);
622                         } catch (Exception e) {
623                                 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
624                                 Console.Error.WriteLine ("Please, report this problem to the Mono team");
625                                 return false;
626                         }
627                 }
628
629                 static bool CheckDomainName (string subjectName, string targetHost)
630                 {
631                         string  domainName = String.Empty;
632                         Regex search = new Regex (@"CN\s*=\s*([^,]*)");
633                         MatchCollection elements = search.Matches (subjectName);
634                         if (elements.Count == 1) {
635                                 if (elements [0].Success)
636                                         domainName = elements [0].Groups [1].Value.ToString ();
637                         }
638
639                         return Match (targetHost, domainName);
640                 }
641
642                 // ensure the pattern is valid wrt to RFC2595 and RFC2818
643                 // http://www.ietf.org/rfc/rfc2595.txt
644                 // http://www.ietf.org/rfc/rfc2818.txt
645                 static bool Match (string hostname, string pattern)
646                 {
647                         // check if this is a pattern
648                         int index = pattern.IndexOf ('*');
649                         if (index == -1) {
650                                 // not a pattern, do a direct case-insensitive comparison
651                                 return (String.Compare (hostname, pattern, true, CultureInfo.InvariantCulture) == 0);
652                         }
653
654                         // check pattern validity
655                         // A "*" wildcard character MAY be used as the left-most name component in the certificate.
656
657                         // unless this is the last char (valid)
658                         if (index != pattern.Length - 1) {
659                                 // then the next char must be a dot .'.
660                                 if (pattern [index + 1] != '.')
661                                         return false;
662                         }
663
664                         // only one (A) wildcard is supported
665                         int i2 = pattern.IndexOf ('*', index + 1);
666                         if (i2 != -1)
667                                 return false;
668
669                         // match the end of the pattern
670                         string end = pattern.Substring (index + 1);
671                         int length = hostname.Length - end.Length;
672                         // no point to check a pattern that is longer than the hostname
673                         if (length <= 0)
674                                 return false;
675
676                         if (String.Compare (hostname, length, end, 0, end.Length, true, CultureInfo.InvariantCulture) != 0)
677                                 return false;
678
679                         // special case, we start with the wildcard
680                         if (index == 0) {
681                                 // ensure we hostname non-matched part (start) doesn't contain a dot
682                                 int i3 = hostname.IndexOf ('.');
683                                 return ((i3 == -1) || (i3 >= (hostname.Length - end.Length)));
684                         }
685
686                         // match the start of the pattern
687                         string start = pattern.Substring (0, index);
688                         return (String.Compare (hostname, 0, start, 0, start.Length, true, CultureInfo.InvariantCulture) == 0);
689                 }
690 #endif
691         }
692 }
693 #endif
694