fix dist, reviewed by vargaz
[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         public delegate bool CertificateValidationCallback(
41                 X509Certificate certificate, 
42                 int[]                   certificateErrors);
43         public delegate bool CertificateValidationCallback2 (Mono.Security.X509.X509CertificateCollection collection);
44
45         public delegate X509Certificate CertificateSelectionCallback(
46                 X509CertificateCollection       clientCertificates, 
47                 X509Certificate                         serverCertificate, 
48                 string                                          targetHost, 
49                 X509CertificateCollection       serverRequestedCertificates);
50
51         public delegate AsymmetricAlgorithm PrivateKeySelectionCallback(
52                 X509Certificate certificate, 
53                 string                  targetHost);
54
55         #endregion
56
57         public class SslClientStream : SslStreamBase
58         {
59                 #region Internal Events
60                 
61                 internal event CertificateValidationCallback    ServerCertValidation;
62                 internal event CertificateSelectionCallback             ClientCertSelection;
63                 internal event PrivateKeySelectionCallback              PrivateKeySelection;
64                 
65                 #endregion
66
67                 #region Properties
68
69                 // required by HttpsClientStream for proxy support
70                 internal Stream InputBuffer 
71                 {
72                         get { return base.inputBuffer; }
73                 }
74
75                 public X509CertificateCollection ClientCertificates
76                 {
77                         get { return this.context.ClientSettings.Certificates; }
78                 }
79
80                 public X509Certificate SelectedClientCertificate
81                 {
82                         get { return this.context.ClientSettings.ClientCertificate; }
83                 }
84
85                 #endregion
86
87                 #region Callback Properties
88
89                 public CertificateValidationCallback ServerCertValidationDelegate
90                 {
91                         get { return this.ServerCertValidation; }
92                         set { this.ServerCertValidation = value; }                      
93                 }
94
95                 public CertificateSelectionCallback ClientCertSelectionDelegate 
96                 {
97                         get { return this.ClientCertSelection; }
98                         set { this.ClientCertSelection = value; }
99                 }
100
101                 public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate
102                 {
103                         get { return this.PrivateKeySelection; }
104                         set { this.PrivateKeySelection = value; }
105                 }
106                 
107                 #endregion
108
109                 public event CertificateValidationCallback2 ServerCertValidation2;
110                 #region Constructors
111                 
112                 public SslClientStream(
113                         Stream  stream, 
114                         string  targetHost, 
115                         bool    ownsStream) 
116                         : this(
117                                 stream, targetHost, ownsStream, 
118                                 SecurityProtocolType.Default, null)
119                 {
120                 }
121                 
122                 public SslClientStream(
123                         Stream                          stream, 
124                         string                          targetHost, 
125                         X509Certificate         clientCertificate) 
126                         : this(
127                                 stream, targetHost, false, SecurityProtocolType.Default, 
128                                 new X509CertificateCollection(new X509Certificate[]{clientCertificate}))
129                 {
130                 }
131
132                 public SslClientStream(
133                         Stream                                          stream,
134                         string                                          targetHost, 
135                         X509CertificateCollection clientCertificates) : 
136                         this(
137                                 stream, targetHost, false, SecurityProtocolType.Default, 
138                                 clientCertificates)
139                 {
140                 }
141
142                 public SslClientStream(
143                         Stream                                  stream,
144                         string                                  targetHost,
145                         bool                                    ownsStream,
146                         SecurityProtocolType    securityProtocolType) 
147                         : this(
148                                 stream, targetHost, ownsStream, securityProtocolType,
149                                 new X509CertificateCollection())
150                 {
151                 }
152
153                 public SslClientStream(
154                         Stream                                          stream,
155                         string                                          targetHost,
156                         bool                                            ownsStream,
157                         SecurityProtocolType            securityProtocolType,
158                         X509CertificateCollection       clientCertificates):
159                         base(stream, ownsStream)
160                 {
161                         if (targetHost == null || targetHost.Length == 0)
162                         {
163                                 throw new ArgumentNullException("targetHost is null or an empty string.");
164                         }
165
166                         this.context = new ClientContext(
167                                 this,
168                                 securityProtocolType, 
169                                 targetHost, 
170                                 clientCertificates);
171
172                         this.protocol = new ClientRecordProtocol(innerStream, (ClientContext)this.context);
173                 }
174
175                 #endregion
176
177                 #region Finalizer
178
179                 ~SslClientStream()
180                 {
181                         base.Dispose(false);
182                 }
183
184                 #endregion
185
186                 #region IDisposable Methods
187
188                 protected override void Dispose(bool disposing)
189                 {
190                         base.Dispose(disposing);
191
192                         if (disposing)
193                         {
194                                 this.ServerCertValidation = null;
195                                 this.ClientCertSelection = null;
196                                 this.PrivateKeySelection = null;
197                                 this.ServerCertValidation2 = null;
198                         }
199                 }
200
201                 #endregion
202
203                 #region Handshake Methods
204
205                 /*
206                         Client                                                                                  Server
207
208                         ClientHello                 -------->
209                                                                                                                         ServerHello
210                                                                                                                         Certificate*
211                                                                                                                         ServerKeyExchange*
212                                                                                                                         CertificateRequest*
213                                                                                 <--------                       ServerHelloDone
214                         Certificate*
215                         ClientKeyExchange
216                         CertificateVerify*
217                         [ChangeCipherSpec]
218                         Finished                    -------->
219                                                                                                                         [ChangeCipherSpec]
220                                                                                 <--------           Finished
221                         Application Data            <------->                   Application Data
222
223                                         Fig. 1 - Message flow for a full handshake              
224                 */
225
226                 internal override IAsyncResult OnBeginNegotiateHandshake(AsyncCallback callback, object state)
227                 {
228                         try
229                         {
230                                 if (this.context.HandshakeState != HandshakeState.None)
231                                 {
232                                         this.context.Clear();
233                                 }
234
235                                 // Obtain supported cipher suites
236                                 this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
237
238                                 // Set handshake state
239                                 this.context.HandshakeState = HandshakeState.Started;
240
241                                 // Send client hello
242                                 return this.protocol.BeginSendRecord(HandshakeType.ClientHello, callback, state);
243                         }
244                         catch (TlsException ex)
245                         {
246                                 this.protocol.SendAlert(ex.Alert);
247
248                                 throw new IOException("The authentication or decryption has failed.", ex);
249                         }
250                         catch (Exception ex)
251                         {
252                                 this.protocol.SendAlert(AlertDescription.InternalError);
253
254                                 throw new IOException("The authentication or decryption has failed.", ex);
255                         }
256                 }
257
258                 private void SafeReceiveRecord (Stream s)
259                 {
260                         byte[] record = this.protocol.ReceiveRecord (s);
261                         if ((record == null) || (record.Length == 0)) {
262                                 throw new TlsException (
263                                         AlertDescription.HandshakeFailiure,
264                                         "The server stopped the handshake.");
265                         }
266                 }
267
268                 internal override void OnNegotiateHandshakeCallback(IAsyncResult asyncResult)
269                 {
270                         this.protocol.EndSendRecord(asyncResult);
271
272                         // Read server response
273                         while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone) 
274                         {
275                                 // Read next record
276                                 SafeReceiveRecord (this.innerStream);
277
278                                 // special case for abbreviated handshake where no ServerHelloDone is sent from the server
279                                 if (this.context.AbbreviatedHandshake && (this.context.LastHandshakeMsg == HandshakeType.ServerHello))
280                                         break;
281                         }
282
283                         // the handshake is much easier if we can reuse a previous session settings
284                         if (this.context.AbbreviatedHandshake) 
285                         {
286                                 ClientSessionCache.SetContextFromCache (this.context);
287                                 this.context.Negotiating.Cipher.ComputeKeys ();
288                                 this.context.Negotiating.Cipher.InitializeCipher ();
289
290                                 // Send Cipher Spec protocol
291                                 this.protocol.SendChangeCipherSpec ();
292
293                                 // Read record until server finished is received
294                                 while (this.context.HandshakeState != HandshakeState.Finished) 
295                                 {
296                                         // If all goes well this will process messages:
297                                         //              Change Cipher Spec
298                                         //              Server finished
299                                         SafeReceiveRecord (this.innerStream);
300                                 }
301
302                                 // Send Finished message
303                                 this.protocol.SendRecord (HandshakeType.Finished);
304                         }
305                         else
306                         {
307                                 // Send client certificate if requested
308                                 // even if the server ask for it it _may_ still be optional
309                                 bool clientCertificate = this.context.ServerSettings.CertificateRequest;
310
311                                 // NOTE: sadly SSL3 and TLS1 differs in how they handle this and
312                                 // the current design doesn't allow a very cute way to handle 
313                                 // SSL3 alert warning for NoCertificate (41).
314                                 if (this.context.SecurityProtocol == SecurityProtocolType.Ssl3)
315                                 {
316                                         clientCertificate = ((this.context.ClientSettings.Certificates != null) &&
317                                                 (this.context.ClientSettings.Certificates.Count > 0));
318                                         // this works well with OpenSSL (but only for SSL3)
319                                 }
320
321                                 if (clientCertificate)
322                                 {
323                                         this.protocol.SendRecord(HandshakeType.Certificate);
324                                 }
325
326                                 // Send Client Key Exchange
327                                 this.protocol.SendRecord(HandshakeType.ClientKeyExchange);
328
329                                 // Now initialize session cipher with the generated keys
330                                 this.context.Negotiating.Cipher.InitializeCipher();
331
332                                 // Send certificate verify if requested (optional)
333                                 if (clientCertificate && (this.context.ClientSettings.ClientCertificate != null))
334                                 {
335                                         this.protocol.SendRecord(HandshakeType.CertificateVerify);
336                                 }
337
338                                 // Send Cipher Spec protocol
339                                 this.protocol.SendChangeCipherSpec ();
340
341                                 // Send Finished message
342                                 this.protocol.SendRecord (HandshakeType.Finished);
343
344                                 // Read record until server finished is received
345                                 while (this.context.HandshakeState != HandshakeState.Finished) {
346                                         // If all goes well this will process messages:
347                                         //              Change Cipher Spec
348                                         //              Server finished
349                                         SafeReceiveRecord (this.innerStream);
350                                 }
351                         }
352
353                         // Reset Handshake messages information
354                         this.context.HandshakeMessages.Reset ();
355
356                         // Clear Key Info
357                         this.context.ClearKeyInfo();
358
359                 }
360
361                 #endregion
362
363                 #region Event Methods
364
365                 internal override X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates, X509Certificate serverCertificate, string targetHost, X509CertificateCollection serverRequestedCertificates)
366                 {
367                         if (this.ClientCertSelection != null)
368                         {
369                                 return this.ClientCertSelection(
370                                         clientCertificates,
371                                         serverCertificate,
372                                         targetHost,
373                                         serverRequestedCertificates);
374                         }
375
376                         return null;
377                 }
378
379                 internal override bool HaveRemoteValidation2Callback {
380                         get { return ServerCertValidation2 != null; }
381                 }
382
383                 internal override bool OnRemoteCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection)
384                 {
385                         CertificateValidationCallback2 cb = ServerCertValidation2;
386                         if (cb != null)
387                                 return cb (collection);
388                         return false;
389                 }
390
391                 internal override bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors)
392                 {
393                         if (this.ServerCertValidation != null)
394                         {
395                                 return this.ServerCertValidation(certificate, errors);
396                         }
397
398                         return (errors != null && errors.Length == 0);
399                 }
400
401                 internal virtual bool RaiseServerCertificateValidation(
402                         X509Certificate certificate, 
403                         int[]                   certificateErrors)
404                 {
405                         return base.RaiseRemoteCertificateValidation(certificate, certificateErrors);
406                 }
407
408                 internal virtual bool RaiseServerCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection)
409                 {
410                         return base.RaiseRemoteCertificateValidation2 (collection);
411                 }
412
413                 internal X509Certificate RaiseClientCertificateSelection(
414                         X509CertificateCollection       clientCertificates, 
415                         X509Certificate                         serverCertificate, 
416                         string                                          targetHost, 
417                         X509CertificateCollection       serverRequestedCertificates)
418                 {
419                         return base.RaiseLocalCertificateSelection(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates);
420                 }
421
422                 internal override AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost)
423                 {
424                         if (this.PrivateKeySelection != null)
425                         {
426                                 return this.PrivateKeySelection(certificate, targetHost);
427                         }
428
429                         return null;
430                 }
431
432                 internal AsymmetricAlgorithm RaisePrivateKeySelection(
433                         X509Certificate certificate,
434                         string targetHost)
435                 {
436                         return base.RaiseLocalPrivateKeySelection(certificate, targetHost);
437                 }
438
439                 #endregion
440         }
441 }