1 /* Transport Security Layer (TLS)
2 * Copyright (c) 2003-2004 Carlos Guzman Alvarez
4 * Permission is hereby granted, free of charge, to any person
5 * obtaining a copy of this software and associated documentation
6 * files (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * 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.Alerts;
35 namespace Mono.Security.Protocol.Tls
37 public class SslServerStream : Stream, IDisposable
39 #region Internal Events
41 internal event CertificateValidationCallback ClientCertValidation;
47 private CertificateValidationCallback clientCertValidationDelegate;
49 private ServerRecordProtocol protocol;
50 private BufferedStream inputBuffer;
51 private ServerContext context;
52 private Stream innerStream;
53 private bool disposed;
54 private bool ownsStream;
55 private bool checkCertRevocationStatus;
63 public override bool CanRead
65 get { return this.innerStream.CanRead; }
68 public override bool CanWrite
70 get { return this.innerStream.CanWrite; }
73 public override bool CanSeek
75 get { return this.innerStream.CanSeek; }
78 public override long Length
80 get { throw new NotSupportedException(); }
83 public override long Position
85 get { throw new NotSupportedException(); }
86 set { throw new NotSupportedException(); }
91 #region Security Properties
93 public bool CheckCertRevocationStatus
95 get { return this.checkCertRevocationStatus ; }
96 set { this.checkCertRevocationStatus = value; }
99 public CipherAlgorithmType CipherAlgorithm
103 if (this.context.HandshakeState == HandshakeState.Finished)
105 return this.context.Cipher.CipherAlgorithmType;
108 return CipherAlgorithmType.None;
112 public int CipherStrength
116 if (this.context.HandshakeState == HandshakeState.Finished)
118 return this.context.Cipher.EffectiveKeyBits;
125 public X509Certificate ClientCertificate
129 if (this.context.HandshakeState == HandshakeState.Finished)
131 return this.context.ClientSettings.ClientCertificate;
138 public HashAlgorithmType HashAlgorithm
142 if (this.context.HandshakeState == HandshakeState.Finished)
144 return this.context.Cipher.HashAlgorithmType;
147 return HashAlgorithmType.None;
151 public int HashStrength
155 if (this.context.HandshakeState == HandshakeState.Finished)
157 return this.context.Cipher.HashSize * 8;
164 public int KeyExchangeStrength
168 if (this.context.HandshakeState == HandshakeState.Finished)
170 return this.context.ServerSettings.Certificates[0].RSA.KeySize;
177 public ExchangeAlgorithmType KeyExchangeAlgorithm
181 if (this.context.HandshakeState == HandshakeState.Finished)
183 return this.context.Cipher.ExchangeAlgorithmType;
186 return ExchangeAlgorithmType.None;
190 public SecurityProtocolType SecurityProtocol
194 if (this.context.HandshakeState == HandshakeState.Finished)
196 return this.context.SecurityProtocol;
203 public X509Certificate ServerCertificate
207 if (this.context.HandshakeState == HandshakeState.Finished)
209 if (this.context.ServerSettings.Certificates != null &&
210 this.context.ServerSettings.Certificates.Count > 0)
212 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
222 #region Callback Properties
224 public CertificateValidationCallback ClientCertValidationDelegate
226 get { return this.clientCertValidationDelegate; }
229 if (this.ClientCertValidation != null)
231 this.ClientCertValidation -= this.clientCertValidationDelegate;
233 this.clientCertValidationDelegate = value;
234 this.ClientCertValidation += this.clientCertValidationDelegate;
242 public SslServerStream(
244 X509Certificate serverCertificate) : this(
249 SecurityProtocolType.Default)
253 public SslServerStream(
255 X509Certificate serverCertificate,
256 bool clientCertificateRequired,
257 bool ownsStream): this(
260 clientCertificateRequired,
262 SecurityProtocolType.Default)
266 public SslServerStream(
268 X509Certificate serverCertificate,
269 bool clientCertificateRequired,
271 SecurityProtocolType securityProtocolType)
275 throw new ArgumentNullException("stream is null.");
277 if (!stream.CanRead || !stream.CanWrite)
279 throw new ArgumentNullException("stream is not both readable and writable.");
282 this.context = new ServerContext(
284 securityProtocolType,
286 clientCertificateRequired);
288 this.inputBuffer = new BufferedStream(new MemoryStream());
289 this.innerStream = stream;
290 this.ownsStream = ownsStream;
291 this.read = String.Empty;
292 this.write = String.Empty;
293 this.protocol = new ServerRecordProtocol(innerStream, context);
307 #region IDisposable Methods
309 void IDisposable.Dispose()
312 GC.SuppressFinalize(this);
315 protected virtual void Dispose(bool disposing)
321 if (this.innerStream != null)
323 if (this.context.HandshakeState == HandshakeState.Finished)
325 // Write close notify
326 this.protocol.SendAlert(TlsAlertDescription.CloseNotify);
331 // Close inner stream
332 this.innerStream.Close();
335 this.ownsStream = false;
336 this.innerStream = null;
337 if (this.ClientCertValidation != null)
339 this.ClientCertValidation -= this.clientCertValidationDelegate;
341 this.clientCertValidationDelegate = null;
344 this.disposed = true;
352 public override IAsyncResult BeginRead(
356 AsyncCallback callback,
359 this.checkDisposed();
363 throw new ArgumentNullException("buffer is a null reference.");
367 throw new ArgumentOutOfRangeException("offset is less than 0.");
369 if (offset > buffer.Length)
371 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
375 throw new ArgumentOutOfRangeException("count is less than 0.");
377 if (count > (buffer.Length - offset))
379 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
384 if (this.context.HandshakeState == HandshakeState.None)
386 this.doHandshake(); // Handshake negotiation
390 IAsyncResult asyncResult;
396 // If actual buffer is full readed reset it
397 if (this.inputBuffer.Position == this.inputBuffer.Length &&
398 this.inputBuffer.Length > 0)
403 if (!this.context.ConnectionEnd)
405 // Check if we have space in the middle buffer
406 // if not Read next TLS record and update the inputBuffer
407 while ((this.inputBuffer.Length - this.inputBuffer.Position) < count)
409 // Read next record and write it into the inputBuffer
410 long position = this.inputBuffer.Position;
411 byte[] record = this.protocol.ReceiveRecord();
413 if (record != null && record.Length > 0)
415 // Write new data to the inputBuffer
416 this.inputBuffer.Seek(0, SeekOrigin.End);
417 this.inputBuffer.Write(record, 0, record.Length);
419 // Restore buffer position
420 this.inputBuffer.Seek(position, SeekOrigin.Begin);
430 // TODO: Review if we need to check the Length
431 // property of the innerStream for other types
432 // of streams, to check that there are data available
434 if (this.innerStream is NetworkStream &&
435 !((NetworkStream)this.innerStream).DataAvailable)
442 asyncResult = this.inputBuffer.BeginRead(
443 buffer, offset, count, callback, state);
447 throw new IOException("The authentication or decryption has failed.");
451 throw new IOException("IO exception during read.");
458 public override IAsyncResult BeginWrite(
462 AsyncCallback callback,
465 this.checkDisposed();
469 throw new ArgumentNullException("buffer is a null reference.");
473 throw new ArgumentOutOfRangeException("offset is less than 0.");
475 if (offset > buffer.Length)
477 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
481 throw new ArgumentOutOfRangeException("count is less than 0.");
483 if (count > (buffer.Length - offset))
485 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
490 if (this.context.HandshakeState == HandshakeState.None)
492 // Start handshake negotiation
497 IAsyncResult asyncResult;
503 // Send the buffer as a TLS record
505 byte[] record = this.protocol.EncodeRecord(
506 TlsContentType.ApplicationData, buffer, offset, count);
508 asyncResult = this.innerStream.BeginWrite(
509 record, 0, record.Length, callback, state);
513 throw new IOException("The authentication or decryption has failed.");
517 throw new IOException("IO exception during Write.");
524 public override int EndRead(IAsyncResult asyncResult)
526 this.checkDisposed();
528 if (asyncResult == null)
530 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
533 return this.inputBuffer.EndRead(asyncResult);
536 public override void EndWrite(IAsyncResult asyncResult)
538 this.checkDisposed();
540 if (asyncResult == null)
542 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
545 this.innerStream.EndWrite (asyncResult);
548 public override void Close()
550 ((IDisposable)this).Dispose();
553 public override void Flush()
555 this.checkDisposed();
557 this.innerStream.Flush();
560 public int Read(byte[] buffer)
562 return this.Read(buffer, 0, buffer.Length);
565 public override int Read(byte[] buffer, int offset, int count)
567 IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
569 return this.EndRead(res);
572 public override long Seek(long offset, SeekOrigin origin)
574 throw new NotSupportedException();
577 public override void SetLength(long value)
579 throw new NotSupportedException();
582 public void Write(byte[] buffer)
584 this.Write(buffer, 0, buffer.Length);
587 public override void Write(byte[] buffer, int offset, int count)
589 IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null);
598 private void resetBuffer()
600 this.inputBuffer.SetLength(0);
601 this.inputBuffer.Position = 0;
604 private void checkDisposed()
608 throw new ObjectDisposedException("The SslClientStream is closed.");
614 #region Handsake Methods
619 ClientHello -------->
624 <-------- ServerHelloDone
632 Application Data <-------> Application Data
634 Fig. 1 - Message flow for a full handshake
637 private void doHandshake()
641 #warning "Implement server handshake logic"
643 // Obtain supported cipher suites
644 this.context.SupportedCiphers = TlsCipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
647 this.context.ClearKeyInfo();
649 throw new NotSupportedException();
653 throw new IOException("The authentication or decryption has failed.");