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,
54 delegate int ReadDelegate (byte [] buffer, int offset, int count);
57 public class SslClientStream : Stream, IDisposable
59 #region Internal Events
61 internal event CertificateValidationCallback ServerCertValidation;
62 internal event CertificateSelectionCallback ClientCertSelection;
63 internal event PrivateKeySelectionCallback PrivateKeySelection;
69 private Stream innerStream;
70 private MemoryStream inputBuffer;
71 private ClientContext context;
72 private ClientRecordProtocol protocol;
73 private bool ownsStream;
74 private bool disposed;
75 private bool checkCertRevocationStatus;
76 private object negotiate;
79 private ReadDelegate rd;
85 public override bool CanRead
87 get { return this.innerStream.CanRead; }
90 public override bool CanSeek
95 public override bool CanWrite
97 get { return this.innerStream.CanWrite; }
100 public override long Length
102 get { throw new NotSupportedException(); }
105 public override long Position
107 get { throw new NotSupportedException(); }
108 set { throw new NotSupportedException(); }
111 // required by HttpsClientStream for proxy support
112 internal Stream InputBuffer {
113 get { return inputBuffer; }
117 #region Security Properties
119 public bool CheckCertRevocationStatus
121 get { return this.checkCertRevocationStatus ; }
122 set { this.checkCertRevocationStatus = value; }
125 public CipherAlgorithmType CipherAlgorithm
129 if (this.context.HandshakeState == HandshakeState.Finished)
131 return this.context.Cipher.CipherAlgorithmType;
134 return CipherAlgorithmType.None;
138 public int CipherStrength
142 if (this.context.HandshakeState == HandshakeState.Finished)
144 return this.context.Cipher.EffectiveKeyBits;
151 public X509CertificateCollection ClientCertificates
153 get { return this.context.ClientSettings.Certificates;}
156 public HashAlgorithmType HashAlgorithm
160 if (this.context.HandshakeState == HandshakeState.Finished)
162 return this.context.Cipher.HashAlgorithmType;
165 return HashAlgorithmType.None;
169 public int HashStrength
173 if (this.context.HandshakeState == HandshakeState.Finished)
175 return this.context.Cipher.HashSize * 8;
182 public int KeyExchangeStrength
186 if (this.context.HandshakeState == HandshakeState.Finished)
188 return this.context.ServerSettings.Certificates[0].RSA.KeySize;
195 public ExchangeAlgorithmType KeyExchangeAlgorithm
199 if (this.context.HandshakeState == HandshakeState.Finished)
201 return this.context.Cipher.ExchangeAlgorithmType;
204 return ExchangeAlgorithmType.None;
208 public SecurityProtocolType SecurityProtocol
212 if (this.context.HandshakeState == HandshakeState.Finished)
214 return this.context.SecurityProtocol;
221 public X509Certificate SelectedClientCertificate
223 get { return this.context.ClientSettings.ClientCertificate; }
226 public X509Certificate ServerCertificate
230 if (this.context.HandshakeState == HandshakeState.Finished)
232 if (this.context.ServerSettings.Certificates != null &&
233 this.context.ServerSettings.Certificates.Count > 0)
235 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
243 // this is used by Mono's certmgr tool to download certificates
244 internal Mono.Security.X509.X509CertificateCollection ServerCertificates {
245 get { return context.ServerSettings.Certificates; }
250 #region Callback Properties
252 public CertificateValidationCallback ServerCertValidationDelegate
254 get { return this.ServerCertValidation; }
255 set { this.ServerCertValidation = value; }
258 public CertificateSelectionCallback ClientCertSelectionDelegate
260 get { return this.ClientCertSelection; }
261 set { this.ClientCertSelection = value; }
264 public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate
266 get { return this.PrivateKeySelection; }
267 set { this.PrivateKeySelection = value; }
274 public SslClientStream(
279 stream, targetHost, ownsStream,
280 SecurityProtocolType.Default, null)
284 public SslClientStream(
287 X509Certificate clientCertificate)
289 stream, targetHost, false, SecurityProtocolType.Default,
290 new X509CertificateCollection(new X509Certificate[]{clientCertificate}))
294 public SslClientStream(
297 X509CertificateCollection clientCertificates) :
299 stream, targetHost, false, SecurityProtocolType.Default,
304 public SslClientStream(
308 SecurityProtocolType securityProtocolType)
310 stream, targetHost, ownsStream, securityProtocolType,
311 new X509CertificateCollection())
315 public SslClientStream(
319 SecurityProtocolType securityProtocolType,
320 X509CertificateCollection clientCertificates)
324 throw new ArgumentNullException("stream is null.");
326 if (!stream.CanRead || !stream.CanWrite)
328 throw new ArgumentNullException("stream is not both readable and writable.");
330 if (targetHost == null || targetHost.Length == 0)
332 throw new ArgumentNullException("targetHost is null or an empty string.");
335 this.context = new ClientContext(
337 securityProtocolType,
341 this.inputBuffer = new MemoryStream();
342 this.innerStream = stream;
343 this.ownsStream = ownsStream;
344 this.negotiate = new object ();
345 this.read = new object ();
346 this.write = new object ();
347 this.protocol = new ClientRecordProtocol(innerStream, context);
361 #region IDisposable Methods
363 void IDisposable.Dispose()
366 GC.SuppressFinalize(this);
369 protected virtual void Dispose(bool disposing)
375 if (this.innerStream != null)
377 if (this.context.HandshakeState == HandshakeState.Finished &&
378 !this.context.ConnectionEnd)
380 // Write close notify
381 this.protocol.SendAlert(AlertDescription.CloseNotify);
386 // Close inner stream
387 this.innerStream.Close();
390 this.ownsStream = false;
391 this.innerStream = null;
392 this.ClientCertSelection = null;
393 this.ServerCertValidation = null;
394 this.PrivateKeySelection = null;
397 this.disposed = true;
405 public override IAsyncResult BeginRead(
409 AsyncCallback callback,
412 this.checkDisposed ();
416 throw new ArgumentNullException("buffer is a null reference.");
420 throw new ArgumentOutOfRangeException("offset is less than 0.");
422 if (offset > buffer.Length)
424 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
428 throw new ArgumentOutOfRangeException("count is less than 0.");
430 if (count > (buffer.Length - offset))
432 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
435 if (this.context.HandshakeState == HandshakeState.None)
437 // Note: Async code may have problem if they can't ensure that
438 // the Negotiate phase isn't done during a read operation.
439 // System.Net.HttpWebRequest protects itself from that problem
440 lock (this.negotiate)
442 if (this.context.HandshakeState == HandshakeState.None)
444 this.NegotiateHandshake();
449 IAsyncResult asyncResult = null;
455 // If actual buffer is full readed reset it
456 if (this.inputBuffer.Position == this.inputBuffer.Length &&
457 this.inputBuffer.Length > 0)
462 if (!this.context.ConnectionEnd)
464 if ((this.inputBuffer.Length == this.inputBuffer.Position) && (count > 0))
\r
466 // bigger than max record length for SSL/TLS
\r
467 byte[] recbuf = new byte[16384];
\r
469 // this will read data from the network until we have (at least) one
470 // record to send back to the caller
\r
471 this.innerStream.BeginRead (recbuf, 0, recbuf.Length,
472 new AsyncCallback (NetworkReadCallback), recbuf);
\r
474 if (!recordEvent.WaitOne (300000, false)) // 5 minutes
\r
477 DebugHelper.WriteLine ("TIMEOUT length {0}, position {1}, count {2} - {3}\n{4}",
478 this.inputBuffer.Length, this.inputBuffer.Position, count, GetHashCode (),
479 Environment.StackTrace);
\r
480 throw new TlsException (AlertDescription.InternalError);
\r
485 // return the record(s) to the caller
486 rd = new ReadDelegate (this.inputBuffer.Read);
487 asyncResult = rd.BeginInvoke (buffer, offset, count, callback, state);
489 catch (TlsException ex)
491 this.protocol.SendAlert(ex.Alert);
494 throw new IOException("The authentication or decryption has failed.");
498 throw new IOException("IO exception during read.");
506 private ManualResetEvent recordEvent = new ManualResetEvent (false);
\r
507 private MemoryStream recordStream = new MemoryStream ();
\r
509 // read encrypted data until we have enough to decrypt (at least) one
510 // record and return are the records (may be more than one) we have
\r
511 private void NetworkReadCallback (IAsyncResult result)
\r
513 byte[] recbuf = (byte[])result.AsyncState;
\r
514 int n = innerStream.EndRead (result);
517 // add the just received data to the waiting data
\r
518 recordStream.Write (recbuf, 0, n);
\r
521 bool dataToReturn = false;
\r
522 long pos = recordStream.Position;
524 recordStream.Position = 0;
\r
525 byte[] record = null;
527 // don't try to decode record unless we have at least 5 bytes
\r
528 // i.e. type (1), protocol (2) and length (2)
\r
529 if (recordStream.Length >= 5)
\r
531 record = this.protocol.ReceiveRecord (recordStream);
\r
534 // a record of 0 length is valid (and there may be more record after it)
535 while (record != null)
\r
537 // we probably received more stuff after the record, and we must keep it!
\r
538 long remainder = recordStream.Length - recordStream.Position;
\r
539 byte[] outofrecord = null;
\r
542 outofrecord = new byte[remainder];
\r
543 recordStream.Read (outofrecord, 0, outofrecord.Length);
\r
546 long position = this.inputBuffer.Position;
\r
548 if (record.Length > 0)
550 // Write new data to the inputBuffer
\r
551 this.inputBuffer.Seek (0, SeekOrigin.End);
\r
552 this.inputBuffer.Write (record, 0, record.Length);
\r
554 // Restore buffer position
\r
555 this.inputBuffer.Seek (position, SeekOrigin.Begin);
559 recordStream.SetLength (0);
\r
564 recordStream.Write (outofrecord, 0, outofrecord.Length);
\r
565 // type (1), protocol (2) and length (2)
\r
566 if (recordStream.Length >= 5)
\r
568 // try to see if another record is available
\r
569 recordStream.Position = 0;
\r
570 record = this.protocol.ReceiveRecord (recordStream);
\r
571 if (record == null)
\r
572 pos = recordStream.Length;
\r
581 if (!dataToReturn && (n > 0))
\r
583 // there is no record to return to caller and (possibly) more data waiting
\r
584 // so continue reading from network (and appending to stream)
\r
585 recordStream.Position = recordStream.Length;
\r
586 this.innerStream.BeginRead (recbuf, 0, recbuf.Length,
587 new AsyncCallback (NetworkReadCallback), recbuf);
\r
591 // we have record(s) to return -or- no more available to read from network
592 // reset position for further reading
\r
593 recordStream.Position = pos;
\r
594 recordEvent.Set ();
\r
598 public override IAsyncResult BeginWrite(
602 AsyncCallback callback,
605 this.checkDisposed();
609 throw new ArgumentNullException("buffer is a null reference.");
613 throw new ArgumentOutOfRangeException("offset is less than 0.");
615 if (offset > buffer.Length)
617 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
621 throw new ArgumentOutOfRangeException("count is less than 0.");
623 if (count > (buffer.Length - offset))
625 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
628 if (this.context.HandshakeState == HandshakeState.None)
630 lock (this.negotiate)
632 if (this.context.HandshakeState == HandshakeState.None)
634 this.NegotiateHandshake();
639 IAsyncResult asyncResult;
645 // Send the buffer as a TLS record
647 byte[] record = this.protocol.EncodeRecord(
648 ContentType.ApplicationData, buffer, offset, count);
650 asyncResult = this.innerStream.BeginWrite(
651 record, 0, record.Length, callback, state);
653 catch (TlsException ex)
655 this.protocol.SendAlert(ex.Alert);
658 throw new IOException("The authentication or decryption has failed.");
662 throw new IOException("IO exception during Write.");
669 public override int EndRead(IAsyncResult asyncResult)
671 this.checkDisposed();
673 if (asyncResult == null)
675 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
678 recordEvent.Reset ();
679 return this.rd.EndInvoke (asyncResult);
682 public override void EndWrite(IAsyncResult asyncResult)
684 this.checkDisposed();
686 if (asyncResult == null)
688 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
691 this.innerStream.EndWrite (asyncResult);
694 public override void Close()
696 ((IDisposable)this).Dispose();
699 public override void Flush()
701 this.checkDisposed();
703 this.innerStream.Flush();
706 public int Read(byte[] buffer)
708 return this.Read(buffer, 0, buffer.Length);
711 public override int Read(byte[] buffer, int offset, int count)
713 IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
715 return this.EndRead(res);
718 public override long Seek(long offset, SeekOrigin origin)
720 throw new NotSupportedException();
723 public override void SetLength(long value)
725 throw new NotSupportedException();
728 public void Write(byte[] buffer)
730 this.Write(buffer, 0, buffer.Length);
733 public override void Write(byte[] buffer, int offset, int count)
735 IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null);
744 private void resetBuffer()
746 this.inputBuffer.SetLength(0);
747 this.inputBuffer.Position = 0;
750 private void checkDisposed()
754 throw new ObjectDisposedException("The SslClientStream is closed.");
760 #region Handsake Methods
765 ClientHello -------->
770 <-------- ServerHelloDone
778 Application Data <-------> Application Data
780 Fig. 1 - Message flow for a full handshake
783 internal void NegotiateHandshake()
787 if (this.context.HandshakeState != HandshakeState.None)
789 this.context.Clear();
792 // Obtain supported cipher suites
793 this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
796 this.protocol.SendRecord(HandshakeType.ClientHello);
798 // Read server response
799 while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone)
801 // Read next record
\r
802 this.protocol.ReceiveRecord (this.innerStream);
805 // Send client certificate if requested
806 if (this.context.ServerSettings.CertificateRequest)
808 this.protocol.SendRecord(HandshakeType.Certificate);
811 // Send Client Key Exchange
812 this.protocol.SendRecord(HandshakeType.ClientKeyExchange);
814 // Now initialize session cipher with the generated keys
815 this.context.Cipher.InitializeCipher();
817 // Send certificate verify if requested
818 if (this.context.ServerSettings.CertificateRequest)
820 this.protocol.SendRecord(HandshakeType.CertificateVerify);
823 // Send Cipher Spec protocol
824 this.protocol.SendChangeCipherSpec();
826 // Read record until server finished is received
827 while (this.context.HandshakeState != HandshakeState.Finished)
829 // If all goes well this will process messages:
830 // Change Cipher Spec
832 this.protocol.ReceiveRecord (this.innerStream);
836 this.context.ClearKeyInfo();
838 catch (TlsException ex)
840 this.protocol.SendAlert(ex.Alert);
843 throw new IOException("The authentication or decryption has failed.");
847 this.protocol.SendAlert(AlertDescription.InternalError);
850 throw new IOException("The authentication or decryption has failed.");
856 #region Event Methods
858 internal virtual bool RaiseServerCertificateValidation(
859 X509Certificate certificate,
860 int[] certificateErrors)
862 if (this.ServerCertValidation != null)
864 return this.ServerCertValidation(certificate, certificateErrors);
867 return (certificateErrors != null && certificateErrors.Length == 0);
870 internal X509Certificate RaiseClientCertificateSelection(
871 X509CertificateCollection clientCertificates,
872 X509Certificate serverCertificate,
874 X509CertificateCollection serverRequestedCertificates)
876 if (this.ClientCertSelection != null)
878 return this.ClientCertSelection(
882 serverRequestedCertificates);
888 internal AsymmetricAlgorithm RaisePrivateKeySelection(
889 X509Certificate certificate,
892 if (this.PrivateKeySelection != null)
894 return this.PrivateKeySelection(certificate, targetHost);