2005-04-13 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / SslServerStream.cs
1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3
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.
23 //
24
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;
32
33 using Mono.Security.Protocol.Tls.Handshake;
34
35 namespace Mono.Security.Protocol.Tls
36 {
37         public class SslServerStream : Stream, IDisposable
38         {
39                 #region Internal Events
40                 
41                 internal event CertificateValidationCallback    ClientCertValidation;
42                 internal event PrivateKeySelectionCallback              PrivateKeySelection;
43                 
44                 #endregion
45
46                 #region Fields
47
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;          
57
58                 #endregion
59
60                 #region Properties
61
62                 public override bool CanRead
63                 {
64                         get { return this.innerStream.CanRead; }
65                 }
66
67                 public override bool CanWrite
68                 {
69                         get { return this.innerStream.CanWrite; }
70                 }
71
72                 public override bool CanSeek
73                 {
74                         get { return this.innerStream.CanSeek; }
75                 }
76
77                 public override long Length
78                 {
79                         get { throw new NotSupportedException(); }
80                 }
81
82                 public override long Position
83                 {
84                         get { throw new NotSupportedException(); }
85                         set { throw new NotSupportedException(); }
86                 }
87
88                 #endregion
89
90                 #region Security Properties
91
92                 public bool CheckCertRevocationStatus 
93                 {
94                         get { return this.checkCertRevocationStatus ; }
95                         set { this.checkCertRevocationStatus = value; }
96                 }
97
98                 public CipherAlgorithmType CipherAlgorithm 
99                 {
100                         get 
101                         { 
102                                 if (this.context.HandshakeState == HandshakeState.Finished)
103                                 {
104                                         return this.context.Cipher.CipherAlgorithmType;
105                                 }
106
107                                 return CipherAlgorithmType.None;
108                         }
109                 }
110
111                 public int CipherStrength 
112                 {
113                         get 
114                         { 
115                                 if (this.context.HandshakeState == HandshakeState.Finished)
116                                 {
117                                         return this.context.Cipher.EffectiveKeyBits;
118                                 }
119
120                                 return 0;
121                         }
122                 }
123                 
124                 public X509Certificate ClientCertificate
125                 {
126                         get 
127                         { 
128                                 if (this.context.HandshakeState == HandshakeState.Finished)
129                                 {
130                                         return this.context.ClientSettings.ClientCertificate;
131                                 }
132
133                                 return null;
134                         }
135                 }               
136                 
137                 public HashAlgorithmType HashAlgorithm 
138                 {
139                         get 
140                         { 
141                                 if (this.context.HandshakeState == HandshakeState.Finished)
142                                 {
143                                         return this.context.Cipher.HashAlgorithmType; 
144                                 }
145
146                                 return HashAlgorithmType.None;
147                         }
148                 }
149                 
150                 public int HashStrength
151                 {
152                         get 
153                         { 
154                                 if (this.context.HandshakeState == HandshakeState.Finished)
155                                 {
156                                         return this.context.Cipher.HashSize * 8; 
157                                 }
158
159                                 return 0;
160                         }
161                 }
162                 
163                 public int KeyExchangeStrength 
164                 {
165                         get 
166                         { 
167                                 if (this.context.HandshakeState == HandshakeState.Finished)
168                                 {
169                                         return this.context.ServerSettings.Certificates[0].RSA.KeySize;
170                                 }
171
172                                 return 0;
173                         }
174                 }
175                 
176                 public ExchangeAlgorithmType KeyExchangeAlgorithm 
177                 {
178                         get 
179                         { 
180                                 if (this.context.HandshakeState == HandshakeState.Finished)
181                                 {
182                                         return this.context.Cipher.ExchangeAlgorithmType; 
183                                 }
184
185                                 return ExchangeAlgorithmType.None;
186                         }
187                 }
188                 
189                 public SecurityProtocolType SecurityProtocol 
190                 {
191                         get 
192                         { 
193                                 if (this.context.HandshakeState == HandshakeState.Finished)
194                                 {
195                                         return this.context.SecurityProtocol; 
196                                 }
197
198                                 return 0;
199                         }
200                 }
201
202                 public X509Certificate ServerCertificate 
203                 {
204                         get 
205                         { 
206                                 if (this.context.HandshakeState == HandshakeState.Finished)
207                                 {
208                                         if (this.context.ServerSettings.Certificates != null &&
209                                                 this.context.ServerSettings.Certificates.Count > 0)
210                                         {
211                                                 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
212                                         }
213                                 }
214
215                                 return null;
216                         }
217                 } 
218
219                 #endregion
220
221                 #region Callback Properties
222
223                 public CertificateValidationCallback ClientCertValidationDelegate 
224                 {
225                         get { return this.ClientCertValidation; }
226                         set { this.ClientCertValidation = value; }
227                 }
228
229                 public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate 
230                 {
231                         get { return this.PrivateKeySelection; }
232                         set { this.PrivateKeySelection = value; }
233                 }
234
235                 #endregion
236
237                 #region Constructors
238
239                 public SslServerStream(
240                         Stream                  stream, 
241                         X509Certificate serverCertificate) : this(
242                         stream, 
243                         serverCertificate, 
244                         false, 
245                         false, 
246                         SecurityProtocolType.Default)
247                 {
248                 }
249
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)
260                 {
261                 }
262
263                 public SslServerStream(
264                         Stream                                  stream,
265                         X509Certificate                 serverCertificate,
266                         bool                                    clientCertificateRequired,
267                         bool                                    ownsStream,
268                         SecurityProtocolType    securityProtocolType)
269                 {
270                         if (stream == null)
271                         {
272                                 throw new ArgumentNullException("stream is null.");
273                         }
274                         if (!stream.CanRead || !stream.CanWrite)
275                         {
276                                 throw new ArgumentNullException("stream is not both readable and writable.");
277                         }
278
279                         this.context = new ServerContext(
280                                 this,
281                                 securityProtocolType,
282                                 serverCertificate,
283                                 clientCertificateRequired);
284
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);
291                 }
292
293                 #endregion
294
295                 #region Finalizer
296
297                 ~SslServerStream()
298                 {
299                         this.Dispose(false);
300                 }
301
302                 #endregion
303
304                 #region IDisposable Methods
305
306                 void IDisposable.Dispose()
307                 {
308                         this.Dispose(true);
309                         GC.SuppressFinalize(this);
310                 }
311
312                 protected virtual void Dispose(bool disposing)
313                 {
314                         if (!this.disposed)
315                         {
316                                 if (disposing)
317                                 {
318                                         if (this.innerStream != null)
319                                         {
320                                                 if (this.context.HandshakeState == HandshakeState.Finished)
321                                                 {
322                                                         // Write close notify
323                                                         this.protocol.SendAlert(AlertDescription.CloseNotify);
324                                                 }
325
326                                                 if (this.ownsStream)
327                                                 {
328                                                         // Close inner stream
329                                                         this.innerStream.Close();
330                                                 }
331                                         }
332                                         this.ownsStream                         = false;
333                                         this.innerStream                        = null;
334                                         this.ClientCertValidation       = null;
335                                         this.PrivateKeySelection        = null;
336                                 }
337
338                                 this.disposed = true;
339                         }
340                 }
341
342                 #endregion
343
344                 #region Methods
345
346                 public override IAsyncResult BeginRead(
347                         byte[]                  buffer,
348                         int                             offset,
349                         int                             count,
350                         AsyncCallback   callback,
351                         object                  state)
352                 {
353                         this.checkDisposed();
354                         
355                         if (buffer == null)
356                         {
357                                 throw new ArgumentNullException("buffer is a null reference.");
358                         }
359                         if (offset < 0)
360                         {
361                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
362                         }
363                         if (offset > buffer.Length)
364                         {
365                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
366                         }
367                         if (count < 0)
368                         {
369                                 throw new ArgumentOutOfRangeException("count is less than 0.");
370                         }
371                         if (count > (buffer.Length - offset))
372                         {
373                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
374                         }
375
376                         lock (this)
377                         {
378                                 if (this.context.HandshakeState == HandshakeState.None)
379                                 {
380                                         this.doHandshake();     // Handshake negotiation
381                                 }
382                         }
383
384                         IAsyncResult asyncResult;
385
386                         lock (this.read)
387                         {
388                                 try
389                                 {
390                                         // If actual buffer is full readed reset it
391                                         if (this.inputBuffer.Position == this.inputBuffer.Length &&
392                                                 this.inputBuffer.Length > 0)
393                                         {
394                                                 this.resetBuffer();
395                                         }
396
397                                         if (!this.context.ConnectionEnd)
398                                         {
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)
402                                                 {
403                                                         // Read next record and write it into the inputBuffer
404                                                         long    position        = this.inputBuffer.Position;                                    
405                                                         byte[]  record          = this.protocol.ReceiveRecord(this.innerStream);
406                                         
407                                                         if (record != null && record.Length > 0)
408                                                         {
409                                                                 // Write new data to the inputBuffer
410                                                                 this.inputBuffer.Seek(0, SeekOrigin.End);
411                                                                 this.inputBuffer.Write(record, 0, record.Length);
412
413                                                                 // Restore buffer position
414                                                                 this.inputBuffer.Seek(position, SeekOrigin.Begin);
415                                                         }
416                                                         else
417                                                         {
418                                                                 if (record == null)
419                                                                 {
420                                                                         break;
421                                                                 }
422                                                         }
423
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)
430                                                         {
431                                                                 break;
432                                                         }
433                                                 }
434                                         }
435
436                                         asyncResult = this.inputBuffer.BeginRead(
437                                                 buffer, offset, count, callback, state);
438                                 }
439                                 catch (TlsException ex)
440                                 {
441                                         this.protocol.SendAlert(ex.Alert);
442                                         this.Close();
443
444                                         throw new IOException("The authentication or decryption has failed.");
445                                 }
446                                 catch (Exception)
447                                 {
448                                         throw new IOException("IO exception during read.");
449                                 }
450                         }
451
452                         return asyncResult;
453                 }
454
455                 public override IAsyncResult BeginWrite(
456                         byte[]                  buffer,
457                         int                             offset,
458                         int                             count,
459                         AsyncCallback   callback,
460                         object                  state)
461                 {
462                         this.checkDisposed();
463
464                         if (buffer == null)
465                         {
466                                 throw new ArgumentNullException("buffer is a null reference.");
467                         }
468                         if (offset < 0)
469                         {
470                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
471                         }
472                         if (offset > buffer.Length)
473                         {
474                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
475                         }
476                         if (count < 0)
477                         {
478                                 throw new ArgumentOutOfRangeException("count is less than 0.");
479                         }
480                         if (count > (buffer.Length - offset))
481                         {
482                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
483                         }
484
485                         lock (this)
486                         {
487                                 if (this.context.HandshakeState == HandshakeState.None)
488                                 {
489                                         // Start handshake negotiation
490                                         this.doHandshake();
491                                 }
492                         }
493
494                         IAsyncResult asyncResult;
495
496                         lock (this.write)
497                         {
498                                 try
499                                 {
500                                         // Send the buffer as a TLS record                                      
501                                         byte[] record = this.protocol.EncodeRecord(
502                                                 ContentType.ApplicationData, buffer, offset, count);
503                                 
504                                         asyncResult = this.innerStream.BeginWrite(
505                                                 record, 0, record.Length, callback, state);
506                                 }
507                                 catch (TlsException ex)
508                                 {
509                                         this.protocol.SendAlert(ex.Alert);
510                                         this.Close();
511
512                                         throw new IOException("The authentication or decryption has failed.");
513                                 }
514                                 catch (Exception)
515                                 {
516                                         throw new IOException("IO exception during Write.");
517                                 }
518                         }
519
520                         return asyncResult;
521                 }
522
523                 public override int EndRead(IAsyncResult asyncResult)
524                 {
525                         this.checkDisposed();
526
527                         if (asyncResult == null)
528                         {
529                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
530                         }
531
532                         return this.inputBuffer.EndRead(asyncResult);
533                 }
534
535                 public override void EndWrite(IAsyncResult asyncResult)
536                 {
537                         this.checkDisposed();
538
539                         if (asyncResult == null)
540                         {
541                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
542                         }
543
544                         this.innerStream.EndWrite (asyncResult);
545                 }
546
547                 public override void Close()
548                 {
549                         ((IDisposable)this).Dispose();
550                 }
551
552                 public override void Flush()
553                 {
554                         this.checkDisposed();
555
556                         this.innerStream.Flush();
557                 }
558
559                 public int Read(byte[] buffer)
560                 {
561                         return this.Read(buffer, 0, buffer.Length);
562                 }
563
564                 public override int Read(byte[] buffer, int offset, int count)
565                 {
566                         IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
567
568                         return this.EndRead(res);
569                 }
570
571                 public override long Seek(long offset, SeekOrigin origin)
572                 {
573                         throw new NotSupportedException();
574                 }
575                 
576                 public override void SetLength(long value)
577                 {
578                         throw new NotSupportedException();
579                 }
580
581                 public void Write(byte[] buffer)
582                 {
583                         this.Write(buffer, 0, buffer.Length);
584                 }
585
586                 public override void Write(byte[] buffer, int offset, int count)
587                 {
588                         IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null);
589
590                         this.EndWrite(res);
591                 }
592
593                 #endregion
594
595                 #region Misc Methods
596
597                 private void resetBuffer()
598                 {
599                         this.inputBuffer.SetLength(0);
600                         this.inputBuffer.Position = 0;
601                 }
602
603                 private void checkDisposed()
604                 {
605                         if (this.disposed)
606                         {
607                                 throw new ObjectDisposedException("The SslClientStream is closed.");
608                         }
609                 }
610
611                 #endregion
612
613                 #region Handsake Methods
614
615                 /*
616                         Client                                                                                  Server
617
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
632
633                                         Fig. 1 - Message flow for a full handshake              
634                 */
635
636                 private void doHandshake()
637                 {
638                         try
639                         {
640                                 // Reset the context if needed
641                                 if (this.context.HandshakeState != HandshakeState.None)
642                                 {
643                                         this.context.Clear();
644                                 }
645
646                                 // Obtain supported cipher suites
647                                 this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
648
649                                 // Set handshake state
650                                 this.context.HandshakeState = HandshakeState.Started;
651
652                                 // Receive Client Hello message\r
653                                 this.protocol.ReceiveRecord (this.innerStream);
654
655                                 // If received message is not an ClientHello send a
656                                 // Fatal Alert
657                                 if (this.context.LastHandshakeMsg != HandshakeType.ClientHello)
658                                 {
659                                         this.protocol.SendAlert(AlertDescription.UnexpectedMessage);
660                                 }
661
662                                 // Send ServerHello message
663                                 this.protocol.SendRecord(HandshakeType.ServerHello);
664
665                                 // Send ServerCertificate message
666                                 this.protocol.SendRecord(HandshakeType.Certificate);
667
668                                 // If the negotiated cipher is a KeyEx cipher send ServerKeyExchange
669                                 if (this.context.Cipher.ExchangeAlgorithmType == ExchangeAlgorithmType.RsaKeyX)
670                                 {
671                                         this.protocol.SendRecord(HandshakeType.ServerKeyExchange);
672                                 }
673
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)
678                                 {
679                                         this.protocol.SendRecord(HandshakeType.CertificateRequest);
680                                 }
681
682                                 // Send ServerHelloDone message
683                                 this.protocol.SendRecord(HandshakeType.ServerHelloDone);
684
685                                 // Receive client response, until the Client Finished message
686                                 // is received
687                                 while (this.context.LastHandshakeMsg != HandshakeType.Finished)
688                                 {\r
689                                         this.protocol.ReceiveRecord (this.innerStream);
690                                 }
691                                 
692                                 // Send ChangeCipherSpec and ServerFinished messages
693                                 this.protocol.SendChangeCipherSpec();
694
695                                 // The handshake is finished
696                                 this.context.HandshakeState = HandshakeState.Finished;
697
698                                 // Clear Key Info
699                                 this.context.ClearKeyInfo();
700                         }
701                         catch (TlsException ex)
702                         {
703                                 this.protocol.SendAlert(ex.Alert);
704                                 this.Close();
705
706                                 throw new IOException("The authentication or decryption has failed.");
707                         }
708                         catch (Exception)
709                         {
710                                 this.protocol.SendAlert(AlertDescription.InternalError);
711                                 this.Close();
712
713                                 throw new IOException("The authentication or decryption has failed.");
714                         }
715                 }
716
717                 #endregion
718
719                 #region Event Methods
720
721                 internal bool RaiseClientCertificateValidation(
722                         X509Certificate certificate, 
723                         int[]                   certificateErrors)
724                 {
725                         if (this.ClientCertValidation != null)
726                         {
727                                 return this.ClientCertValidation(certificate, certificateErrors);
728                         }
729
730                         return (certificateErrors != null && certificateErrors.Length == 0);
731                 }
732
733                 internal AsymmetricAlgorithm RaisePrivateKeySelection(
734                         X509Certificate certificate, 
735                         string                  targetHost)
736                 {
737                         if (this.PrivateKeySelection != null)
738                         {
739                                 return this.PrivateKeySelection(certificate, targetHost);
740                         }
741
742                         return null;
743                 }
744
745                 #endregion
746         }
747 }