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