merge -r 58784:58785
[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                 internal override void OnNegotiateHandshakeCallback(IAsyncResult asyncResult)
256                 {
257                         this.protocol.EndSendRecord(asyncResult);
258
259                         // Read server response
260                         while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone)
261                         {
262                                 // Read next record
263                                 this.protocol.ReceiveRecord(this.innerStream);
264                         }
265
266                         // Send client certificate if requested
267                         // even if the server ask for it it _may_ still be optional
268                         bool clientCertificate = this.context.ServerSettings.CertificateRequest;
269
270                         // NOTE: sadly SSL3 and TLS1 differs in how they handle this and
271                         // the current design doesn't allow a very cute way to handle 
272                         // SSL3 alert warning for NoCertificate (41).
273                         if (this.context.SecurityProtocol == SecurityProtocolType.Ssl3)
274                         {
275                                 clientCertificate = ((this.context.ClientSettings.Certificates != null) &&
276                                         (this.context.ClientSettings.Certificates.Count > 0));
277                                 // this works well with OpenSSL (but only for SSL3)
278                         }
279
280                         if (clientCertificate)
281                         {
282                                 this.protocol.SendRecord(HandshakeType.Certificate);
283                         }
284
285                         // Send Client Key Exchange
286                         this.protocol.SendRecord(HandshakeType.ClientKeyExchange);
287
288                         // Now initialize session cipher with the generated keys
289                         this.context.Cipher.InitializeCipher();
290
291                         // Send certificate verify if requested (optional)
292                         if (clientCertificate && (this.context.ClientSettings.ClientCertificate != null))
293                         {
294                                 this.protocol.SendRecord(HandshakeType.CertificateVerify);
295                         }
296
297                         // Send Cipher Spec protocol
298                         this.protocol.SendChangeCipherSpec();
299
300                         // Read record until server finished is received
301                         while (this.context.HandshakeState != HandshakeState.Finished)
302                         {
303                                 // If all goes well this will process messages:
304                                 //              Change Cipher Spec
305                                 //              Server finished
306                                 this.protocol.ReceiveRecord(this.innerStream);
307                         }
308
309                         // Clear Key Info
310                         this.context.ClearKeyInfo();
311
312                 }
313
314                 #endregion
315
316                 #region Event Methods
317
318                 internal override X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates, X509Certificate serverCertificate, string targetHost, X509CertificateCollection serverRequestedCertificates)
319                 {
320                         if (this.ClientCertSelection != null)
321                         {
322                                 return this.ClientCertSelection(
323                                         clientCertificates,
324                                         serverCertificate,
325                                         targetHost,
326                                         serverRequestedCertificates);
327                         }
328
329                         return null;
330                 }
331                 
332                 internal override bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors)
333                 {
334                         if (this.ServerCertValidation != null)
335                         {
336                                 return this.ServerCertValidation(certificate, errors);
337                         }
338
339                         return (errors != null && errors.Length == 0);
340                 }
341
342                 internal virtual bool RaiseServerCertificateValidation(
343                         X509Certificate certificate, 
344                         int[]                   certificateErrors)
345                 {
346                         return base.RaiseRemoteCertificateValidation(certificate, certificateErrors);
347                 }
348
349                 internal X509Certificate RaiseClientCertificateSelection(
350                         X509CertificateCollection       clientCertificates, 
351                         X509Certificate                         serverCertificate, 
352                         string                                          targetHost, 
353                         X509CertificateCollection       serverRequestedCertificates)
354                 {
355                         return base.RaiseLocalCertificateSelection(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates);
356                 }
357
358                 internal override AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost)
359                 {
360                         if (this.PrivateKeySelection != null)
361                         {
362                                 return this.PrivateKeySelection(certificate, targetHost);
363                         }
364
365                         return null;
366                 }
367
368                 internal AsymmetricAlgorithm RaisePrivateKeySelection(
369                         X509Certificate certificate,
370                         string targetHost)
371                 {
372                         return base.RaiseLocalPrivateKeySelection(certificate, targetHost);
373                 }
374
375                 #endregion
376         }
377 }