3 // Permission is hereby granted, free of charge, to any person obtaining
4 // a copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to
8 // permit persons to whom the Software is furnished to do so, subject to
9 // the following conditions:
11 // The above copyright notice and this permission notice shall be
12 // included in all copies or substantial portions of the Software.
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 /* Transport Security Layer (TLS)
23 * Copyright (c) 2003-2004 Carlos Guzman Alvarez
25 * Permission is hereby granted, free of charge, to any person
26 * obtaining a copy of this software and associated documentation
27 * files (the "Software"), to deal in the Software without restriction,
28 * including without limitation the rights to use, copy, modify, merge,
29 * publish, distribute, sublicense, and/or sell copies of the Software,
30 * and to permit persons to whom the Software is furnished to do so,
31 * subject to the following conditions:
33 * The above copyright notice and this permission notice shall be included
34 * in all copies or substantial portions of the Software.
36 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
37 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
38 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
39 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
40 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
41 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
42 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
43 * DEALINGS IN THE SOFTWARE.
47 using System.Collections;
50 using System.Net.Sockets;
51 using System.Security.Cryptography;
52 using System.Security.Cryptography.X509Certificates;
54 using Mono.Security.Protocol.Tls.Handshake;
56 namespace Mono.Security.Protocol.Tls
60 public delegate bool CertificateValidationCallback(
61 X509Certificate certificate,
62 int[] certificateErrors);
64 public delegate X509Certificate CertificateSelectionCallback(
65 X509CertificateCollection clientCertificates,
66 X509Certificate serverCertificate,
68 X509CertificateCollection serverRequestedCertificates);
70 public delegate AsymmetricAlgorithm PrivateKeySelectionCallback(
71 X509Certificate certificate,
76 public class SslClientStream : Stream, IDisposable
78 #region Internal Events
80 internal event CertificateValidationCallback ServerCertValidation;
81 internal event CertificateSelectionCallback ClientCertSelection;
82 internal event PrivateKeySelectionCallback PrivateKeySelection;
88 private Stream innerStream;
89 private BufferedStream inputBuffer;
90 private ClientContext context;
91 private ClientRecordProtocol protocol;
92 private bool ownsStream;
93 private bool disposed;
94 private bool checkCertRevocationStatus;
102 public override bool CanRead
104 get { return this.innerStream.CanRead; }
107 public override bool CanSeek
109 get { return false; }
112 public override bool CanWrite
114 get { return this.innerStream.CanWrite; }
117 public override long Length
119 get { throw new NotSupportedException(); }
122 public override long Position
124 get { throw new NotSupportedException(); }
125 set { throw new NotSupportedException(); }
128 // required by HttpsClientStream for proxy support
129 internal Stream InputBuffer {
130 get { return inputBuffer; }
134 #region Security Properties
136 public bool CheckCertRevocationStatus
138 get { return this.checkCertRevocationStatus ; }
139 set { this.checkCertRevocationStatus = value; }
142 public CipherAlgorithmType CipherAlgorithm
146 if (this.context.HandshakeState == HandshakeState.Finished)
148 return this.context.Cipher.CipherAlgorithmType;
151 return CipherAlgorithmType.None;
155 public int CipherStrength
159 if (this.context.HandshakeState == HandshakeState.Finished)
161 return this.context.Cipher.EffectiveKeyBits;
168 public X509CertificateCollection ClientCertificates
170 get { return this.context.ClientSettings.Certificates;}
173 public HashAlgorithmType HashAlgorithm
177 if (this.context.HandshakeState == HandshakeState.Finished)
179 return this.context.Cipher.HashAlgorithmType;
182 return HashAlgorithmType.None;
186 public int HashStrength
190 if (this.context.HandshakeState == HandshakeState.Finished)
192 return this.context.Cipher.HashSize * 8;
199 public int KeyExchangeStrength
203 if (this.context.HandshakeState == HandshakeState.Finished)
205 return this.context.ServerSettings.Certificates[0].RSA.KeySize;
212 public ExchangeAlgorithmType KeyExchangeAlgorithm
216 if (this.context.HandshakeState == HandshakeState.Finished)
218 return this.context.Cipher.ExchangeAlgorithmType;
221 return ExchangeAlgorithmType.None;
225 public SecurityProtocolType SecurityProtocol
229 if (this.context.HandshakeState == HandshakeState.Finished)
231 return this.context.SecurityProtocol;
238 public X509Certificate SelectedClientCertificate
240 get { return this.context.ClientSettings.ClientCertificate; }
243 public X509Certificate ServerCertificate
247 if (this.context.HandshakeState == HandshakeState.Finished)
249 if (this.context.ServerSettings.Certificates != null &&
250 this.context.ServerSettings.Certificates.Count > 0)
252 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
262 #region Callback Properties
264 public CertificateValidationCallback ServerCertValidationDelegate
266 get { return this.ServerCertValidation; }
267 set { this.ServerCertValidation = value; }
270 public CertificateSelectionCallback ClientCertSelectionDelegate
272 get { return this.ClientCertSelection; }
273 set { this.ClientCertSelection = value; }
276 public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate
278 get { return this.PrivateKeySelection; }
279 set { this.PrivateKeySelection = value; }
286 public SslClientStream(
291 stream, targetHost, ownsStream,
292 SecurityProtocolType.Default, null)
296 public SslClientStream(
299 X509Certificate clientCertificate)
301 stream, targetHost, false, SecurityProtocolType.Default,
302 new X509CertificateCollection(new X509Certificate[]{clientCertificate}))
306 public SslClientStream(
309 X509CertificateCollection clientCertificates) :
311 stream, targetHost, false, SecurityProtocolType.Default,
316 public SslClientStream(
320 SecurityProtocolType securityProtocolType)
322 stream, targetHost, ownsStream, securityProtocolType,
323 new X509CertificateCollection())
327 public SslClientStream(
331 SecurityProtocolType securityProtocolType,
332 X509CertificateCollection clientCertificates)
336 throw new ArgumentNullException("stream is null.");
338 if (!stream.CanRead || !stream.CanWrite)
340 throw new ArgumentNullException("stream is not both readable and writable.");
342 if (targetHost == null || targetHost.Length == 0)
344 throw new ArgumentNullException("targetHost is null or an empty string.");
347 this.context = new ClientContext(
349 securityProtocolType,
353 this.inputBuffer = new BufferedStream(new MemoryStream());
354 this.innerStream = stream;
355 this.ownsStream = ownsStream;
356 this.read = new object ();
357 this.write = new object ();
358 this.protocol = new ClientRecordProtocol(innerStream, context);
372 #region IDisposable Methods
374 void IDisposable.Dispose()
377 GC.SuppressFinalize(this);
380 protected virtual void Dispose(bool disposing)
386 if (this.innerStream != null)
388 if (this.context.HandshakeState == HandshakeState.Finished &&
389 !this.context.ConnectionEnd)
391 // Write close notify
392 this.protocol.SendAlert(AlertDescription.CloseNotify);
397 // Close inner stream
398 this.innerStream.Close();
401 this.ownsStream = false;
402 this.innerStream = null;
403 this.ClientCertSelection = null;
404 this.ServerCertValidation = null;
405 this.PrivateKeySelection = null;
408 this.disposed = true;
416 public override IAsyncResult BeginRead(
420 AsyncCallback callback,
423 this.checkDisposed();
427 throw new ArgumentNullException("buffer is a null reference.");
431 throw new ArgumentOutOfRangeException("offset is less than 0.");
433 if (offset > buffer.Length)
435 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
439 throw new ArgumentOutOfRangeException("count is less than 0.");
441 if (count > (buffer.Length - offset))
443 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
448 if (this.context.HandshakeState == HandshakeState.None)
450 this.NegotiateHandshake();
454 IAsyncResult asyncResult;
460 // If actual buffer is full readed reset it
461 if (this.inputBuffer.Position == this.inputBuffer.Length &&
462 this.inputBuffer.Length > 0)
467 if (!this.context.ConnectionEnd)
469 // Check if we have space in the middle buffer
470 // if not Read next TLS record and update the inputBuffer
471 while ((this.inputBuffer.Length - this.inputBuffer.Position) < count)
473 // Read next record and write it into the inputBuffer
474 long position = this.inputBuffer.Position;
475 byte[] record = this.protocol.ReceiveRecord();
477 if (record != null && record.Length > 0)
479 // Write new data to the inputBuffer
480 this.inputBuffer.Seek(0, SeekOrigin.End);
481 this.inputBuffer.Write(record, 0, record.Length);
483 // Restore buffer position
484 this.inputBuffer.Seek(position, SeekOrigin.Begin);
494 // TODO: Review if we need to check the Length
495 // property of the innerStream for other types
496 // of streams, to check that there are data available
498 if (this.innerStream is NetworkStream &&
499 !((NetworkStream)this.innerStream).DataAvailable)
506 asyncResult = this.inputBuffer.BeginRead(
507 buffer, offset, count, callback, state);
509 catch (TlsException ex)
511 this.protocol.SendAlert(ex.Alert);
514 throw new IOException("The authentication or decryption has failed.");
518 throw new IOException("IO exception during read.");
525 public override IAsyncResult BeginWrite(
529 AsyncCallback callback,
532 this.checkDisposed();
536 throw new ArgumentNullException("buffer is a null reference.");
540 throw new ArgumentOutOfRangeException("offset is less than 0.");
542 if (offset > buffer.Length)
544 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
548 throw new ArgumentOutOfRangeException("count is less than 0.");
550 if (count > (buffer.Length - offset))
552 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
557 if (this.context.HandshakeState == HandshakeState.None)
559 this.NegotiateHandshake();
563 IAsyncResult asyncResult;
569 // Send the buffer as a TLS record
571 byte[] record = this.protocol.EncodeRecord(
572 ContentType.ApplicationData, buffer, offset, count);
574 asyncResult = this.innerStream.BeginWrite(
575 record, 0, record.Length, callback, state);
577 catch (TlsException ex)
579 this.protocol.SendAlert(ex.Alert);
582 throw new IOException("The authentication or decryption has failed.");
586 throw new IOException("IO exception during Write.");
593 public override int EndRead(IAsyncResult asyncResult)
595 this.checkDisposed();
597 if (asyncResult == null)
599 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
602 return this.inputBuffer.EndRead(asyncResult);
605 public override void EndWrite(IAsyncResult asyncResult)
607 this.checkDisposed();
609 if (asyncResult == null)
611 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
614 this.innerStream.EndWrite (asyncResult);
617 public override void Close()
619 ((IDisposable)this).Dispose();
622 public override void Flush()
624 this.checkDisposed();
626 this.innerStream.Flush();
629 public int Read(byte[] buffer)
631 return this.Read(buffer, 0, buffer.Length);
634 public override int Read(byte[] buffer, int offset, int count)
636 IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
638 return this.EndRead(res);
641 public override long Seek(long offset, SeekOrigin origin)
643 throw new NotSupportedException();
646 public override void SetLength(long value)
648 throw new NotSupportedException();
651 public void Write(byte[] buffer)
653 this.Write(buffer, 0, buffer.Length);
656 public override void Write(byte[] buffer, int offset, int count)
658 IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null);
667 private void resetBuffer()
669 this.inputBuffer.SetLength(0);
670 this.inputBuffer.Position = 0;
673 private void checkDisposed()
677 throw new ObjectDisposedException("The SslClientStream is closed.");
683 #region Handsake Methods
688 ClientHello -------->
693 <-------- ServerHelloDone
701 Application Data <-------> Application Data
703 Fig. 1 - Message flow for a full handshake
706 internal void NegotiateHandshake()
712 if (this.context.HandshakeState != HandshakeState.None)
714 this.context.Clear();
717 // Obtain supported cipher suites
718 this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
721 this.protocol.SendRecord(HandshakeType.ClientHello);
723 // Read server response
724 while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone)
727 this.protocol.ReceiveRecord();
730 // Send client certificate if requested
731 if (this.context.ServerSettings.CertificateRequest)
733 this.protocol.SendRecord(HandshakeType.Certificate);
736 // Send Client Key Exchange
737 this.protocol.SendRecord(HandshakeType.ClientKeyExchange);
739 // Now initialize session cipher with the generated keys
740 this.context.Cipher.InitializeCipher();
742 // Send certificate verify if requested
743 if (this.context.ServerSettings.CertificateRequest)
745 this.protocol.SendRecord(HandshakeType.CertificateVerify);
748 // Send Cipher Spec protocol
749 this.protocol.SendChangeCipherSpec();
751 // Read record until server finished is received
752 while (this.context.HandshakeState != HandshakeState.Finished)
754 // If all goes well this will process messages:
755 // Change Cipher Spec
757 this.protocol.ReceiveRecord();
761 this.context.ClearKeyInfo();
763 catch (TlsException ex)
765 this.protocol.SendAlert(ex.Alert);
768 throw new IOException("The authentication or decryption has failed.");
772 this.protocol.SendAlert(AlertDescription.InternalError);
775 throw new IOException("The authentication or decryption has failed.");
782 #region Event Methods
784 internal virtual bool RaiseServerCertificateValidation(
785 X509Certificate certificate,
786 int[] certificateErrors)
788 if (this.ServerCertValidation != null)
790 return this.ServerCertValidation(certificate, certificateErrors);
793 return (certificateErrors != null && certificateErrors.Length == 0);
796 internal X509Certificate RaiseClientCertificateSelection(
797 X509CertificateCollection clientCertificates,
798 X509Certificate serverCertificate,
800 X509CertificateCollection serverRequestedCertificates)
802 if (this.ClientCertSelection != null)
804 return this.ClientCertSelection(
808 serverRequestedCertificates);
814 internal AsymmetricAlgorithm RaisePrivateKeySelection(
815 X509Certificate certificate,
818 if (this.PrivateKeySelection != null)
820 return this.PrivateKeySelection(certificate, targetHost);