Add unit test for AggregateException.GetBaseException that works on .net but is broke...
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / SslClientStream.cs
1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the
7 // "Software"), to deal in the Software without restriction, including
8 // without limitation the rights to use, copy, modify, merge, publish,
9 // distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so, subject to
11 // the following conditions:
12 // 
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
15 // 
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 //
24
25 using System;
26 using System.Collections;
27 using System.IO;
28 using System.Net;
29 using System.Net.Sockets;
30 using System.Security.Cryptography;
31 using System.Security.Cryptography.X509Certificates;
32 using System.Threading;
33
34 using Mono.Security.Protocol.Tls.Handshake;
35
36 namespace Mono.Security.Protocol.Tls
37 {
38         #region Delegates
39
40 #if INSIDE_SYSTEM
41         internal
42 #else
43         public
44 #endif
45         delegate bool CertificateValidationCallback(
46                 X509Certificate certificate, 
47                 int[]                   certificateErrors);
48
49 #if INSIDE_SYSTEM
50         internal
51 #else
52         public
53 #endif
54         class ValidationResult {
55                 bool trusted;
56                 bool user_denied;
57                 int error_code;
58
59                 public ValidationResult (bool trusted, bool user_denied, int error_code)
60                 {
61                         this.trusted = trusted;
62                         this.user_denied = user_denied;
63                         this.error_code = error_code;
64                 }
65
66                 public bool Trusted {
67                         get { return trusted; }
68                 }
69
70                 public bool UserDenied {
71                         get { return user_denied; }
72                 }
73
74                 public int ErrorCode {
75                         get { return error_code; }
76                 }
77         }
78
79 #if INSIDE_SYSTEM
80         internal
81 #else
82         public
83 #endif
84         delegate ValidationResult CertificateValidationCallback2 (Mono.Security.X509.X509CertificateCollection collection);
85
86 #if INSIDE_SYSTEM
87         internal
88 #else
89         public
90 #endif
91         delegate X509Certificate CertificateSelectionCallback(
92                 X509CertificateCollection       clientCertificates, 
93                 X509Certificate                         serverCertificate, 
94                 string                                          targetHost, 
95                 X509CertificateCollection       serverRequestedCertificates);
96
97 #if INSIDE_SYSTEM
98         internal
99 #else
100         public
101 #endif
102         delegate AsymmetricAlgorithm PrivateKeySelectionCallback(
103                 X509Certificate certificate, 
104                 string                  targetHost);
105
106         #endregion
107
108 #if INSIDE_SYSTEM
109         internal
110 #else
111         public
112 #endif
113         class SslClientStream : SslStreamBase
114         {
115                 #region Internal Events
116                 
117                 internal event CertificateValidationCallback    ServerCertValidation;
118                 internal event CertificateSelectionCallback             ClientCertSelection;
119                 internal event PrivateKeySelectionCallback              PrivateKeySelection;
120                 
121                 #endregion
122
123                 #region Properties
124
125                 // required by HttpsClientStream for proxy support
126                 internal Stream InputBuffer 
127                 {
128                         get { return base.inputBuffer; }
129                 }
130
131                 public X509CertificateCollection ClientCertificates
132                 {
133                         get { return this.context.ClientSettings.Certificates; }
134                 }
135
136                 public X509Certificate SelectedClientCertificate
137                 {
138                         get { return this.context.ClientSettings.ClientCertificate; }
139                 }
140
141                 #endregion
142
143                 #region Callback Properties
144
145                 public CertificateValidationCallback ServerCertValidationDelegate
146                 {
147                         get { return this.ServerCertValidation; }
148                         set { this.ServerCertValidation = value; }                      
149                 }
150
151                 public CertificateSelectionCallback ClientCertSelectionDelegate 
152                 {
153                         get { return this.ClientCertSelection; }
154                         set { this.ClientCertSelection = value; }
155                 }
156
157                 public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate
158                 {
159                         get { return this.PrivateKeySelection; }
160                         set { this.PrivateKeySelection = value; }
161                 }
162                 
163                 #endregion
164
165                 public event CertificateValidationCallback2 ServerCertValidation2;
166
167                 #region Constructors
168                 
169                 public SslClientStream(
170                         Stream  stream, 
171                         string  targetHost, 
172                         bool    ownsStream) 
173                         : this(
174                                 stream, targetHost, ownsStream, 
175                                 SecurityProtocolType.Default, null)
176                 {
177                 }
178                 
179                 public SslClientStream(
180                         Stream                          stream, 
181                         string                          targetHost, 
182                         X509Certificate         clientCertificate) 
183                         : this(
184                                 stream, targetHost, false, SecurityProtocolType.Default, 
185                                 new X509CertificateCollection(new X509Certificate[]{clientCertificate}))
186                 {
187                 }
188
189                 public SslClientStream(
190                         Stream                                          stream,
191                         string                                          targetHost, 
192                         X509CertificateCollection clientCertificates) : 
193                         this(
194                                 stream, targetHost, false, SecurityProtocolType.Default, 
195                                 clientCertificates)
196                 {
197                 }
198
199                 public SslClientStream(
200                         Stream                                  stream,
201                         string                                  targetHost,
202                         bool                                    ownsStream,
203                         SecurityProtocolType    securityProtocolType) 
204                         : this(
205                                 stream, targetHost, ownsStream, securityProtocolType,
206                                 new X509CertificateCollection())
207                 {
208                 }
209
210                 public SslClientStream(
211                         Stream                                          stream,
212                         string                                          targetHost,
213                         bool                                            ownsStream,
214                         SecurityProtocolType            securityProtocolType,
215                         X509CertificateCollection       clientCertificates):
216                         base(stream, ownsStream)
217                 {
218                         if (targetHost == null || targetHost.Length == 0)
219                         {
220                                 throw new ArgumentNullException("targetHost is null or an empty string.");
221                         }
222
223                         this.context = new ClientContext(
224                                 this,
225                                 securityProtocolType, 
226                                 targetHost, 
227                                 clientCertificates);
228
229                         this.protocol = new ClientRecordProtocol(innerStream, (ClientContext)this.context);
230                 }
231
232                 #endregion
233
234                 #region Finalizer
235
236                 ~SslClientStream()
237                 {
238                         base.Dispose(false);
239                 }
240
241                 #endregion
242
243                 #region IDisposable Methods
244
245                 protected override void Dispose(bool disposing)
246                 {
247                         base.Dispose(disposing);
248
249                         if (disposing)
250                         {
251                                 this.ServerCertValidation = null;
252                                 this.ClientCertSelection = null;
253                                 this.PrivateKeySelection = null;
254                                 this.ServerCertValidation2 = null;
255                         }
256                 }
257
258                 #endregion
259
260                 #region Handshake Methods
261
262                 /*
263                         Client                                                                                  Server
264
265                         ClientHello                 -------->
266                                                                                                                         ServerHello
267                                                                                                                         Certificate*
268                                                                                                                         ServerKeyExchange*
269                                                                                                                         CertificateRequest*
270                                                                                 <--------                       ServerHelloDone
271                         Certificate*
272                         ClientKeyExchange
273                         CertificateVerify*
274                         [ChangeCipherSpec]
275                         Finished                    -------->
276                                                                                                                         [ChangeCipherSpec]
277                                                                                 <--------           Finished
278                         Application Data            <------->                   Application Data
279
280                                         Fig. 1 - Message flow for a full handshake              
281                 */
282
283                 internal override IAsyncResult OnBeginNegotiateHandshake(AsyncCallback callback, object state)
284                 {
285                         try
286                         {
287                                 if (this.context.HandshakeState != HandshakeState.None)
288                                 {
289                                         this.context.Clear();
290                                 }
291
292                                 // Obtain supported cipher suites
293                                 this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
294
295                                 // Set handshake state
296                                 this.context.HandshakeState = HandshakeState.Started;
297
298                                 // Send client hello
299                                 return this.protocol.BeginSendRecord(HandshakeType.ClientHello, callback, state);
300                         }
301                         catch (TlsException ex)
302                         {
303                                 this.protocol.SendAlert(ex.Alert);
304
305                                 throw new IOException("The authentication or decryption has failed.", ex);
306                         }
307                         catch (Exception ex)
308                         {
309                                 this.protocol.SendAlert(AlertDescription.InternalError);
310
311                                 throw new IOException("The authentication or decryption has failed.", ex);
312                         }
313                 }
314
315                 private void SafeReceiveRecord (Stream s, bool ignoreEmpty = false)
316                 {
317                         byte[] record = this.protocol.ReceiveRecord (s);
318                         if (!ignoreEmpty && ((record == null) || (record.Length == 0))) {
319                                 throw new TlsException (
320                                         AlertDescription.HandshakeFailiure,
321                                         "The server stopped the handshake.");
322                         }
323                 }
324
325                 internal override void OnNegotiateHandshakeCallback(IAsyncResult asyncResult)
326                 {
327                         this.protocol.EndSendRecord(asyncResult);
328
329                         // Read server response
330                         while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone) 
331                         {
332                                 // Read next record (skip empty, e.g. warnings alerts)
333                                 SafeReceiveRecord (this.innerStream, true);
334
335                                 // special case for abbreviated handshake where no ServerHelloDone is sent from the server
336                                 if (this.context.AbbreviatedHandshake && (this.context.LastHandshakeMsg == HandshakeType.ServerHello))
337                                         break;
338                         }
339
340                         // the handshake is much easier if we can reuse a previous session settings
341                         if (this.context.AbbreviatedHandshake) 
342                         {
343                                 ClientSessionCache.SetContextFromCache (this.context);
344                                 this.context.Negotiating.Cipher.ComputeKeys ();
345                                 this.context.Negotiating.Cipher.InitializeCipher ();
346
347                                 // Send Cipher Spec protocol
348                                 this.protocol.SendChangeCipherSpec ();
349
350                                 // Read record until server finished is received
351                                 while (this.context.HandshakeState != HandshakeState.Finished) 
352                                 {
353                                         // If all goes well this will process messages:
354                                         //              Change Cipher Spec
355                                         //              Server finished
356                                         SafeReceiveRecord (this.innerStream);
357                                 }
358
359                                 // Send Finished message
360                                 this.protocol.SendRecord (HandshakeType.Finished);
361                         }
362                         else
363                         {
364                                 // Send client certificate if requested
365                                 // even if the server ask for it it _may_ still be optional
366                                 bool clientCertificate = this.context.ServerSettings.CertificateRequest;
367
368                                 // NOTE: sadly SSL3 and TLS1 differs in how they handle this and
369                                 // the current design doesn't allow a very cute way to handle 
370                                 // SSL3 alert warning for NoCertificate (41).
371                                 if (this.context.SecurityProtocol == SecurityProtocolType.Ssl3)
372                                 {
373                                         clientCertificate = ((this.context.ClientSettings.Certificates != null) &&
374                                                 (this.context.ClientSettings.Certificates.Count > 0));
375                                         // this works well with OpenSSL (but only for SSL3)
376                                 }
377
378                                 if (clientCertificate)
379                                 {
380                                         this.protocol.SendRecord(HandshakeType.Certificate);
381                                 }
382
383                                 // Send Client Key Exchange
384                                 this.protocol.SendRecord(HandshakeType.ClientKeyExchange);
385
386                                 // Now initialize session cipher with the generated keys
387                                 this.context.Negotiating.Cipher.InitializeCipher();
388
389                                 // Send certificate verify if requested (optional)
390                                 if (clientCertificate && (this.context.ClientSettings.ClientCertificate != null))
391                                 {
392                                         this.protocol.SendRecord(HandshakeType.CertificateVerify);
393                                 }
394
395                                 // Send Cipher Spec protocol
396                                 this.protocol.SendChangeCipherSpec ();
397
398                                 // Send Finished message
399                                 this.protocol.SendRecord (HandshakeType.Finished);
400
401                                 // Read record until server finished is received
402                                 while (this.context.HandshakeState != HandshakeState.Finished) {
403                                         // If all goes well this will process messages:
404                                         //              Change Cipher Spec
405                                         //              Server finished
406                                         SafeReceiveRecord (this.innerStream);
407                                 }
408                         }
409
410                         // Reset Handshake messages information
411                         this.context.HandshakeMessages.Reset ();
412
413                         // Clear Key Info
414                         this.context.ClearKeyInfo();
415
416                 }
417
418                 #endregion
419
420                 #region Event Methods
421
422                 internal override X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates, X509Certificate serverCertificate, string targetHost, X509CertificateCollection serverRequestedCertificates)
423                 {
424                         if (this.ClientCertSelection != null)
425                         {
426                                 return this.ClientCertSelection(
427                                         clientCertificates,
428                                         serverCertificate,
429                                         targetHost,
430                                         serverRequestedCertificates);
431                         }
432
433                         return null;
434                 }
435
436                 internal override bool HaveRemoteValidation2Callback {
437                         get { return ServerCertValidation2 != null; }
438                 }
439
440                 internal override ValidationResult OnRemoteCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection)
441                 {
442                         CertificateValidationCallback2 cb = ServerCertValidation2;
443                         if (cb != null)
444                                 return cb (collection);
445                         return null;
446                 }
447
448                 internal override bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors)
449                 {
450                         if (this.ServerCertValidation != null)
451                         {
452                                 return this.ServerCertValidation(certificate, errors);
453                         }
454
455                         return (errors != null && errors.Length == 0);
456                 }
457
458                 internal virtual bool RaiseServerCertificateValidation(
459                         X509Certificate certificate, 
460                         int[]                   certificateErrors)
461                 {
462                         return base.RaiseRemoteCertificateValidation(certificate, certificateErrors);
463                 }
464
465                 internal virtual ValidationResult RaiseServerCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection)
466                 {
467                         return base.RaiseRemoteCertificateValidation2 (collection);
468                 }
469
470                 internal X509Certificate RaiseClientCertificateSelection(
471                         X509CertificateCollection       clientCertificates, 
472                         X509Certificate                         serverCertificate, 
473                         string                                          targetHost, 
474                         X509CertificateCollection       serverRequestedCertificates)
475                 {
476                         return base.RaiseLocalCertificateSelection(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates);
477                 }
478
479                 internal override AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost)
480                 {
481                         if (this.PrivateKeySelection != null)
482                         {
483                                 return this.PrivateKeySelection(certificate, targetHost);
484                         }
485
486                         return null;
487                 }
488
489                 internal AsymmetricAlgorithm RaisePrivateKeySelection(
490                         X509Certificate certificate,
491                         string targetHost)
492                 {
493                         return base.RaiseLocalPrivateKeySelection(certificate, targetHost);
494                 }
495
496                 #endregion
497         }
498 }