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