Merge pull request #2236 from akoeplinger/add-dataflow
[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 using XX509Chain = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509Chain;
53 #else
54 using XX509CertificateCollection = System.Security.Cryptography.X509Certificates.X509CertificateCollection;
55 using XX509Chain = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509Chain;
56 #endif
57
58 using System;
59 using System.Net;
60 using System.Threading;
61 using System.Collections;
62 using System.Collections.Generic;
63 using System.Collections.Specialized;
64 using System.Configuration;
65 using System.Net.Configuration;
66 using System.Text.RegularExpressions;
67 using System.Security.Cryptography.X509Certificates;
68
69 using System.Globalization;
70 using System.Net.Security;
71 using System.Diagnostics;
72
73 namespace Mono.Net.Security
74 {
75         internal delegate bool ServerCertValidationCallbackWrapper (ServerCertValidationCallback callback, X509Certificate certificate, X509Chain chain, MonoSslPolicyErrors sslPolicyErrors);
76
77         internal class ChainValidationHelper : ICertificateValidator
78         {
79                 readonly object sender;
80                 readonly MonoTlsSettings settings;
81                 readonly MonoTlsProvider provider;
82                 readonly ServerCertValidationCallback certValidationCallback;
83                 readonly LocalCertSelectionCallback certSelectionCallback;
84                 readonly ServerCertValidationCallbackWrapper callbackWrapper;
85                 readonly MonoTlsStream tlsStream;
86                 readonly HttpWebRequest request;
87
88                 static bool is_macosx;
89                 static bool is_mobile;
90 #if !MOBILE
91                 static X509RevocationMode revocation_mode;
92 #endif
93
94                 static ChainValidationHelper ()
95                 {
96 #if MONOTOUCH
97                         is_macosx = true;
98                         is_mobile = true;
99 #elif MONODROID
100                         is_macosx = false;
101                         is_mobile = true;
102 #else
103                         is_macosx = System.IO.File.Exists (OSX509Certificates.SecurityLibrary);
104                         is_mobile = false;
105 #endif
106
107 #if !MOBILE
108                         revocation_mode = X509RevocationMode.NoCheck;
109                         try {
110                                 string str = Environment.GetEnvironmentVariable ("MONO_X509_REVOCATION_MODE");
111                                 if (String.IsNullOrEmpty (str))
112                                         return;
113                                 revocation_mode = (X509RevocationMode)Enum.Parse (typeof(X509RevocationMode), str, true);
114                         } catch {
115                         }
116 #endif
117                 }
118
119                 internal static ICertificateValidator GetDefaultValidator (MonoTlsProvider provider, MonoTlsSettings settings)
120                 {
121                         if (settings == null)
122                                 return new ChainValidationHelper (provider, null, false, null, null);
123                         if (settings.CertificateValidator != null)
124                                 return settings.CertificateValidator;
125                         return new ChainValidationHelper (provider, settings, false, null, null);
126                 }
127
128 #region SslStream support
129
130                 /*
131                  * This is a hack which is used in SslStream - see ReferenceSources/SslStream.cs for details.
132                  */
133                 internal static ChainValidationHelper CloneWithCallbackWrapper (MonoTlsProvider provider, ref MonoTlsSettings settings, ServerCertValidationCallbackWrapper wrapper)
134                 {
135                         var helper = (ChainValidationHelper)settings.CertificateValidator;
136                         if (helper == null)
137                                 helper = new ChainValidationHelper (provider, settings, true, null, wrapper);
138                         else
139                                 helper = new ChainValidationHelper (helper, provider, settings, wrapper);
140                         settings = helper.settings;
141                         return helper;
142                 }
143
144                 internal static bool InvokeCallback (ServerCertValidationCallback callback, object sender, X509Certificate certificate, X509Chain chain, MonoSslPolicyErrors sslPolicyErrors)
145                 {
146                         return callback.Invoke (sender, certificate, chain, (SslPolicyErrors)sslPolicyErrors);
147                 }
148
149 #endregion
150
151                 ChainValidationHelper (ChainValidationHelper other, MonoTlsProvider provider, MonoTlsSettings settings, ServerCertValidationCallbackWrapper callbackWrapper = null)
152                 {
153                         sender = other.sender;
154                         certValidationCallback = other.certValidationCallback;
155                         certSelectionCallback = other.certSelectionCallback;
156                         tlsStream = other.tlsStream;
157                         request = other.request;
158
159                         this.provider = provider;
160                         this.settings = settings = settings.CloneWithValidator (this);
161                         this.callbackWrapper = callbackWrapper;
162                 }
163
164                 internal static ChainValidationHelper Create (MonoTlsProvider provider, ref MonoTlsSettings settings, MonoTlsStream stream)
165                 {
166                         var helper = new ChainValidationHelper (provider, settings, true, stream, null);
167                         settings = helper.settings;
168                         return helper;
169                 }
170
171                 ChainValidationHelper (MonoTlsProvider provider, MonoTlsSettings settings, bool cloneSettings, MonoTlsStream stream, ServerCertValidationCallbackWrapper callbackWrapper)
172                 {
173                         if (cloneSettings)
174                                 settings = settings.CloneWithValidator (this);
175
176                         this.provider = provider;
177                         this.settings = settings;
178                         this.tlsStream = stream;
179                         this.callbackWrapper = callbackWrapper;
180
181                         var fallbackToSPM = false;
182
183                         if (settings != null) {
184                                 if (settings.RemoteCertificateValidationCallback != null) {
185                                         var callback = Private.CallbackHelpers.MonoToPublic (settings.RemoteCertificateValidationCallback);
186                                         certValidationCallback = new ServerCertValidationCallback (callback);
187                                 }
188                                 certSelectionCallback = Private.CallbackHelpers.MonoToInternal (settings.ClientCertificateSelectionCallback);
189                                 fallbackToSPM = settings.UseServicePointManagerCallback;
190                         }
191
192                         if (stream != null) {
193                                 this.request = stream.Request;
194                                 this.sender = request;
195
196                                 if (certValidationCallback == null)
197                                         certValidationCallback = request.ServerCertValidationCallback;
198                                 if (certSelectionCallback == null)
199                                         certSelectionCallback = new LocalCertSelectionCallback (DefaultSelectionCallback);
200
201                                 if (settings == null)
202                                         fallbackToSPM = true;
203                         }
204
205                         if (fallbackToSPM && certValidationCallback == null)
206                                 certValidationCallback = ServicePointManager.ServerCertValidationCallback;
207                 }
208
209                 static X509Certificate DefaultSelectionCallback (string targetHost, XX509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
210                 {
211                         X509Certificate clientCertificate;
212                         if (localCertificates == null || localCertificates.Count == 0)
213                                 clientCertificate = null;
214                         else
215                                 clientCertificate = localCertificates [0];
216                         return clientCertificate;
217                 }
218
219                 public MonoTlsProvider Provider {
220                         get { return provider; }
221                 }
222
223                 public MonoTlsSettings Settings {
224                         get { return settings; }
225                 }
226
227                 public bool HasCertificateSelectionCallback {
228                         get { return certSelectionCallback != null; }
229                 }
230
231                 public bool SelectClientCertificate (
232                         string targetHost, XX509CertificateCollection localCertificates, X509Certificate remoteCertificate,
233                         string[] acceptableIssuers, out X509Certificate clientCertificate)
234                 {
235                         if (certSelectionCallback == null) {
236                                 clientCertificate = null;
237                                 return false;
238                         }
239                         clientCertificate = certSelectionCallback (targetHost, localCertificates, remoteCertificate, acceptableIssuers);
240                         return true;
241                 }
242
243                 internal X509Certificate SelectClientCertificate (
244                         string targetHost, XX509CertificateCollection localCertificates, X509Certificate remoteCertificate,
245                         string[] acceptableIssuers)
246                 {
247                         if (certSelectionCallback == null)
248                                 return null;
249                         return certSelectionCallback (targetHost, localCertificates, remoteCertificate, acceptableIssuers);
250                 }
251
252                 internal bool ValidateClientCertificate (X509Certificate certificate, MonoSslPolicyErrors errors)
253                 {
254                         var certs = new XX509CertificateCollection ();
255                         certs.Add (new X509Certificate2 (certificate.GetRawCertData ()));
256
257                         var result = ValidateChain (string.Empty, true, certs, (SslPolicyErrors)errors);
258                         if (result == null)
259                                 return false;
260
261                         return result.Trusted && !result.UserDenied;
262                 }
263
264                 public ValidationResult ValidateCertificate (string host, bool serverMode, XX509CertificateCollection certs)
265                 {
266                         try {
267                                 var result = ValidateChain (host, serverMode, certs, 0);
268                                 if (tlsStream != null)
269                                         tlsStream.CertificateValidationFailed = result == null || !result.Trusted || result.UserDenied;
270                                 return result;
271                         } catch {
272                                 if (tlsStream != null)
273                                         tlsStream.CertificateValidationFailed = true;
274                                 throw;
275                         }
276                 }
277
278                 ValidationResult ValidateChain (string host, bool server, XX509CertificateCollection certs, SslPolicyErrors errors)
279                 {
280                         // user_denied is true if the user callback is called and returns false
281                         bool user_denied = false;
282                         bool result = false;
283
284                         var hasCallback = certValidationCallback != null || callbackWrapper != null;
285
286                         X509Certificate leaf;
287                         if (certs == null || certs.Count == 0)
288                                 leaf = null;
289                         else
290                                 leaf = certs [0];
291
292                         if (tlsStream != null)
293                                 request.ServicePoint.SetServerCertificate (leaf);
294
295                         if (leaf == null) {
296                                 errors |= SslPolicyErrors.RemoteCertificateNotAvailable;
297                                 if (hasCallback) {
298                                         if (callbackWrapper != null)
299                                                 result = callbackWrapper.Invoke (certValidationCallback, leaf, null, (MonoSslPolicyErrors)errors);
300                                         else
301                                                 result = certValidationCallback.Invoke (sender, leaf, null, errors);
302                                         user_denied = !result;
303                                 }
304                                 return new ValidationResult (result, user_denied, 0, (MonoSslPolicyErrors)errors);
305                         }
306
307                         ICertificatePolicy policy = ServicePointManager.GetLegacyCertificatePolicy ();
308
309                         int status11 = 0; // Error code passed to the obsolete ICertificatePolicy callback
310                         X509Chain chain = null;
311
312                         bool wantsChain = SystemCertificateValidator.NeedsChain (settings);
313                         if (!wantsChain && hasCallback) {
314                                 if (settings == null || settings.CallbackNeedsCertificateChain)
315                                         wantsChain = true;
316                         }
317
318                         if (wantsChain)
319                                 chain = SystemCertificateValidator.CreateX509Chain (certs);
320
321                         if (wantsChain || SystemCertificateValidator.NeedsChain (settings))
322                                 SystemCertificateValidator.BuildX509Chain (certs, chain, ref errors, ref status11);
323
324                         bool providerValidated = false;
325                         if (provider != null && provider.HasCustomSystemCertificateValidator) {
326                                 var xerrors = (MonoSslPolicyErrors)errors;
327                                 var xchain = (XX509Chain)(object)chain;
328                                 providerValidated = provider.InvokeSystemCertificateValidator (this, host, server, certs, xchain, out result, ref xerrors, ref status11);
329                                 errors = (SslPolicyErrors)xerrors;
330                         }
331
332                         if (!providerValidated)
333                                 result = SystemCertificateValidator.Evaluate (settings, host, certs, chain, ref errors, ref status11);
334
335                         if (policy != null && (!(policy is DefaultCertificatePolicy) || certValidationCallback == null)) {
336                                 ServicePoint sp = null;
337                                 if (request != null)
338                                         sp = request.ServicePointNoLock;
339                                 if (status11 == 0 && errors != 0) {
340                                         // TRUST_E_FAIL
341                                         status11 = unchecked ((int)0x800B010B);
342                                 }
343
344                                 // pre 2.0 callback
345                                 result = policy.CheckValidationResult (sp, leaf, request, status11);
346                                 user_denied = !result && !(policy is DefaultCertificatePolicy);
347                         }
348                         // If there's a 2.0 callback, it takes precedence
349                         if (hasCallback) {
350                                 if (callbackWrapper != null)
351                                         result = callbackWrapper.Invoke (certValidationCallback, leaf, chain, (MonoSslPolicyErrors)errors);
352                                 else
353                                         result = certValidationCallback.Invoke (sender, leaf, chain, errors);
354                                 user_denied = !result;
355                         }
356                         return new ValidationResult (result, user_denied, status11, (MonoSslPolicyErrors)errors);
357                 }
358
359                 public bool InvokeSystemValidator (string targetHost, bool serverMode, XX509CertificateCollection certificates, XX509Chain xchain, ref MonoSslPolicyErrors xerrors, ref int status11)
360                 {
361                         X509Chain chain = (X509Chain)(object)xchain;
362                         var errors = (SslPolicyErrors)xerrors;
363                         var result = SystemCertificateValidator.Evaluate (settings, targetHost, certificates, chain, ref errors, ref status11);
364                         xerrors = (MonoSslPolicyErrors)errors;
365                         return result;
366                 }
367         }
368 }
369 #endif
370