1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
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:
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
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.
26 using System
.Collections
;
29 using System
.Net
.Sockets
;
30 using System
.Security
.Cryptography
;
31 using System
.Security
.Cryptography
.X509Certificates
;
32 using System
.Threading
;
34 using Mono
.Security
.Protocol
.Tls
.Handshake
;
36 namespace Mono
.Security
.Protocol
.Tls
40 public delegate bool CertificateValidationCallback(
41 X509Certificate certificate
,
42 int[] certificateErrors
);
44 public delegate X509Certificate
CertificateSelectionCallback(
45 X509CertificateCollection clientCertificates
,
46 X509Certificate serverCertificate
,
48 X509CertificateCollection serverRequestedCertificates
);
50 public delegate AsymmetricAlgorithm
PrivateKeySelectionCallback(
51 X509Certificate certificate
,
56 public class SslClientStream
: SslStreamBase
58 #region Internal Events
60 internal event CertificateValidationCallback ServerCertValidation
;
61 internal event CertificateSelectionCallback ClientCertSelection
;
62 internal event PrivateKeySelectionCallback PrivateKeySelection
;
68 // required by HttpsClientStream for proxy support
69 internal Stream InputBuffer
71 get { return base.inputBuffer; }
74 public X509CertificateCollection ClientCertificates
76 get { return this.context.ClientSettings.Certificates; }
79 public X509Certificate SelectedClientCertificate
81 get { return this.context.ClientSettings.ClientCertificate; }
86 #region Callback Properties
88 public CertificateValidationCallback ServerCertValidationDelegate
90 get { return this.ServerCertValidation; }
91 set { this.ServerCertValidation = value; }
94 public CertificateSelectionCallback ClientCertSelectionDelegate
96 get { return this.ClientCertSelection; }
97 set { this.ClientCertSelection = value; }
100 public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate
102 get { return this.PrivateKeySelection; }
103 set { this.PrivateKeySelection = value; }
110 public SslClientStream(
115 stream
, targetHost
, ownsStream
,
116 SecurityProtocolType
.Default
, null)
120 public SslClientStream(
123 X509Certificate clientCertificate
)
125 stream
, targetHost
, false, SecurityProtocolType
.Default
,
126 new X509CertificateCollection(new X509Certificate
[]{clientCertificate}
))
130 public SslClientStream(
133 X509CertificateCollection clientCertificates
) :
135 stream
, targetHost
, false, SecurityProtocolType
.Default
,
140 public SslClientStream(
144 SecurityProtocolType securityProtocolType
)
146 stream
, targetHost
, ownsStream
, securityProtocolType
,
147 new X509CertificateCollection())
151 public SslClientStream(
155 SecurityProtocolType securityProtocolType
,
156 X509CertificateCollection clientCertificates
):
157 base(stream
, ownsStream
)
159 if (targetHost
== null || targetHost
.Length
== 0)
161 throw new ArgumentNullException("targetHost is null or an empty string.");
164 this.context
= new ClientContext(
166 securityProtocolType
,
170 this.protocol
= new ClientRecordProtocol(innerStream
, (ClientContext
)this.context
);
184 #region IDisposable Methods
186 protected override void Dispose(bool disposing
)
188 base.Dispose(disposing
);
192 this.ServerCertValidation
= null;
193 this.ClientCertSelection
= null;
194 this.PrivateKeySelection
= null;
200 #region Handshake Methods
205 ClientHello -------->
210 <-------- ServerHelloDone
218 Application Data <-------> Application Data
220 Fig. 1 - Message flow for a full handshake
223 internal override IAsyncResult
OnBeginNegotiateHandshake(AsyncCallback callback
, object state
)
227 if (this.context
.HandshakeState
!= HandshakeState
.None
)
229 this.context
.Clear();
232 // Obtain supported cipher suites
233 this.context
.SupportedCiphers
= CipherSuiteFactory
.GetSupportedCiphers(this.context
.SecurityProtocol
);
235 // Set handshake state
236 this.context
.HandshakeState
= HandshakeState
.Started
;
239 return this.protocol
.BeginSendRecord(HandshakeType
.ClientHello
, callback
, state
);
241 catch (TlsException ex
)
243 this.protocol
.SendAlert(ex
.Alert
);
245 throw new IOException("The authentication or decryption has failed.", ex
);
249 this.protocol
.SendAlert(AlertDescription
.InternalError
);
251 throw new IOException("The authentication or decryption has failed.", ex
);
255 private void SafeReceiveRecord (Stream s
)
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.");
265 internal override void OnNegotiateHandshakeCallback(IAsyncResult asyncResult
)
267 this.protocol
.EndSendRecord(asyncResult
);
269 // Read server response
270 while (this.context
.LastHandshakeMsg
!= HandshakeType
.ServerHelloDone
)
273 SafeReceiveRecord (this.innerStream
);
275 // special case for abbreviated handshake where no ServerHelloDone is sent from the server
276 if (this.context
.AbbreviatedHandshake
&& (this.context
.LastHandshakeMsg
== HandshakeType
.ServerHello
))
280 // the handshake is much easier if we can reuse a previous session settings
281 if (this.context
.AbbreviatedHandshake
)
283 ClientSessionCache
.SetContextFromCache (this.context
);
284 this.context
.Negotiating
.Cipher
.ComputeKeys ();
285 this.context
.Negotiating
.Cipher
.InitializeCipher ();
287 // Send Cipher Spec protocol
288 this.protocol
.SendChangeCipherSpec ();
290 // Read record until server finished is received
291 while (this.context
.HandshakeState
!= HandshakeState
.Finished
)
293 // If all goes well this will process messages:
294 // Change Cipher Spec
296 SafeReceiveRecord (this.innerStream
);
299 // Send Finished message
300 this.protocol
.SendRecord (HandshakeType
.Finished
);
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
;
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
)
313 clientCertificate
= ((this.context
.ClientSettings
.Certificates
!= null) &&
314 (this.context
.ClientSettings
.Certificates
.Count
> 0));
315 // this works well with OpenSSL (but only for SSL3)
318 if (clientCertificate
)
320 this.protocol
.SendRecord(HandshakeType
.Certificate
);
323 // Send Client Key Exchange
324 this.protocol
.SendRecord(HandshakeType
.ClientKeyExchange
);
326 // Now initialize session cipher with the generated keys
327 this.context
.Negotiating
.Cipher
.InitializeCipher();
329 // Send certificate verify if requested (optional)
330 if (clientCertificate
&& (this.context
.ClientSettings
.ClientCertificate
!= null))
332 this.protocol
.SendRecord(HandshakeType
.CertificateVerify
);
335 // Send Cipher Spec protocol
336 this.protocol
.SendChangeCipherSpec ();
338 // Send Finished message
339 this.protocol
.SendRecord (HandshakeType
.Finished
);
341 // Read record until server finished is received
342 while (this.context
.HandshakeState
!= HandshakeState
.Finished
) {
343 // If all goes well this will process messages:
344 // Change Cipher Spec
346 SafeReceiveRecord (this.innerStream
);
350 // Reset Handshake messages information
351 this.context
.HandshakeMessages
.Reset ();
354 this.context
.ClearKeyInfo();
360 #region Event Methods
362 internal override X509Certificate
OnLocalCertificateSelection(X509CertificateCollection clientCertificates
, X509Certificate serverCertificate
, string targetHost
, X509CertificateCollection serverRequestedCertificates
)
364 if (this.ClientCertSelection
!= null)
366 return this.ClientCertSelection(
370 serverRequestedCertificates
);
376 internal override bool OnRemoteCertificateValidation(X509Certificate certificate
, int[] errors
)
378 if (this.ServerCertValidation
!= null)
380 return this.ServerCertValidation(certificate
, errors
);
383 return (errors
!= null && errors
.Length
== 0);
386 internal virtual bool RaiseServerCertificateValidation(
387 X509Certificate certificate
,
388 int[] certificateErrors
)
390 return base.RaiseRemoteCertificateValidation(certificate
, certificateErrors
);
393 internal X509Certificate
RaiseClientCertificateSelection(
394 X509CertificateCollection clientCertificates
,
395 X509Certificate serverCertificate
,
397 X509CertificateCollection serverRequestedCertificates
)
399 return base.RaiseLocalCertificateSelection(clientCertificates
, serverCertificate
, targetHost
, serverRequestedCertificates
);
402 internal override AsymmetricAlgorithm
OnLocalPrivateKeySelection(X509Certificate certificate
, string targetHost
)
404 if (this.PrivateKeySelection
!= null)
406 return this.PrivateKeySelection(certificate
, targetHost
);
412 internal AsymmetricAlgorithm
RaisePrivateKeySelection(
413 X509Certificate certificate
,
416 return base.RaiseLocalPrivateKeySelection(certificate
, targetHost
);