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
37 public class SslServerStream : Stream, IDisposable
39 #region Internal Events
41 internal event CertificateValidationCallback ClientCertValidation;
42 internal event PrivateKeySelectionCallback PrivateKeySelection;
48 private ServerRecordProtocol protocol;
49 private BufferedStream inputBuffer;
50 private ServerContext context;
51 private Stream innerStream;
52 private bool disposed;
53 private bool ownsStream;
54 private bool checkCertRevocationStatus;
62 public override bool CanRead
64 get { return this.innerStream.CanRead; }
67 public override bool CanWrite
69 get { return this.innerStream.CanWrite; }
72 public override bool CanSeek
74 get { return this.innerStream.CanSeek; }
77 public override long Length
79 get { throw new NotSupportedException(); }
82 public override long Position
84 get { throw new NotSupportedException(); }
85 set { throw new NotSupportedException(); }
90 #region Security Properties
92 public bool CheckCertRevocationStatus
94 get { return this.checkCertRevocationStatus ; }
95 set { this.checkCertRevocationStatus = value; }
98 public CipherAlgorithmType CipherAlgorithm
102 if (this.context.HandshakeState == HandshakeState.Finished)
104 return this.context.Cipher.CipherAlgorithmType;
107 return CipherAlgorithmType.None;
111 public int CipherStrength
115 if (this.context.HandshakeState == HandshakeState.Finished)
117 return this.context.Cipher.EffectiveKeyBits;
124 public X509Certificate ClientCertificate
128 if (this.context.HandshakeState == HandshakeState.Finished)
130 return this.context.ClientSettings.ClientCertificate;
137 public HashAlgorithmType HashAlgorithm
141 if (this.context.HandshakeState == HandshakeState.Finished)
143 return this.context.Cipher.HashAlgorithmType;
146 return HashAlgorithmType.None;
150 public int HashStrength
154 if (this.context.HandshakeState == HandshakeState.Finished)
156 return this.context.Cipher.HashSize * 8;
163 public int KeyExchangeStrength
167 if (this.context.HandshakeState == HandshakeState.Finished)
169 return this.context.ServerSettings.Certificates[0].RSA.KeySize;
176 public ExchangeAlgorithmType KeyExchangeAlgorithm
180 if (this.context.HandshakeState == HandshakeState.Finished)
182 return this.context.Cipher.ExchangeAlgorithmType;
185 return ExchangeAlgorithmType.None;
189 public SecurityProtocolType SecurityProtocol
193 if (this.context.HandshakeState == HandshakeState.Finished)
195 return this.context.SecurityProtocol;
202 public X509Certificate ServerCertificate
206 if (this.context.HandshakeState == HandshakeState.Finished)
208 if (this.context.ServerSettings.Certificates != null &&
209 this.context.ServerSettings.Certificates.Count > 0)
211 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
221 #region Callback Properties
223 public CertificateValidationCallback ClientCertValidationDelegate
225 get { return this.ClientCertValidation; }
226 set { this.ClientCertValidation = value; }
229 public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate
231 get { return this.PrivateKeySelection; }
232 set { this.PrivateKeySelection = value; }
239 public SslServerStream(
241 X509Certificate serverCertificate) : this(
246 SecurityProtocolType.Default)
250 public SslServerStream(
252 X509Certificate serverCertificate,
253 bool clientCertificateRequired,
254 bool ownsStream): this(
257 clientCertificateRequired,
259 SecurityProtocolType.Default)
263 public SslServerStream(
265 X509Certificate serverCertificate,
266 bool clientCertificateRequired,
268 SecurityProtocolType securityProtocolType)
272 throw new ArgumentNullException("stream is null.");
274 if (!stream.CanRead || !stream.CanWrite)
276 throw new ArgumentNullException("stream is not both readable and writable.");
279 this.context = new ServerContext(
281 securityProtocolType,
283 clientCertificateRequired);
285 this.inputBuffer = new BufferedStream(new MemoryStream());
286 this.innerStream = stream;
287 this.ownsStream = ownsStream;
288 this.read = new object ();
289 this.write = new object ();
290 this.protocol = new ServerRecordProtocol(innerStream, context);
304 #region IDisposable Methods
306 void IDisposable.Dispose()
309 GC.SuppressFinalize(this);
312 protected virtual void Dispose(bool disposing)
318 if (this.innerStream != null)
320 if (this.context.HandshakeState == HandshakeState.Finished)
322 // Write close notify
323 this.protocol.SendAlert(AlertDescription.CloseNotify);
328 // Close inner stream
329 this.innerStream.Close();
332 this.ownsStream = false;
333 this.innerStream = null;
334 this.ClientCertValidation = null;
335 this.PrivateKeySelection = null;
338 this.disposed = true;
346 public override IAsyncResult BeginRead(
350 AsyncCallback callback,
353 this.checkDisposed();
357 throw new ArgumentNullException("buffer is a null reference.");
361 throw new ArgumentOutOfRangeException("offset is less than 0.");
363 if (offset > buffer.Length)
365 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
369 throw new ArgumentOutOfRangeException("count is less than 0.");
371 if (count > (buffer.Length - offset))
373 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
378 if (this.context.HandshakeState == HandshakeState.None)
380 this.doHandshake(); // Handshake negotiation
384 IAsyncResult asyncResult;
390 // If actual buffer is full readed reset it
391 if (this.inputBuffer.Position == this.inputBuffer.Length &&
392 this.inputBuffer.Length > 0)
397 if (!this.context.ConnectionEnd)
399 // Check if we have space in the middle buffer
400 // if not Read next TLS record and update the inputBuffer
401 while ((this.inputBuffer.Length - this.inputBuffer.Position) < count)
403 // Read next record and write it into the inputBuffer
404 long position = this.inputBuffer.Position;
405 byte[] record = this.protocol.ReceiveRecord(this.innerStream);
407 if (record != null && record.Length > 0)
409 // Write new data to the inputBuffer
410 this.inputBuffer.Seek(0, SeekOrigin.End);
411 this.inputBuffer.Write(record, 0, record.Length);
413 // Restore buffer position
414 this.inputBuffer.Seek(position, SeekOrigin.Begin);
424 // TODO: Review if we need to check the Length
425 // property of the innerStream for other types
426 // of streams, to check that there are data available
428 if (this.innerStream is NetworkStream &&
429 !((NetworkStream)this.innerStream).DataAvailable)
436 asyncResult = this.inputBuffer.BeginRead(
437 buffer, offset, count, callback, state);
439 catch (TlsException ex)
441 this.protocol.SendAlert(ex.Alert);
444 throw new IOException("The authentication or decryption has failed.");
448 throw new IOException("IO exception during read.");
455 public override IAsyncResult BeginWrite(
459 AsyncCallback callback,
462 this.checkDisposed();
466 throw new ArgumentNullException("buffer is a null reference.");
470 throw new ArgumentOutOfRangeException("offset is less than 0.");
472 if (offset > buffer.Length)
474 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
478 throw new ArgumentOutOfRangeException("count is less than 0.");
480 if (count > (buffer.Length - offset))
482 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
487 if (this.context.HandshakeState == HandshakeState.None)
489 // Start handshake negotiation
494 IAsyncResult asyncResult;
500 // Send the buffer as a TLS record
501 byte[] record = this.protocol.EncodeRecord(
502 ContentType.ApplicationData, buffer, offset, count);
504 asyncResult = this.innerStream.BeginWrite(
505 record, 0, record.Length, callback, state);
507 catch (TlsException ex)
509 this.protocol.SendAlert(ex.Alert);
512 throw new IOException("The authentication or decryption has failed.");
516 throw new IOException("IO exception during Write.");
523 public override int EndRead(IAsyncResult asyncResult)
525 this.checkDisposed();
527 if (asyncResult == null)
529 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
532 return this.inputBuffer.EndRead(asyncResult);
535 public override void EndWrite(IAsyncResult asyncResult)
537 this.checkDisposed();
539 if (asyncResult == null)
541 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
544 this.innerStream.EndWrite (asyncResult);
547 public override void Close()
549 ((IDisposable)this).Dispose();
552 public override void Flush()
554 this.checkDisposed();
556 this.innerStream.Flush();
559 public int Read(byte[] buffer)
561 return this.Read(buffer, 0, buffer.Length);
564 public override int Read(byte[] buffer, int offset, int count)
566 IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
568 return this.EndRead(res);
571 public override long Seek(long offset, SeekOrigin origin)
573 throw new NotSupportedException();
576 public override void SetLength(long value)
578 throw new NotSupportedException();
581 public void Write(byte[] buffer)
583 this.Write(buffer, 0, buffer.Length);
586 public override void Write(byte[] buffer, int offset, int count)
588 IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null);
597 private void resetBuffer()
599 this.inputBuffer.SetLength(0);
600 this.inputBuffer.Position = 0;
603 private void checkDisposed()
607 throw new ObjectDisposedException("The SslClientStream is closed.");
613 #region Handsake Methods
618 ClientHello -------->
623 <-------- ServerHelloDone
631 Application Data <-------> Application Data
633 Fig. 1 - Message flow for a full handshake
636 private void doHandshake()
640 // Reset the context if needed
641 if (this.context.HandshakeState != HandshakeState.None)
643 this.context.Clear();
646 // Obtain supported cipher suites
647 this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
649 // Set handshake state
650 this.context.HandshakeState = HandshakeState.Started;
652 // Receive Client Hello message
\r
653 this.protocol.ReceiveRecord (this.innerStream);
655 // If received message is not an ClientHello send a
657 if (this.context.LastHandshakeMsg != HandshakeType.ClientHello)
659 this.protocol.SendAlert(AlertDescription.UnexpectedMessage);
662 // Send ServerHello message
663 this.protocol.SendRecord(HandshakeType.ServerHello);
665 // Send ServerCertificate message
666 this.protocol.SendRecord(HandshakeType.Certificate);
668 // If the negotiated cipher is a KeyEx cipher send ServerKeyExchange
669 if (this.context.Cipher.ExchangeAlgorithmType == ExchangeAlgorithmType.RsaKeyX)
671 this.protocol.SendRecord(HandshakeType.ServerKeyExchange);
674 // If the negotiated cipher is a KeyEx cipher or
675 // the client certificate is required send the CertificateRequest message
676 if (this.context.Cipher.ExchangeAlgorithmType == ExchangeAlgorithmType.RsaKeyX ||
677 this.context.ClientCertificateRequired)
679 this.protocol.SendRecord(HandshakeType.CertificateRequest);
682 // Send ServerHelloDone message
683 this.protocol.SendRecord(HandshakeType.ServerHelloDone);
685 // Receive client response, until the Client Finished message
687 while (this.context.LastHandshakeMsg != HandshakeType.Finished)
689 this.protocol.ReceiveRecord (this.innerStream);
692 // Send ChangeCipherSpec and ServerFinished messages
693 this.protocol.SendChangeCipherSpec();
695 // The handshake is finished
696 this.context.HandshakeState = HandshakeState.Finished;
699 this.context.ClearKeyInfo();
701 catch (TlsException ex)
703 this.protocol.SendAlert(ex.Alert);
706 throw new IOException("The authentication or decryption has failed.");
710 this.protocol.SendAlert(AlertDescription.InternalError);
713 throw new IOException("The authentication or decryption has failed.");
719 #region Event Methods
721 internal bool RaiseClientCertificateValidation(
722 X509Certificate certificate,
723 int[] certificateErrors)
725 if (this.ClientCertValidation != null)
727 return this.ClientCertValidation(certificate, certificateErrors);
730 return (certificateErrors != null && certificateErrors.Length == 0);
733 internal AsymmetricAlgorithm RaisePrivateKeySelection(
734 X509Certificate certificate,
737 if (this.PrivateKeySelection != null)
739 return this.PrivateKeySelection(certificate, targetHost);