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
516 byte[] recbuf = (byte[])result.AsyncState;
\r
517 int n = innerStream.EndRead (result);
520 // add the just received data to the waiting data
\r
521 recordStream.Write (recbuf, 0, n);
\r
524 bool dataToReturn = false;
\r
525 long pos = recordStream.Position;
527 recordStream.Position = 0;
\r
528 byte[] record = null;
530 // don't try to decode record unless we have at least 5 bytes
\r
531 // i.e. type (1), protocol (2) and length (2)
\r
532 if (recordStream.Length >= 5)
\r
534 record = this.protocol.ReceiveRecord (recordStream);
\r
537 // a record of 0 length is valid (and there may be more record after it)
538 while (record != null)
\r
540 // we probably received more stuff after the record, and we must keep it!
\r
541 long remainder = recordStream.Length - recordStream.Position;
\r
542 byte[] outofrecord = null;
\r
545 outofrecord = new byte[remainder];
\r
546 recordStream.Read (outofrecord, 0, outofrecord.Length);
\r
549 long position = this.inputBuffer.Position;
\r
551 if (record.Length > 0)
553 // Write new data to the inputBuffer
\r
554 this.inputBuffer.Seek (0, SeekOrigin.End);
\r
555 this.inputBuffer.Write (record, 0, record.Length);
\r
557 // Restore buffer position
\r
558 this.inputBuffer.Seek (position, SeekOrigin.Begin);
562 recordStream.SetLength (0);
\r
567 recordStream.Write (outofrecord, 0, outofrecord.Length);
\r
568 // type (1), protocol (2) and length (2)
\r
569 if (recordStream.Length >= 5)
\r
571 // try to see if another record is available
\r
572 recordStream.Position = 0;
\r
573 record = this.protocol.ReceiveRecord (recordStream);
\r
574 if (record == null)
\r
575 pos = recordStream.Length;
\r
584 if (!dataToReturn && (n > 0))
\r
586 // there is no record to return to caller and (possibly) more data waiting
\r
587 // so continue reading from network (and appending to stream)
\r
588 recordStream.Position = recordStream.Length;
\r
589 this.innerStream.BeginRead (recbuf, 0, recbuf.Length,
590 new AsyncCallback (NetworkReadCallback), recbuf);
\r
594 // we have record(s) to return -or- no more available to read from network
595 // reset position for further reading
\r
596 recordStream.Position = pos;
\r
597 recordEvent.Set ();
\r
601 public override IAsyncResult BeginWrite(
605 AsyncCallback callback,
608 this.checkDisposed();
612 throw new ArgumentNullException("buffer is a null reference.");
616 throw new ArgumentOutOfRangeException("offset is less than 0.");
618 if (offset > buffer.Length)
620 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
624 throw new ArgumentOutOfRangeException("count is less than 0.");
626 if (count > (buffer.Length - offset))
628 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
631 if (this.context.HandshakeState == HandshakeState.None)
633 lock (this.negotiate)
635 if (this.context.HandshakeState == HandshakeState.None)
637 this.NegotiateHandshake();
642 IAsyncResult asyncResult;
648 // Send the buffer as a TLS record
650 byte[] record = this.protocol.EncodeRecord(
651 ContentType.ApplicationData, buffer, offset, count);
653 asyncResult = this.innerStream.BeginWrite(
654 record, 0, record.Length, callback, state);
656 catch (TlsException ex)
658 this.protocol.SendAlert(ex.Alert);
661 throw new IOException("The authentication or decryption has failed.");
665 throw new IOException("IO exception during Write.");
672 public override int EndRead(IAsyncResult asyncResult)
674 this.checkDisposed();
676 if (asyncResult == null)
678 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
681 recordEvent.Reset ();
682 return this.rd.EndInvoke (asyncResult);
685 public override void EndWrite(IAsyncResult asyncResult)
687 this.checkDisposed();
689 if (asyncResult == null)
691 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
694 this.innerStream.EndWrite (asyncResult);
697 public override void Close()
699 ((IDisposable)this).Dispose();
702 public override void Flush()
704 this.checkDisposed();
706 this.innerStream.Flush();
709 public int Read(byte[] buffer)
711 return this.Read(buffer, 0, buffer.Length);
714 public override int Read(byte[] buffer, int offset, int count)
716 IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
718 return this.EndRead(res);
721 public override long Seek(long offset, SeekOrigin origin)
723 throw new NotSupportedException();
726 public override void SetLength(long value)
728 throw new NotSupportedException();
731 public void Write(byte[] buffer)
733 this.Write(buffer, 0, buffer.Length);
736 public override void Write(byte[] buffer, int offset, int count)
738 IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null);
747 private void resetBuffer()
749 this.inputBuffer.SetLength(0);
750 this.inputBuffer.Position = 0;
753 private void checkDisposed()
757 throw new ObjectDisposedException("The SslClientStream is closed.");
763 #region Handsake Methods
768 ClientHello -------->
773 <-------- ServerHelloDone
781 Application Data <-------> Application Data
783 Fig. 1 - Message flow for a full handshake
786 internal void NegotiateHandshake()
790 if (this.context.HandshakeState != HandshakeState.None)
792 this.context.Clear();
795 // Obtain supported cipher suites
796 this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
799 this.protocol.SendRecord(HandshakeType.ClientHello);
801 // Read server response
802 while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone)
804 // Read next record
\r
805 this.protocol.ReceiveRecord (this.innerStream);
808 // Send client certificate if requested
809 if (this.context.ServerSettings.CertificateRequest)
811 this.protocol.SendRecord(HandshakeType.Certificate);
814 // Send Client Key Exchange
815 this.protocol.SendRecord(HandshakeType.ClientKeyExchange);
817 // Now initialize session cipher with the generated keys
818 this.context.Cipher.InitializeCipher();
820 // Send certificate verify if requested
821 if (this.context.ServerSettings.CertificateRequest)
823 this.protocol.SendRecord(HandshakeType.CertificateVerify);
826 // Send Cipher Spec protocol
827 this.protocol.SendChangeCipherSpec();
829 // Read record until server finished is received
830 while (this.context.HandshakeState != HandshakeState.Finished)
832 // If all goes well this will process messages:
833 // Change Cipher Spec
835 this.protocol.ReceiveRecord (this.innerStream);
839 this.context.ClearKeyInfo();
841 catch (TlsException ex)
843 this.protocol.SendAlert(ex.Alert);
846 throw new IOException("The authentication or decryption has failed.");
850 this.protocol.SendAlert(AlertDescription.InternalError);
853 throw new IOException("The authentication or decryption has failed.");
859 #region Event Methods
861 internal virtual bool RaiseServerCertificateValidation(
862 X509Certificate certificate,
863 int[] certificateErrors)
865 if (this.ServerCertValidation != null)
867 return this.ServerCertValidation(certificate, certificateErrors);
870 return (certificateErrors != null && certificateErrors.Length == 0);
873 internal X509Certificate RaiseClientCertificateSelection(
874 X509CertificateCollection clientCertificates,
875 X509Certificate serverCertificate,
877 X509CertificateCollection serverRequestedCertificates)
879 if (this.ClientCertSelection != null)
881 return this.ClientCertSelection(
885 serverRequestedCertificates);
891 internal AsymmetricAlgorithm RaisePrivateKeySelection(
892 X509Certificate certificate,
895 if (this.PrivateKeySelection != null)
897 return this.PrivateKeySelection(certificate, targetHost);