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;
33 using Mono.Security.Protocol.Tls.Handshake;
35 namespace Mono.Security.Protocol.Tls
39 public delegate bool CertificateValidationCallback(
40 X509Certificate certificate,
41 int[] certificateErrors);
43 public delegate X509Certificate CertificateSelectionCallback(
44 X509CertificateCollection clientCertificates,
45 X509Certificate serverCertificate,
47 X509CertificateCollection serverRequestedCertificates);
49 public delegate AsymmetricAlgorithm PrivateKeySelectionCallback(
50 X509Certificate certificate,
55 public class SslClientStream : Stream, IDisposable
57 #region Internal Events
59 internal event CertificateValidationCallback ServerCertValidation;
60 internal event CertificateSelectionCallback ClientCertSelection;
61 internal event PrivateKeySelectionCallback PrivateKeySelection;
67 private Stream innerStream;
68 private BufferedStream inputBuffer;
69 private ClientContext context;
70 private ClientRecordProtocol protocol;
71 private bool ownsStream;
72 private bool disposed;
73 private bool checkCertRevocationStatus;
81 public override bool CanRead
83 get { return this.innerStream.CanRead; }
86 public override bool CanSeek
91 public override bool CanWrite
93 get { return this.innerStream.CanWrite; }
96 public override long Length
98 get { throw new NotSupportedException(); }
101 public override long Position
103 get { throw new NotSupportedException(); }
104 set { throw new NotSupportedException(); }
107 // required by HttpsClientStream for proxy support
108 internal Stream InputBuffer {
109 get { return inputBuffer; }
113 #region Security Properties
115 public bool CheckCertRevocationStatus
117 get { return this.checkCertRevocationStatus ; }
118 set { this.checkCertRevocationStatus = value; }
121 public CipherAlgorithmType CipherAlgorithm
125 if (this.context.HandshakeState == HandshakeState.Finished)
127 return this.context.Cipher.CipherAlgorithmType;
130 return CipherAlgorithmType.None;
134 public int CipherStrength
138 if (this.context.HandshakeState == HandshakeState.Finished)
140 return this.context.Cipher.EffectiveKeyBits;
147 public X509CertificateCollection ClientCertificates
149 get { return this.context.ClientSettings.Certificates;}
152 public HashAlgorithmType HashAlgorithm
156 if (this.context.HandshakeState == HandshakeState.Finished)
158 return this.context.Cipher.HashAlgorithmType;
161 return HashAlgorithmType.None;
165 public int HashStrength
169 if (this.context.HandshakeState == HandshakeState.Finished)
171 return this.context.Cipher.HashSize * 8;
178 public int KeyExchangeStrength
182 if (this.context.HandshakeState == HandshakeState.Finished)
184 return this.context.ServerSettings.Certificates[0].RSA.KeySize;
191 public ExchangeAlgorithmType KeyExchangeAlgorithm
195 if (this.context.HandshakeState == HandshakeState.Finished)
197 return this.context.Cipher.ExchangeAlgorithmType;
200 return ExchangeAlgorithmType.None;
204 public SecurityProtocolType SecurityProtocol
208 if (this.context.HandshakeState == HandshakeState.Finished)
210 return this.context.SecurityProtocol;
217 public X509Certificate SelectedClientCertificate
219 get { return this.context.ClientSettings.ClientCertificate; }
222 public X509Certificate ServerCertificate
226 if (this.context.HandshakeState == HandshakeState.Finished)
228 if (this.context.ServerSettings.Certificates != null &&
229 this.context.ServerSettings.Certificates.Count > 0)
231 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
241 #region Callback Properties
243 public CertificateValidationCallback ServerCertValidationDelegate
245 get { return this.ServerCertValidation; }
246 set { this.ServerCertValidation = value; }
249 public CertificateSelectionCallback ClientCertSelectionDelegate
251 get { return this.ClientCertSelection; }
252 set { this.ClientCertSelection = value; }
255 public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate
257 get { return this.PrivateKeySelection; }
258 set { this.PrivateKeySelection = value; }
265 public SslClientStream(
270 stream, targetHost, ownsStream,
271 SecurityProtocolType.Default, null)
275 public SslClientStream(
278 X509Certificate clientCertificate)
280 stream, targetHost, false, SecurityProtocolType.Default,
281 new X509CertificateCollection(new X509Certificate[]{clientCertificate}))
285 public SslClientStream(
288 X509CertificateCollection clientCertificates) :
290 stream, targetHost, false, SecurityProtocolType.Default,
295 public SslClientStream(
299 SecurityProtocolType securityProtocolType)
301 stream, targetHost, ownsStream, securityProtocolType,
302 new X509CertificateCollection())
306 public SslClientStream(
310 SecurityProtocolType securityProtocolType,
311 X509CertificateCollection clientCertificates)
315 throw new ArgumentNullException("stream is null.");
317 if (!stream.CanRead || !stream.CanWrite)
319 throw new ArgumentNullException("stream is not both readable and writable.");
321 if (targetHost == null || targetHost.Length == 0)
323 throw new ArgumentNullException("targetHost is null or an empty string.");
326 this.context = new ClientContext(
328 securityProtocolType,
332 this.inputBuffer = new BufferedStream(new MemoryStream());
333 this.innerStream = stream;
334 this.ownsStream = ownsStream;
335 this.read = new object ();
336 this.write = new object ();
337 this.protocol = new ClientRecordProtocol(innerStream, context);
351 #region IDisposable Methods
353 void IDisposable.Dispose()
356 GC.SuppressFinalize(this);
359 protected virtual void Dispose(bool disposing)
365 if (this.innerStream != null)
367 if (this.context.HandshakeState == HandshakeState.Finished &&
368 !this.context.ConnectionEnd)
370 // Write close notify
371 this.protocol.SendAlert(AlertDescription.CloseNotify);
376 // Close inner stream
377 this.innerStream.Close();
380 this.ownsStream = false;
381 this.innerStream = null;
382 this.ClientCertSelection = null;
383 this.ServerCertValidation = null;
384 this.PrivateKeySelection = null;
387 this.disposed = true;
395 public override IAsyncResult BeginRead(
399 AsyncCallback callback,
402 this.checkDisposed();
406 throw new ArgumentNullException("buffer is a null reference.");
410 throw new ArgumentOutOfRangeException("offset is less than 0.");
412 if (offset > buffer.Length)
414 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
418 throw new ArgumentOutOfRangeException("count is less than 0.");
420 if (count > (buffer.Length - offset))
422 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
427 if (this.context.HandshakeState == HandshakeState.None)
429 this.NegotiateHandshake();
433 IAsyncResult asyncResult;
439 // If actual buffer is full readed reset it
440 if (this.inputBuffer.Position == this.inputBuffer.Length &&
441 this.inputBuffer.Length > 0)
446 if (!this.context.ConnectionEnd)
448 // Check if we have space in the middle buffer
449 // if not Read next TLS record and update the inputBuffer
450 while ((this.inputBuffer.Length - this.inputBuffer.Position) < count)
452 // Read next record and write it into the inputBuffer
453 long position = this.inputBuffer.Position;
454 byte[] record = this.protocol.ReceiveRecord();
456 if (record != null && record.Length > 0)
458 // Write new data to the inputBuffer
459 this.inputBuffer.Seek(0, SeekOrigin.End);
460 this.inputBuffer.Write(record, 0, record.Length);
462 // Restore buffer position
463 this.inputBuffer.Seek(position, SeekOrigin.Begin);
473 // TODO: Review if we need to check the Length
474 // property of the innerStream for other types
475 // of streams, to check that there are data available
477 if (this.innerStream is NetworkStream &&
478 !((NetworkStream)this.innerStream).DataAvailable)
485 asyncResult = this.inputBuffer.BeginRead(
486 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.");
504 public override IAsyncResult BeginWrite(
508 AsyncCallback callback,
511 this.checkDisposed();
515 throw new ArgumentNullException("buffer is a null reference.");
519 throw new ArgumentOutOfRangeException("offset is less than 0.");
521 if (offset > buffer.Length)
523 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
527 throw new ArgumentOutOfRangeException("count is less than 0.");
529 if (count > (buffer.Length - offset))
531 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
536 if (this.context.HandshakeState == HandshakeState.None)
538 this.NegotiateHandshake();
542 IAsyncResult asyncResult;
548 // Send the buffer as a TLS record
550 byte[] record = this.protocol.EncodeRecord(
551 ContentType.ApplicationData, buffer, offset, count);
553 asyncResult = this.innerStream.BeginWrite(
554 record, 0, record.Length, callback, state);
556 catch (TlsException ex)
558 this.protocol.SendAlert(ex.Alert);
561 throw new IOException("The authentication or decryption has failed.");
565 throw new IOException("IO exception during Write.");
572 public override int EndRead(IAsyncResult asyncResult)
574 this.checkDisposed();
576 if (asyncResult == null)
578 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
581 return this.inputBuffer.EndRead(asyncResult);
584 public override void EndWrite(IAsyncResult asyncResult)
586 this.checkDisposed();
588 if (asyncResult == null)
590 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
593 this.innerStream.EndWrite (asyncResult);
596 public override void Close()
598 ((IDisposable)this).Dispose();
601 public override void Flush()
603 this.checkDisposed();
605 this.innerStream.Flush();
608 public int Read(byte[] buffer)
610 return this.Read(buffer, 0, buffer.Length);
613 public override int Read(byte[] buffer, int offset, int count)
615 IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
617 return this.EndRead(res);
620 public override long Seek(long offset, SeekOrigin origin)
622 throw new NotSupportedException();
625 public override void SetLength(long value)
627 throw new NotSupportedException();
630 public void Write(byte[] buffer)
632 this.Write(buffer, 0, buffer.Length);
635 public override void Write(byte[] buffer, int offset, int count)
637 IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null);
646 private void resetBuffer()
648 this.inputBuffer.SetLength(0);
649 this.inputBuffer.Position = 0;
652 private void checkDisposed()
656 throw new ObjectDisposedException("The SslClientStream is closed.");
662 #region Handsake Methods
667 ClientHello -------->
672 <-------- ServerHelloDone
680 Application Data <-------> Application Data
682 Fig. 1 - Message flow for a full handshake
685 internal void NegotiateHandshake()
691 if (this.context.HandshakeState != HandshakeState.None)
693 this.context.Clear();
696 // Obtain supported cipher suites
697 this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
700 this.protocol.SendRecord(HandshakeType.ClientHello);
702 // Read server response
703 while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone)
706 this.protocol.ReceiveRecord();
709 // Send client certificate if requested
710 if (this.context.ServerSettings.CertificateRequest)
712 this.protocol.SendRecord(HandshakeType.Certificate);
715 // Send Client Key Exchange
716 this.protocol.SendRecord(HandshakeType.ClientKeyExchange);
718 // Now initialize session cipher with the generated keys
719 this.context.Cipher.InitializeCipher();
721 // Send certificate verify if requested
722 if (this.context.ServerSettings.CertificateRequest)
724 this.protocol.SendRecord(HandshakeType.CertificateVerify);
727 // Send Cipher Spec protocol
728 this.protocol.SendChangeCipherSpec();
730 // Read record until server finished is received
731 while (this.context.HandshakeState != HandshakeState.Finished)
733 // If all goes well this will process messages:
734 // Change Cipher Spec
736 this.protocol.ReceiveRecord();
740 this.context.ClearKeyInfo();
742 catch (TlsException ex)
744 this.protocol.SendAlert(ex.Alert);
747 throw new IOException("The authentication or decryption has failed.");
751 this.protocol.SendAlert(AlertDescription.InternalError);
754 throw new IOException("The authentication or decryption has failed.");
761 #region Event Methods
763 internal virtual bool RaiseServerCertificateValidation(
764 X509Certificate certificate,
765 int[] certificateErrors)
767 if (this.ServerCertValidation != null)
769 return this.ServerCertValidation(certificate, certificateErrors);
772 return (certificateErrors != null && certificateErrors.Length == 0);
775 internal X509Certificate RaiseClientCertificateSelection(
776 X509CertificateCollection clientCertificates,
777 X509Certificate serverCertificate,
779 X509CertificateCollection serverRequestedCertificates)
781 if (this.ClientCertSelection != null)
783 return this.ClientCertSelection(
787 serverRequestedCertificates);
793 internal AsymmetricAlgorithm RaisePrivateKeySelection(
794 X509Certificate certificate,
797 if (this.PrivateKeySelection != null)
799 return this.PrivateKeySelection(certificate, targetHost);