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();
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
653 this.protocol
.ReceiveRecord();
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();
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
);