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(); }
130 #region Security Properties
132 public bool CheckCertRevocationStatus
134 get { return this.checkCertRevocationStatus ; }
135 set { this.checkCertRevocationStatus = value; }
138 public CipherAlgorithmType CipherAlgorithm
142 if (this.context.HandshakeState == HandshakeState.Finished)
144 return this.context.Cipher.CipherAlgorithmType;
147 return CipherAlgorithmType.None;
151 public int CipherStrength
155 if (this.context.HandshakeState == HandshakeState.Finished)
157 return this.context.Cipher.EffectiveKeyBits;
164 public X509CertificateCollection ClientCertificates
166 get { return this.context.ClientSettings.Certificates;}
169 public HashAlgorithmType HashAlgorithm
173 if (this.context.HandshakeState == HandshakeState.Finished)
175 return this.context.Cipher.HashAlgorithmType;
178 return HashAlgorithmType.None;
182 public int HashStrength
186 if (this.context.HandshakeState == HandshakeState.Finished)
188 return this.context.Cipher.HashSize * 8;
195 public int KeyExchangeStrength
199 if (this.context.HandshakeState == HandshakeState.Finished)
201 return this.context.ServerSettings.Certificates[0].RSA.KeySize;
208 public ExchangeAlgorithmType KeyExchangeAlgorithm
212 if (this.context.HandshakeState == HandshakeState.Finished)
214 return this.context.Cipher.ExchangeAlgorithmType;
217 return ExchangeAlgorithmType.None;
221 public SecurityProtocolType SecurityProtocol
225 if (this.context.HandshakeState == HandshakeState.Finished)
227 return this.context.SecurityProtocol;
234 public X509Certificate SelectedClientCertificate
236 get { return this.context.ClientSettings.ClientCertificate; }
239 public X509Certificate ServerCertificate
243 if (this.context.HandshakeState == HandshakeState.Finished)
245 if (this.context.ServerSettings.Certificates != null &&
246 this.context.ServerSettings.Certificates.Count > 0)
248 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
258 #region Callback Properties
260 public CertificateValidationCallback ServerCertValidationDelegate
262 get { return this.ServerCertValidation; }
263 set { this.ServerCertValidation = value; }
266 public CertificateSelectionCallback ClientCertSelectionDelegate
268 get { return this.ClientCertSelection; }
269 set { this.ClientCertSelection = value; }
272 public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate
274 get { return this.PrivateKeySelection; }
275 set { this.PrivateKeySelection = value; }
282 public SslClientStream(
287 stream, targetHost, ownsStream,
288 SecurityProtocolType.Default, null)
292 public SslClientStream(
295 X509Certificate clientCertificate)
297 stream, targetHost, false, SecurityProtocolType.Default,
298 new X509CertificateCollection(new X509Certificate[]{clientCertificate}))
302 public SslClientStream(
305 X509CertificateCollection clientCertificates) :
307 stream, targetHost, false, SecurityProtocolType.Default,
312 public SslClientStream(
316 SecurityProtocolType securityProtocolType)
318 stream, targetHost, ownsStream, securityProtocolType,
319 new X509CertificateCollection())
323 public SslClientStream(
327 SecurityProtocolType securityProtocolType,
328 X509CertificateCollection clientCertificates)
332 throw new ArgumentNullException("stream is null.");
334 if (!stream.CanRead || !stream.CanWrite)
336 throw new ArgumentNullException("stream is not both readable and writable.");
338 if (targetHost == null || targetHost.Length == 0)
340 throw new ArgumentNullException("targetHost is null or an empty string.");
343 this.context = new ClientContext(
345 securityProtocolType,
349 this.inputBuffer = new BufferedStream(new MemoryStream());
350 this.innerStream = stream;
351 this.ownsStream = ownsStream;
352 this.read = new object ();
353 this.write = new object ();
354 this.protocol = new ClientRecordProtocol(innerStream, context);
368 #region IDisposable Methods
370 void IDisposable.Dispose()
373 GC.SuppressFinalize(this);
376 protected virtual void Dispose(bool disposing)
382 if (this.innerStream != null)
384 if (this.context.HandshakeState == HandshakeState.Finished &&
385 !this.context.ConnectionEnd)
387 // Write close notify
388 this.protocol.SendAlert(AlertDescription.CloseNotify);
393 // Close inner stream
394 this.innerStream.Close();
397 this.ownsStream = false;
398 this.innerStream = null;
399 this.ClientCertSelection = null;
400 this.ServerCertValidation = null;
401 this.PrivateKeySelection = null;
404 this.disposed = true;
412 public override IAsyncResult BeginRead(
416 AsyncCallback callback,
419 this.checkDisposed();
423 throw new ArgumentNullException("buffer is a null reference.");
427 throw new ArgumentOutOfRangeException("offset is less than 0.");
429 if (offset > buffer.Length)
431 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
435 throw new ArgumentOutOfRangeException("count is less than 0.");
437 if (count > (buffer.Length - offset))
439 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
444 if (this.context.HandshakeState == HandshakeState.None)
446 this.NegotiateHandshake();
450 IAsyncResult asyncResult;
456 // If actual buffer is full readed reset it
457 if (this.inputBuffer.Position == this.inputBuffer.Length &&
458 this.inputBuffer.Length > 0)
463 if (!this.context.ConnectionEnd)
465 // Check if we have space in the middle buffer
466 // if not Read next TLS record and update the inputBuffer
467 while ((this.inputBuffer.Length - this.inputBuffer.Position) < count)
469 // Read next record and write it into the inputBuffer
470 long position = this.inputBuffer.Position;
471 byte[] record = this.protocol.ReceiveRecord();
473 if (record != null && record.Length > 0)
475 // Write new data to the inputBuffer
476 this.inputBuffer.Seek(0, SeekOrigin.End);
477 this.inputBuffer.Write(record, 0, record.Length);
479 // Restore buffer position
480 this.inputBuffer.Seek(position, SeekOrigin.Begin);
490 // TODO: Review if we need to check the Length
491 // property of the innerStream for other types
492 // of streams, to check that there are data available
494 if (this.innerStream is NetworkStream &&
495 !((NetworkStream)this.innerStream).DataAvailable)
502 asyncResult = this.inputBuffer.BeginRead(
503 buffer, offset, count, callback, state);
505 catch (TlsException ex)
507 this.protocol.SendAlert(ex.Alert);
510 throw new IOException("The authentication or decryption has failed.");
514 throw new IOException("IO exception during read.");
521 public override IAsyncResult BeginWrite(
525 AsyncCallback callback,
528 this.checkDisposed();
532 throw new ArgumentNullException("buffer is a null reference.");
536 throw new ArgumentOutOfRangeException("offset is less than 0.");
538 if (offset > buffer.Length)
540 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
544 throw new ArgumentOutOfRangeException("count is less than 0.");
546 if (count > (buffer.Length - offset))
548 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
553 if (this.context.HandshakeState == HandshakeState.None)
555 this.NegotiateHandshake();
559 IAsyncResult asyncResult;
565 // Send the buffer as a TLS record
567 byte[] record = this.protocol.EncodeRecord(
568 ContentType.ApplicationData, buffer, offset, count);
570 asyncResult = this.innerStream.BeginWrite(
571 record, 0, record.Length, callback, state);
573 catch (TlsException ex)
575 this.protocol.SendAlert(ex.Alert);
578 throw new IOException("The authentication or decryption has failed.");
582 throw new IOException("IO exception during Write.");
589 public override int EndRead(IAsyncResult asyncResult)
591 this.checkDisposed();
593 if (asyncResult == null)
595 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
598 return this.inputBuffer.EndRead(asyncResult);
601 public override void EndWrite(IAsyncResult asyncResult)
603 this.checkDisposed();
605 if (asyncResult == null)
607 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
610 this.innerStream.EndWrite (asyncResult);
613 public override void Close()
615 ((IDisposable)this).Dispose();
618 public override void Flush()
620 this.checkDisposed();
622 this.innerStream.Flush();
625 public int Read(byte[] buffer)
627 return this.Read(buffer, 0, buffer.Length);
630 public override int Read(byte[] buffer, int offset, int count)
632 IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
634 return this.EndRead(res);
637 public override long Seek(long offset, SeekOrigin origin)
639 throw new NotSupportedException();
642 public override void SetLength(long value)
644 throw new NotSupportedException();
647 public void Write(byte[] buffer)
649 this.Write(buffer, 0, buffer.Length);
652 public override void Write(byte[] buffer, int offset, int count)
654 IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null);
663 private void resetBuffer()
665 this.inputBuffer.SetLength(0);
666 this.inputBuffer.Position = 0;
669 private void checkDisposed()
673 throw new ObjectDisposedException("The SslClientStream is closed.");
679 #region Handsake Methods
684 ClientHello -------->
689 <-------- ServerHelloDone
697 Application Data <-------> Application Data
699 Fig. 1 - Message flow for a full handshake
702 internal void NegotiateHandshake()
708 if (this.context.HandshakeState != HandshakeState.None)
710 this.context.Clear();
713 // Obtain supported cipher suites
714 this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
717 this.protocol.SendRecord(HandshakeType.ClientHello);
719 // Read server response
720 while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone)
723 this.protocol.ReceiveRecord();
726 // Send client certificate if requested
727 if (this.context.ServerSettings.CertificateRequest)
729 this.protocol.SendRecord(HandshakeType.Certificate);
732 // Send Client Key Exchange
733 this.protocol.SendRecord(HandshakeType.ClientKeyExchange);
735 // Now initialize session cipher with the generated keys
736 this.context.Cipher.InitializeCipher();
738 // Send certificate verify if requested
739 if (this.context.ServerSettings.CertificateRequest)
741 this.protocol.SendRecord(HandshakeType.CertificateVerify);
744 // Send Cipher Spec protocol
745 this.protocol.SendChangeCipherSpec();
747 // Read record until server finished is received
748 while (this.context.HandshakeState != HandshakeState.Finished)
750 // If all goes well this will process messages:
751 // Change Cipher Spec
753 this.protocol.ReceiveRecord();
757 this.context.ClearKeyInfo();
759 catch (TlsException ex)
761 this.protocol.SendAlert(ex.Alert);
764 throw new IOException("The authentication or decryption has failed.");
768 this.protocol.SendAlert(AlertDescription.InternalError);
771 throw new IOException("The authentication or decryption has failed.");
778 #region Event Methods
780 internal virtual bool RaiseServerCertificateValidation(
781 X509Certificate certificate,
782 int[] certificateErrors)
784 if (this.ServerCertValidation != null)
786 return this.ServerCertValidation(certificate, certificateErrors);
789 return (certificateErrors != null && certificateErrors.Length == 0);
792 internal X509Certificate RaiseClientCertificateSelection(
793 X509CertificateCollection clientCertificates,
794 X509Certificate serverCertificate,
796 X509CertificateCollection serverRequestedCertificates)
798 if (this.ClientCertSelection != null)
800 return this.ClientCertSelection(
804 serverRequestedCertificates);
810 internal AsymmetricAlgorithm RaisePrivateKeySelection(
811 X509Certificate certificate,
814 if (this.PrivateKeySelection != null)
816 return this.PrivateKeySelection(certificate, targetHost);