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