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
45 delegate bool CertificateValidationCallback(
46 X509Certificate certificate,
47 int[] certificateErrors);
54 class ValidationResult {
59 public ValidationResult (bool trusted, bool user_denied, int error_code)
61 this.trusted = trusted;
62 this.user_denied = user_denied;
63 this.error_code = error_code;
67 get { return trusted; }
70 public bool UserDenied {
71 get { return user_denied; }
74 public int ErrorCode {
75 get { return error_code; }
84 delegate ValidationResult CertificateValidationCallback2 (Mono.Security.X509.X509CertificateCollection collection);
91 delegate X509Certificate CertificateSelectionCallback(
92 X509CertificateCollection clientCertificates,
93 X509Certificate serverCertificate,
95 X509CertificateCollection serverRequestedCertificates);
102 delegate AsymmetricAlgorithm PrivateKeySelectionCallback(
103 X509Certificate certificate,
113 class SslClientStream : SslStreamBase
115 #region Internal Events
117 internal event CertificateValidationCallback ServerCertValidation;
118 internal event CertificateSelectionCallback ClientCertSelection;
119 internal event PrivateKeySelectionCallback PrivateKeySelection;
125 // required by HttpsClientStream for proxy support
126 internal Stream InputBuffer
128 get { return base.inputBuffer; }
131 public X509CertificateCollection ClientCertificates
133 get { return this.context.ClientSettings.Certificates; }
136 public X509Certificate SelectedClientCertificate
138 get { return this.context.ClientSettings.ClientCertificate; }
143 #region Callback Properties
145 public CertificateValidationCallback ServerCertValidationDelegate
147 get { return this.ServerCertValidation; }
148 set { this.ServerCertValidation = value; }
151 public CertificateSelectionCallback ClientCertSelectionDelegate
153 get { return this.ClientCertSelection; }
154 set { this.ClientCertSelection = value; }
157 public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate
159 get { return this.PrivateKeySelection; }
160 set { this.PrivateKeySelection = value; }
165 public event CertificateValidationCallback2 ServerCertValidation2;
169 public SslClientStream(
174 stream, targetHost, ownsStream,
175 SecurityProtocolType.Default, null)
179 public SslClientStream(
182 X509Certificate clientCertificate)
184 stream, targetHost, false, SecurityProtocolType.Default,
185 new X509CertificateCollection(new X509Certificate[]{clientCertificate}))
189 public SslClientStream(
192 X509CertificateCollection clientCertificates) :
194 stream, targetHost, false, SecurityProtocolType.Default,
199 public SslClientStream(
203 SecurityProtocolType securityProtocolType)
205 stream, targetHost, ownsStream, securityProtocolType,
206 new X509CertificateCollection())
210 public SslClientStream(
214 SecurityProtocolType securityProtocolType,
215 X509CertificateCollection clientCertificates):
216 base(stream, ownsStream)
218 if (targetHost == null || targetHost.Length == 0)
220 throw new ArgumentNullException("targetHost is null or an empty string.");
223 this.context = new ClientContext(
225 securityProtocolType,
229 this.protocol = new ClientRecordProtocol(innerStream, (ClientContext)this.context);
243 #region IDisposable Methods
245 protected override void Dispose(bool disposing)
247 base.Dispose(disposing);
251 this.ServerCertValidation = null;
252 this.ClientCertSelection = null;
253 this.PrivateKeySelection = null;
254 this.ServerCertValidation2 = null;
260 #region Handshake Methods
265 ClientHello -------->
270 <-------- ServerHelloDone
278 Application Data <-------> Application Data
280 Fig. 1 - Message flow for a full handshake
283 private void SafeEndReceiveRecord (IAsyncResult ar, bool ignoreEmpty = false)
285 byte[] record = this.protocol.EndReceiveRecord (ar);
286 if (!ignoreEmpty && ((record == null) || (record.Length == 0))) {
287 throw new TlsException (
288 AlertDescription.HandshakeFailiure,
289 "The server stopped the handshake.");
293 private enum NegotiateState
296 ReceiveClientHelloResponse,
298 ReceiveCipherSpecResponse,
300 ReceiveFinishResponse,
304 private class NegotiateAsyncResult : IAsyncResult
306 private object locker = new object ();
307 private AsyncCallback _userCallback;
308 private object _userState;
309 private Exception _asyncException;
310 private ManualResetEvent handle;
311 private NegotiateState _state;
312 private bool completed;
314 public NegotiateAsyncResult(AsyncCallback userCallback, object userState, NegotiateState state)
316 _userCallback = userCallback;
317 _userState = userState;
321 public NegotiateState State
323 get { return _state; }
324 set { _state = value; }
327 public object AsyncState
329 get { return _userState; }
332 public Exception AsyncException
334 get { return _asyncException; }
337 public bool CompletedWithError
341 return false; // Perhaps throw InvalidOperationExcetion?
343 return null != _asyncException;
347 public WaitHandle AsyncWaitHandle
352 handle = new ManualResetEvent (completed);
359 public bool CompletedSynchronously
361 get { return false; }
364 public bool IsCompleted
373 public void SetComplete(Exception ex)
383 if (_userCallback != null)
384 _userCallback.BeginInvoke (this, null, null);
386 _asyncException = ex;
390 public void SetComplete()
396 internal override IAsyncResult BeginNegotiateHandshake(AsyncCallback callback, object state)
398 if (this.context.HandshakeState != HandshakeState.None) {
399 this.context.Clear ();
402 // Obtain supported cipher suites
403 this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers (false, context.SecurityProtocol);
405 // Set handshake state
406 this.context.HandshakeState = HandshakeState.Started;
408 NegotiateAsyncResult result = new NegotiateAsyncResult (callback, state, NegotiateState.SentClientHello);
410 // Begin sending the client hello
411 this.protocol.BeginSendRecord (HandshakeType.ClientHello, NegotiateAsyncWorker, result);
416 internal override void EndNegotiateHandshake (IAsyncResult result)
418 NegotiateAsyncResult negotiate = result as NegotiateAsyncResult;
420 if (negotiate == null)
421 throw new ArgumentNullException ();
422 if (!negotiate.IsCompleted)
423 negotiate.AsyncWaitHandle.WaitOne();
424 if (negotiate.CompletedWithError)
425 throw negotiate.AsyncException;
428 private void NegotiateAsyncWorker (IAsyncResult result)
430 NegotiateAsyncResult negotiate = result.AsyncState as NegotiateAsyncResult;
434 switch (negotiate.State)
436 case NegotiateState.SentClientHello:
437 this.protocol.EndSendRecord (result);
439 // we are now ready to ready the receive the hello response.
440 negotiate.State = NegotiateState.ReceiveClientHelloResponse;
442 // Start reading the client hello response
443 this.protocol.BeginReceiveRecord (this.innerStream, NegotiateAsyncWorker, negotiate);
446 case NegotiateState.ReceiveClientHelloResponse:
447 this.SafeEndReceiveRecord (result, true);
449 if (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone &&
450 (!this.context.AbbreviatedHandshake || this.context.LastHandshakeMsg != HandshakeType.ServerHello)) {
451 // Read next record (skip empty, e.g. warnings alerts)
452 this.protocol.BeginReceiveRecord (this.innerStream, NegotiateAsyncWorker, negotiate);
456 // special case for abbreviated handshake where no ServerHelloDone is sent from the server
457 if (this.context.AbbreviatedHandshake) {
458 ClientSessionCache.SetContextFromCache (this.context);
459 this.context.Negotiating.Cipher.ComputeKeys ();
460 this.context.Negotiating.Cipher.InitializeCipher ();
462 negotiate.State = NegotiateState.SentCipherSpec;
464 // Send Change Cipher Spec message with the current cipher
465 // or as plain text if this is the initial negotiation
466 this.protocol.BeginSendChangeCipherSpec(NegotiateAsyncWorker, negotiate);
468 // Send client certificate if requested
469 // even if the server ask for it it _may_ still be optional
470 bool clientCertificate = this.context.ServerSettings.CertificateRequest;
472 using (var memstream = new MemoryStream())
474 // NOTE: sadly SSL3 and TLS1 differs in how they handle this and
475 // the current design doesn't allow a very cute way to handle
476 // SSL3 alert warning for NoCertificate (41).
477 if (this.context.SecurityProtocol == SecurityProtocolType.Ssl3)
479 clientCertificate = ((this.context.ClientSettings.Certificates != null) &&
480 (this.context.ClientSettings.Certificates.Count > 0));
481 // this works well with OpenSSL (but only for SSL3)
484 byte[] record = null;
486 if (clientCertificate)
488 record = this.protocol.EncodeHandshakeRecord(HandshakeType.Certificate);
489 memstream.Write(record, 0, record.Length);
492 // Send Client Key Exchange
493 record = this.protocol.EncodeHandshakeRecord(HandshakeType.ClientKeyExchange);
494 memstream.Write(record, 0, record.Length);
496 // Now initialize session cipher with the generated keys
497 this.context.Negotiating.Cipher.InitializeCipher();
499 // Send certificate verify if requested (optional)
500 if (clientCertificate && (this.context.ClientSettings.ClientCertificate != null))
502 record = this.protocol.EncodeHandshakeRecord(HandshakeType.CertificateVerify);
503 memstream.Write(record, 0, record.Length);
506 // send the chnage cipher spec.
507 this.protocol.SendChangeCipherSpec(memstream);
509 // Send Finished message
510 record = this.protocol.EncodeHandshakeRecord(HandshakeType.Finished);
511 memstream.Write(record, 0, record.Length);
513 negotiate.State = NegotiateState.SentKeyExchange;
515 // send all the records.
516 this.innerStream.BeginWrite (memstream.GetBuffer (), 0, (int)memstream.Length, NegotiateAsyncWorker, negotiate);
521 case NegotiateState.SentKeyExchange:
522 this.innerStream.EndWrite (result);
524 negotiate.State = NegotiateState.ReceiveFinishResponse;
526 this.protocol.BeginReceiveRecord (this.innerStream, NegotiateAsyncWorker, negotiate);
530 case NegotiateState.ReceiveFinishResponse:
531 this.SafeEndReceiveRecord (result);
533 // Read record until server finished is received
534 if (this.context.HandshakeState != HandshakeState.Finished) {
535 // If all goes well this will process messages:
536 // Change Cipher Spec
538 this.protocol.BeginReceiveRecord (this.innerStream, NegotiateAsyncWorker, negotiate);
541 // Reset Handshake messages information
542 this.context.HandshakeMessages.Reset ();
545 this.context.ClearKeyInfo();
547 negotiate.SetComplete ();
552 case NegotiateState.SentCipherSpec:
553 this.protocol.EndSendChangeCipherSpec (result);
555 negotiate.State = NegotiateState.ReceiveCipherSpecResponse;
557 // Start reading the cipher spec response
558 this.protocol.BeginReceiveRecord (this.innerStream, NegotiateAsyncWorker, negotiate);
561 case NegotiateState.ReceiveCipherSpecResponse:
562 this.SafeEndReceiveRecord (result, true);
564 if (this.context.HandshakeState != HandshakeState.Finished)
566 this.protocol.BeginReceiveRecord (this.innerStream, NegotiateAsyncWorker, negotiate);
570 negotiate.State = NegotiateState.SentFinished;
571 this.protocol.BeginSendRecord(HandshakeType.Finished, NegotiateAsyncWorker, negotiate);
575 case NegotiateState.SentFinished:
576 this.protocol.EndSendRecord (result);
578 // Reset Handshake messages information
579 this.context.HandshakeMessages.Reset ();
582 this.context.ClearKeyInfo();
584 negotiate.SetComplete ();
589 catch (TlsException ex)
593 this.protocol.SendAlert(ref e);
596 negotiate.SetComplete(new IOException("The authentication or decryption has failed.", ex));
601 this.protocol.SendAlert(AlertDescription.InternalError);
604 negotiate.SetComplete(new IOException("The authentication or decryption has failed.", ex));
610 #region Event Methods
612 internal override X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates, X509Certificate serverCertificate, string targetHost, X509CertificateCollection serverRequestedCertificates)
614 if (this.ClientCertSelection != null)
616 return this.ClientCertSelection(
620 serverRequestedCertificates);
626 internal override bool HaveRemoteValidation2Callback {
627 get { return ServerCertValidation2 != null; }
630 internal override ValidationResult OnRemoteCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection)
632 CertificateValidationCallback2 cb = ServerCertValidation2;
634 return cb (collection);
638 internal override bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors)
640 if (this.ServerCertValidation != null)
642 return this.ServerCertValidation(certificate, errors);
645 return (errors != null && errors.Length == 0);
648 internal virtual bool RaiseServerCertificateValidation(
649 X509Certificate certificate,
650 int[] certificateErrors)
652 return base.RaiseRemoteCertificateValidation(certificate, certificateErrors);
655 internal virtual ValidationResult RaiseServerCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection)
657 return base.RaiseRemoteCertificateValidation2 (collection);
660 internal X509Certificate RaiseClientCertificateSelection(
661 X509CertificateCollection clientCertificates,
662 X509Certificate serverCertificate,
664 X509CertificateCollection serverRequestedCertificates)
666 return base.RaiseLocalCertificateSelection(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates);
669 internal override AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost)
671 if (this.PrivateKeySelection != null)
673 return this.PrivateKeySelection(certificate, targetHost);
679 internal AsymmetricAlgorithm RaisePrivateKeySelection(
680 X509Certificate certificate,
683 return base.RaiseLocalPrivateKeySelection(certificate, targetHost);