(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / SslServerStream.cs
blob231c5b357ed41faef409e967a1861fb224acb50c
1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
4 //
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:
12 //
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
15 //
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.
25 using System;
26 using System.Collections;
27 using System.IO;
28 using System.Net;
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;
44 #endregion
46 #region Fields
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;
55 private object read;
56 private object write;
58 #endregion
60 #region Properties
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(); }
88 #endregion
90 #region Security Properties
92 public bool CheckCertRevocationStatus
94 get { return this.checkCertRevocationStatus ; }
95 set { this.checkCertRevocationStatus = value; }
98 public CipherAlgorithmType CipherAlgorithm
100 get
102 if (this.context.HandshakeState == HandshakeState.Finished)
104 return this.context.Cipher.CipherAlgorithmType;
107 return CipherAlgorithmType.None;
111 public int CipherStrength
113 get
115 if (this.context.HandshakeState == HandshakeState.Finished)
117 return this.context.Cipher.EffectiveKeyBits;
120 return 0;
124 public X509Certificate ClientCertificate
126 get
128 if (this.context.HandshakeState == HandshakeState.Finished)
130 return this.context.ClientSettings.ClientCertificate;
133 return null;
137 public HashAlgorithmType HashAlgorithm
139 get
141 if (this.context.HandshakeState == HandshakeState.Finished)
143 return this.context.Cipher.HashAlgorithmType;
146 return HashAlgorithmType.None;
150 public int HashStrength
152 get
154 if (this.context.HandshakeState == HandshakeState.Finished)
156 return this.context.Cipher.HashSize * 8;
159 return 0;
163 public int KeyExchangeStrength
165 get
167 if (this.context.HandshakeState == HandshakeState.Finished)
169 return this.context.ServerSettings.Certificates[0].RSA.KeySize;
172 return 0;
176 public ExchangeAlgorithmType KeyExchangeAlgorithm
178 get
180 if (this.context.HandshakeState == HandshakeState.Finished)
182 return this.context.Cipher.ExchangeAlgorithmType;
185 return ExchangeAlgorithmType.None;
189 public SecurityProtocolType SecurityProtocol
191 get
193 if (this.context.HandshakeState == HandshakeState.Finished)
195 return this.context.SecurityProtocol;
198 return 0;
202 public X509Certificate ServerCertificate
204 get
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);
215 return null;
219 #endregion
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; }
235 #endregion
237 #region Constructors
239 public SslServerStream(
240 Stream stream,
241 X509Certificate serverCertificate) : this(
242 stream,
243 serverCertificate,
244 false,
245 false,
246 SecurityProtocolType.Default)
250 public SslServerStream(
251 Stream stream,
252 X509Certificate serverCertificate,
253 bool clientCertificateRequired,
254 bool ownsStream): this(
255 stream,
256 serverCertificate,
257 clientCertificateRequired,
258 ownsStream,
259 SecurityProtocolType.Default)
263 public SslServerStream(
264 Stream stream,
265 X509Certificate serverCertificate,
266 bool clientCertificateRequired,
267 bool ownsStream,
268 SecurityProtocolType securityProtocolType)
270 if (stream == null)
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(
280 this,
281 securityProtocolType,
282 serverCertificate,
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);
293 #endregion
295 #region Finalizer
297 ~SslServerStream()
299 this.Dispose(false);
302 #endregion
304 #region IDisposable Methods
306 void IDisposable.Dispose()
308 this.Dispose(true);
309 GC.SuppressFinalize(this);
312 protected virtual void Dispose(bool disposing)
314 if (!this.disposed)
316 if (disposing)
318 if (this.innerStream != null)
320 if (this.context.HandshakeState == HandshakeState.Finished)
322 // Write close notify
323 this.protocol.SendAlert(AlertDescription.CloseNotify);
326 if (this.ownsStream)
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;
342 #endregion
344 #region Methods
346 public override IAsyncResult BeginRead(
347 byte[] buffer,
348 int offset,
349 int count,
350 AsyncCallback callback,
351 object state)
353 this.checkDisposed();
355 if (buffer == null)
357 throw new ArgumentNullException("buffer is a null reference.");
359 if (offset < 0)
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.");
367 if (count < 0)
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.");
376 lock (this)
378 if (this.context.HandshakeState == HandshakeState.None)
380 this.doHandshake(); // Handshake negotiation
384 IAsyncResult asyncResult;
386 lock (this.read)
390 // If actual buffer is full readed reset it
391 if (this.inputBuffer.Position == this.inputBuffer.Length &&
392 this.inputBuffer.Length > 0)
394 this.resetBuffer();
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);
416 else
418 if (record == null)
420 break;
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
427 // for read
428 if (this.innerStream is NetworkStream &&
429 !((NetworkStream)this.innerStream).DataAvailable)
431 break;
436 asyncResult = this.inputBuffer.BeginRead(
437 buffer, offset, count, callback, state);
439 catch (TlsException ex)
441 this.protocol.SendAlert(ex.Alert);
442 this.Close();
444 throw new IOException("The authentication or decryption has failed.");
446 catch (Exception)
448 throw new IOException("IO exception during read.");
452 return asyncResult;
455 public override IAsyncResult BeginWrite(
456 byte[] buffer,
457 int offset,
458 int count,
459 AsyncCallback callback,
460 object state)
462 this.checkDisposed();
464 if (buffer == null)
466 throw new ArgumentNullException("buffer is a null reference.");
468 if (offset < 0)
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.");
476 if (count < 0)
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.");
485 lock (this)
487 if (this.context.HandshakeState == HandshakeState.None)
489 // Start handshake negotiation
490 this.doHandshake();
494 IAsyncResult asyncResult;
496 lock (this.write)
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);
510 this.Close();
512 throw new IOException("The authentication or decryption has failed.");
514 catch (Exception)
516 throw new IOException("IO exception during Write.");
520 return asyncResult;
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);
590 this.EndWrite(res);
593 #endregion
595 #region Misc Methods
597 private void resetBuffer()
599 this.inputBuffer.SetLength(0);
600 this.inputBuffer.Position = 0;
603 private void checkDisposed()
605 if (this.disposed)
607 throw new ObjectDisposedException("The SslClientStream is closed.");
611 #endregion
613 #region Handsake Methods
616 Client Server
618 ClientHello -------->
619 ServerHello
620 Certificate*
621 ServerKeyExchange*
622 CertificateRequest*
623 <-------- ServerHelloDone
624 Certificate*
625 ClientKeyExchange
626 CertificateVerify*
627 [ChangeCipherSpec]
628 Finished -------->
629 [ChangeCipherSpec]
630 <-------- Finished
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
656 // Fatal Alert
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
686 // is received
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;
698 // Clear Key Info
699 this.context.ClearKeyInfo();
701 catch (TlsException ex)
703 this.protocol.SendAlert(ex.Alert);
704 this.Close();
706 throw new IOException("The authentication or decryption has failed.");
708 catch (Exception)
710 this.protocol.SendAlert(AlertDescription.InternalError);
711 this.Close();
713 throw new IOException("The authentication or decryption has failed.");
717 #endregion
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,
735 string targetHost)
737 if (this.PrivateKeySelection != null)
739 return this.PrivateKeySelection(certificate, targetHost);
742 return null;
745 #endregion