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;
84 public override bool CanRead
86 get { return this.innerStream.CanRead; }
89 public override bool CanSeek
94 public override bool CanWrite
96 get { return this.innerStream.CanWrite; }
99 public override long Length
101 get { throw new NotSupportedException(); }
104 public override long Position
106 get { throw new NotSupportedException(); }
107 set { throw new NotSupportedException(); }
110 // required by HttpsClientStream for proxy support
111 internal Stream InputBuffer {
112 get { return inputBuffer; }
116 #region Security Properties
118 public bool CheckCertRevocationStatus
120 get { return this.checkCertRevocationStatus ; }
121 set { this.checkCertRevocationStatus = value; }
124 public CipherAlgorithmType CipherAlgorithm
128 if (this.context.HandshakeState == HandshakeState.Finished)
130 return this.context.Cipher.CipherAlgorithmType;
133 return CipherAlgorithmType.None;
137 public int CipherStrength
141 if (this.context.HandshakeState == HandshakeState.Finished)
143 return this.context.Cipher.EffectiveKeyBits;
150 public X509CertificateCollection ClientCertificates
152 get { return this.context.ClientSettings.Certificates;}
155 public HashAlgorithmType HashAlgorithm
159 if (this.context.HandshakeState == HandshakeState.Finished)
161 return this.context.Cipher.HashAlgorithmType;
164 return HashAlgorithmType.None;
168 public int HashStrength
172 if (this.context.HandshakeState == HandshakeState.Finished)
174 return this.context.Cipher.HashSize * 8;
181 public int KeyExchangeStrength
185 if (this.context.HandshakeState == HandshakeState.Finished)
187 return this.context.ServerSettings.Certificates[0].RSA.KeySize;
194 public ExchangeAlgorithmType KeyExchangeAlgorithm
198 if (this.context.HandshakeState == HandshakeState.Finished)
200 return this.context.Cipher.ExchangeAlgorithmType;
203 return ExchangeAlgorithmType.None;
207 public SecurityProtocolType SecurityProtocol
211 if (this.context.HandshakeState == HandshakeState.Finished)
213 return this.context.SecurityProtocol;
220 public X509Certificate SelectedClientCertificate
222 get { return this.context.ClientSettings.ClientCertificate; }
225 public X509Certificate ServerCertificate
229 if (this.context.HandshakeState == HandshakeState.Finished)
231 if (this.context.ServerSettings.Certificates != null &&
232 this.context.ServerSettings.Certificates.Count > 0)
234 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
242 // this is used by Mono's certmgr tool to download certificates
243 internal Mono.Security.X509.X509CertificateCollection ServerCertificates {
244 get { return context.ServerSettings.Certificates; }
249 #region Callback Properties
251 public CertificateValidationCallback ServerCertValidationDelegate
253 get { return this.ServerCertValidation; }
254 set { this.ServerCertValidation = value; }
257 public CertificateSelectionCallback ClientCertSelectionDelegate
259 get { return this.ClientCertSelection; }
260 set { this.ClientCertSelection = value; }
263 public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate
265 get { return this.PrivateKeySelection; }
266 set { this.PrivateKeySelection = value; }
273 public SslClientStream(
278 stream, targetHost, ownsStream,
279 SecurityProtocolType.Default, null)
283 public SslClientStream(
286 X509Certificate clientCertificate)
288 stream, targetHost, false, SecurityProtocolType.Default,
289 new X509CertificateCollection(new X509Certificate[]{clientCertificate}))
293 public SslClientStream(
296 X509CertificateCollection clientCertificates) :
298 stream, targetHost, false, SecurityProtocolType.Default,
303 public SslClientStream(
307 SecurityProtocolType securityProtocolType)
309 stream, targetHost, ownsStream, securityProtocolType,
310 new X509CertificateCollection())
314 public SslClientStream(
318 SecurityProtocolType securityProtocolType,
319 X509CertificateCollection clientCertificates)
323 throw new ArgumentNullException("stream is null.");
325 if (!stream.CanRead || !stream.CanWrite)
327 throw new ArgumentNullException("stream is not both readable and writable.");
329 if (targetHost == null || targetHost.Length == 0)
331 throw new ArgumentNullException("targetHost is null or an empty string.");
334 this.context = new ClientContext(
336 securityProtocolType,
340 this.inputBuffer = new MemoryStream();
341 this.innerStream = stream;
342 this.ownsStream = ownsStream;
343 this.negotiate = new object ();
344 this.read = new object ();
345 this.write = new object ();
346 this.protocol = new ClientRecordProtocol(innerStream, context);
360 #region IDisposable Methods
362 void IDisposable.Dispose()
365 GC.SuppressFinalize(this);
368 protected virtual void Dispose(bool disposing)
374 if (this.innerStream != null)
376 if (this.context.HandshakeState == HandshakeState.Finished &&
377 !this.context.ConnectionEnd)
379 // Write close notify
380 this.protocol.SendAlert(AlertDescription.CloseNotify);
385 // Close inner stream
386 this.innerStream.Close();
389 this.ownsStream = false;
390 this.innerStream = null;
391 this.ClientCertSelection = null;
392 this.ServerCertValidation = null;
393 this.PrivateKeySelection = null;
396 this.disposed = true;
404 public override IAsyncResult BeginRead(
408 AsyncCallback callback,
411 this.checkDisposed ();
415 throw new ArgumentNullException("buffer is a null reference.");
419 throw new ArgumentOutOfRangeException("offset is less than 0.");
421 if (offset > buffer.Length)
423 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
427 throw new ArgumentOutOfRangeException("count is less than 0.");
429 if (count > (buffer.Length - offset))
431 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
434 if (this.context.HandshakeState == HandshakeState.None)
436 // Note: Async code may have problem if they can't ensure that
437 // the Negotiate phase isn't done during a read operation.
438 // System.Net.HttpWebRequest protects itself from that problem
439 lock (this.negotiate)
441 if (this.context.HandshakeState == HandshakeState.None)
443 this.NegotiateHandshake();
448 IAsyncResult asyncResult = null;
454 // If actual buffer is full readed reset it
455 if (this.inputBuffer.Position == this.inputBuffer.Length &&
456 this.inputBuffer.Length > 0)
461 if (!this.context.ConnectionEnd)
463 if ((this.inputBuffer.Length == this.inputBuffer.Position) && (count > 0))
\r
465 // bigger than max record length for SSL/TLS
\r
466 byte[] recbuf = new byte[16384];
\r
468 // this will read data from the network until we have (at least) one
469 // record to send back to the caller
\r
470 this.innerStream.BeginRead (recbuf, 0, recbuf.Length,
471 new AsyncCallback (NetworkReadCallback), recbuf);
\r
473 if (!recordEvent.WaitOne (300000, false)) // 5 minutes
\r
476 DebugHelper.WriteLine ("TIMEOUT length {0}, position {1}, count {2} - {3}\n{4}",
477 this.inputBuffer.Length, this.inputBuffer.Position, count, GetHashCode (),
478 Environment.StackTrace);
\r
479 throw new TlsException (AlertDescription.InternalError);
\r
484 // return the record(s) to the caller
485 ReadDelegate rd = new ReadDelegate (this.inputBuffer.Read);
486 asyncResult = rd.BeginInvoke (buffer, offset, count, callback, state);
488 catch (TlsException ex)
490 this.protocol.SendAlert(ex.Alert);
493 throw new IOException("The authentication or decryption has failed.");
497 throw new IOException("IO exception during read.");
505 private ManualResetEvent recordEvent = new ManualResetEvent (false);
\r
506 private MemoryStream recordStream = new MemoryStream ();
\r
508 // read encrypted data until we have enough to decrypt (at least) one
509 // record and return are the records (may be more than one) we have
\r
510 private void NetworkReadCallback (IAsyncResult result)
\r
512 byte[] recbuf = (byte[])result.AsyncState;
\r
513 int n = innerStream.EndRead (result);
516 // add the just received data to the waiting data
\r
517 recordStream.Write (recbuf, 0, n);
\r
520 bool dataToReturn = false;
\r
521 long pos = recordStream.Position;
523 recordStream.Position = 0;
\r
524 byte[] record = null;
526 // don't try to decode record unless we have at least 5 bytes
\r
527 // i.e. type (1), protocol (2) and length (2)
\r
528 if (recordStream.Length >= 5)
\r
530 record = this.protocol.ReceiveRecord (recordStream);
\r
533 // a record of 0 length is valid (and there may be more record after it)
534 while (record != null)
\r
536 // we probably received more stuff after the record, and we must keep it!
\r
537 long remainder = recordStream.Length - recordStream.Position;
\r
538 byte[] outofrecord = null;
\r
541 outofrecord = new byte[remainder];
\r
542 recordStream.Read (outofrecord, 0, outofrecord.Length);
\r
545 long position = this.inputBuffer.Position;
\r
547 if (record.Length > 0)
549 // Write new data to the inputBuffer
\r
550 this.inputBuffer.Seek (0, SeekOrigin.End);
\r
551 this.inputBuffer.Write (record, 0, record.Length);
\r
553 // Restore buffer position
\r
554 this.inputBuffer.Seek (position, SeekOrigin.Begin);
558 recordStream.SetLength (0);
\r
563 recordStream.Write (outofrecord, 0, outofrecord.Length);
\r
564 // type (1), protocol (2) and length (2)
\r
565 if (recordStream.Length >= 5)
\r
567 // try to see if another record is available
\r
568 recordStream.Position = 0;
\r
569 record = this.protocol.ReceiveRecord (recordStream);
\r
570 if (record == null)
\r
571 pos = recordStream.Length;
\r
580 if (!dataToReturn && (n > 0))
\r
582 // there is no record to return to caller and (possibly) more data waiting
\r
583 // so continue reading from network (and appending to stream)
\r
584 recordStream.Position = recordStream.Length;
\r
585 this.innerStream.BeginRead (recbuf, 0, recbuf.Length,
586 new AsyncCallback (NetworkReadCallback), recbuf);
\r
590 // we have record(s) to return -or- no more available to read from network
591 // reset position for further reading
\r
592 recordStream.Position = pos;
\r
593 recordEvent.Set ();
\r
597 public override IAsyncResult BeginWrite(
601 AsyncCallback callback,
604 this.checkDisposed();
608 throw new ArgumentNullException("buffer is a null reference.");
612 throw new ArgumentOutOfRangeException("offset is less than 0.");
614 if (offset > buffer.Length)
616 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
620 throw new ArgumentOutOfRangeException("count is less than 0.");
622 if (count > (buffer.Length - offset))
624 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
627 if (this.context.HandshakeState == HandshakeState.None)
629 lock (this.negotiate)
631 if (this.context.HandshakeState == HandshakeState.None)
633 this.NegotiateHandshake();
638 IAsyncResult asyncResult;
644 // Send the buffer as a TLS record
646 byte[] record = this.protocol.EncodeRecord(
647 ContentType.ApplicationData, buffer, offset, count);
649 asyncResult = this.innerStream.BeginWrite(
650 record, 0, record.Length, callback, state);
652 catch (TlsException ex)
654 this.protocol.SendAlert(ex.Alert);
657 throw new IOException("The authentication or decryption has failed.");
661 throw new IOException("IO exception during Write.");
668 public override int EndRead(IAsyncResult asyncResult)
670 this.checkDisposed();
672 if (asyncResult == null)
674 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
677 recordEvent.Reset ();
678 return this.inputBuffer.EndRead (asyncResult);
681 public override void EndWrite(IAsyncResult asyncResult)
683 this.checkDisposed();
685 if (asyncResult == null)
687 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
690 this.innerStream.EndWrite (asyncResult);
693 public override void Close()
695 ((IDisposable)this).Dispose();
698 public override void Flush()
700 this.checkDisposed();
702 this.innerStream.Flush();
705 public int Read(byte[] buffer)
707 return this.Read(buffer, 0, buffer.Length);
710 public override int Read(byte[] buffer, int offset, int count)
712 IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
714 return this.EndRead(res);
717 public override long Seek(long offset, SeekOrigin origin)
719 throw new NotSupportedException();
722 public override void SetLength(long value)
724 throw new NotSupportedException();
727 public void Write(byte[] buffer)
729 this.Write(buffer, 0, buffer.Length);
732 public override void Write(byte[] buffer, int offset, int count)
734 IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null);
743 private void resetBuffer()
745 this.inputBuffer.SetLength(0);
746 this.inputBuffer.Position = 0;
749 private void checkDisposed()
753 throw new ObjectDisposedException("The SslClientStream is closed.");
759 #region Handsake Methods
764 ClientHello -------->
769 <-------- ServerHelloDone
777 Application Data <-------> Application Data
779 Fig. 1 - Message flow for a full handshake
782 internal void NegotiateHandshake()
786 if (this.context.HandshakeState != HandshakeState.None)
788 this.context.Clear();
791 // Obtain supported cipher suites
792 this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
795 this.protocol.SendRecord(HandshakeType.ClientHello);
797 // Read server response
798 while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone)
800 // Read next record
\r
801 this.protocol.ReceiveRecord (this.innerStream);
804 // Send client certificate if requested
805 if (this.context.ServerSettings.CertificateRequest)
807 this.protocol.SendRecord(HandshakeType.Certificate);
810 // Send Client Key Exchange
811 this.protocol.SendRecord(HandshakeType.ClientKeyExchange);
813 // Now initialize session cipher with the generated keys
814 this.context.Cipher.InitializeCipher();
816 // Send certificate verify if requested
817 if (this.context.ServerSettings.CertificateRequest)
819 this.protocol.SendRecord(HandshakeType.CertificateVerify);
822 // Send Cipher Spec protocol
823 this.protocol.SendChangeCipherSpec();
825 // Read record until server finished is received
826 while (this.context.HandshakeState != HandshakeState.Finished)
828 // If all goes well this will process messages:
829 // Change Cipher Spec
831 this.protocol.ReceiveRecord (this.innerStream);
835 this.context.ClearKeyInfo();
837 catch (TlsException ex)
839 this.protocol.SendAlert(ex.Alert);
842 throw new IOException("The authentication or decryption has failed.");
846 this.protocol.SendAlert(AlertDescription.InternalError);
849 throw new IOException("The authentication or decryption has failed.");
855 #region Event Methods
857 internal virtual bool RaiseServerCertificateValidation(
858 X509Certificate certificate,
859 int[] certificateErrors)
861 if (this.ServerCertValidation != null)
863 return this.ServerCertValidation(certificate, certificateErrors);
866 return (certificateErrors != null && certificateErrors.Length == 0);
869 internal X509Certificate RaiseClientCertificateSelection(
870 X509CertificateCollection clientCertificates,
871 X509Certificate serverCertificate,
873 X509CertificateCollection serverRequestedCertificates)
875 if (this.ClientCertSelection != null)
877 return this.ClientCertSelection(
881 serverRequestedCertificates);
887 internal AsymmetricAlgorithm RaisePrivateKeySelection(
888 X509Certificate certificate,
891 if (this.PrivateKeySelection != null)
893 return this.PrivateKeySelection(certificate, targetHost);