2004-02-26 Carlos Guzman Alvarez <carlosga@telefonica.net>
[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  * 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:
11  * 
12  * The above copyright notice and this permission notice shall be included 
13  * in all copies or substantial portions of the Software.
14  * 
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.
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.Alerts;
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                 
43                 #endregion
44
45                 #region Fields
46
47                 private CertificateValidationCallback   clientCertValidationDelegate;
48
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;
56                 private object                                  read;
57                 private object                                  write;          
58
59                 #endregion
60
61                 #region Properties
62
63                 public override bool CanRead
64                 {
65                         get { return this.innerStream.CanRead; }
66                 }
67
68                 public override bool CanWrite
69                 {
70                         get { return this.innerStream.CanWrite; }
71                 }
72
73                 public override bool CanSeek
74                 {
75                         get { return this.innerStream.CanSeek; }
76                 }
77
78                 public override long Length
79                 {
80                         get { throw new NotSupportedException(); }
81                 }
82
83                 public override long Position
84                 {
85                         get { throw new NotSupportedException(); }
86                         set { throw new NotSupportedException(); }
87                 }
88
89                 #endregion
90
91                 #region Security Properties
92
93                 public bool CheckCertRevocationStatus 
94                 {
95                         get { return this.checkCertRevocationStatus ; }
96                         set { this.checkCertRevocationStatus = value; }
97                 }
98
99                 public CipherAlgorithmType CipherAlgorithm 
100                 {
101                         get 
102                         { 
103                                 if (this.context.HandshakeState == HandshakeState.Finished)
104                                 {
105                                         return this.context.Cipher.CipherAlgorithmType;
106                                 }
107
108                                 return CipherAlgorithmType.None;
109                         }
110                 }
111
112                 public int CipherStrength 
113                 {
114                         get 
115                         { 
116                                 if (this.context.HandshakeState == HandshakeState.Finished)
117                                 {
118                                         return this.context.Cipher.EffectiveKeyBits;
119                                 }
120
121                                 return 0;
122                         }
123                 }
124                 
125                 public X509Certificate ClientCertificate
126                 {
127                         get 
128                         { 
129                                 if (this.context.HandshakeState == HandshakeState.Finished)
130                                 {
131                                         return this.context.ClientSettings.ClientCertificate;
132                                 }
133
134                                 return null;
135                         }
136                 }               
137                 
138                 public HashAlgorithmType HashAlgorithm 
139                 {
140                         get 
141                         { 
142                                 if (this.context.HandshakeState == HandshakeState.Finished)
143                                 {
144                                         return this.context.Cipher.HashAlgorithmType; 
145                                 }
146
147                                 return HashAlgorithmType.None;
148                         }
149                 }
150                 
151                 public int HashStrength
152                 {
153                         get 
154                         { 
155                                 if (this.context.HandshakeState == HandshakeState.Finished)
156                                 {
157                                         return this.context.Cipher.HashSize * 8; 
158                                 }
159
160                                 return 0;
161                         }
162                 }
163                 
164                 public int KeyExchangeStrength 
165                 {
166                         get 
167                         { 
168                                 if (this.context.HandshakeState == HandshakeState.Finished)
169                                 {
170                                         return this.context.ServerSettings.Certificates[0].RSA.KeySize;
171                                 }
172
173                                 return 0;
174                         }
175                 }
176                 
177                 public ExchangeAlgorithmType KeyExchangeAlgorithm 
178                 {
179                         get 
180                         { 
181                                 if (this.context.HandshakeState == HandshakeState.Finished)
182                                 {
183                                         return this.context.Cipher.ExchangeAlgorithmType; 
184                                 }
185
186                                 return ExchangeAlgorithmType.None;
187                         }
188                 }
189                 
190                 public SecurityProtocolType SecurityProtocol 
191                 {
192                         get 
193                         { 
194                                 if (this.context.HandshakeState == HandshakeState.Finished)
195                                 {
196                                         return this.context.SecurityProtocol; 
197                                 }
198
199                                 return 0;
200                         }
201                 }
202
203                 public X509Certificate ServerCertificate 
204                 {
205                         get 
206                         { 
207                                 if (this.context.HandshakeState == HandshakeState.Finished)
208                                 {
209                                         if (this.context.ServerSettings.Certificates != null &&
210                                                 this.context.ServerSettings.Certificates.Count > 0)
211                                         {
212                                                 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
213                                         }
214                                 }
215
216                                 return null;
217                         }
218                 } 
219
220                 #endregion
221
222                 #region Callback Properties
223
224                 public CertificateValidationCallback ClientCertValidationDelegate 
225                 {
226                         get { return this.clientCertValidationDelegate; }
227                         set 
228                         { 
229                                 if (this.ClientCertValidation != null)
230                                 {
231                                         this.ClientCertValidation -= this.clientCertValidationDelegate;
232                                 }
233                                 this.clientCertValidationDelegate       = value;
234                                 this.ClientCertValidation                       += this.clientCertValidationDelegate;
235                         }
236                 }
237
238                 #endregion
239
240                 #region Constructors
241
242                 public SslServerStream(
243                         Stream                  stream, 
244                         X509Certificate serverCertificate) : this(
245                         stream, 
246                         serverCertificate, 
247                         false, 
248                         false, 
249                         SecurityProtocolType.Default)
250                 {
251                 }
252
253                 public SslServerStream(
254                         Stream                  stream,
255                         X509Certificate serverCertificate,
256                         bool                    clientCertificateRequired,
257                         bool                    ownsStream): this(
258                         stream, 
259                         serverCertificate, 
260                         clientCertificateRequired, 
261                         ownsStream, 
262                         SecurityProtocolType.Default)
263                 {
264                 }
265
266                 public SslServerStream(
267                         Stream                                  stream,
268                         X509Certificate                 serverCertificate,
269                         bool                                    clientCertificateRequired,
270                         bool                                    ownsStream,
271                         SecurityProtocolType    securityProtocolType)
272                 {
273                         if (stream == null)
274                         {
275                                 throw new ArgumentNullException("stream is null.");
276                         }
277                         if (!stream.CanRead || !stream.CanWrite)
278                         {
279                                 throw new ArgumentNullException("stream is not both readable and writable.");
280                         }
281
282                         this.context = new ServerContext(
283                                 this,
284                                 securityProtocolType,
285                                 serverCertificate,
286                                 clientCertificateRequired);
287
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);
294                 }
295
296                 #endregion
297
298                 #region Finalizer
299
300                 ~SslServerStream()
301                 {
302                         this.Dispose(false);
303                 }
304
305                 #endregion
306
307                 #region IDisposable Methods
308
309                 void IDisposable.Dispose()
310                 {
311                         this.Dispose(true);
312                         GC.SuppressFinalize(this);
313                 }
314
315                 protected virtual void Dispose(bool disposing)
316                 {
317                         if (!this.disposed)
318                         {
319                                 if (disposing)
320                                 {
321                                         if (this.innerStream != null)
322                                         {
323                                                 if (this.context.HandshakeState == HandshakeState.Finished)
324                                                 {
325                                                         // Write close notify
326                                                         this.protocol.SendAlert(TlsAlertDescription.CloseNotify);
327                                                 }
328
329                                                 if (this.ownsStream)
330                                                 {
331                                                         // Close inner stream
332                                                         this.innerStream.Close();
333                                                 }
334                                         }
335                                         this.ownsStream         = false;
336                                         this.innerStream        = null;
337                                         if (this.ClientCertValidation != null)
338                                         {
339                                                 this.ClientCertValidation -= this.clientCertValidationDelegate;
340                                         }
341                                         this.clientCertValidationDelegate       = null;
342                                 }
343
344                                 this.disposed = true;
345                         }
346                 }
347
348                 #endregion
349
350                 #region Methods
351
352                 public override IAsyncResult BeginRead(
353                         byte[]                  buffer,
354                         int                             offset,
355                         int                             count,
356                         AsyncCallback   callback,
357                         object                  state)
358                 {
359                         this.checkDisposed();
360                         
361                         if (buffer == null)
362                         {
363                                 throw new ArgumentNullException("buffer is a null reference.");
364                         }
365                         if (offset < 0)
366                         {
367                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
368                         }
369                         if (offset > buffer.Length)
370                         {
371                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
372                         }
373                         if (count < 0)
374                         {
375                                 throw new ArgumentOutOfRangeException("count is less than 0.");
376                         }
377                         if (count > (buffer.Length - offset))
378                         {
379                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
380                         }
381
382                         lock (this)
383                         {
384                                 if (this.context.HandshakeState == HandshakeState.None)
385                                 {
386                                         this.doHandshake();     // Handshake negotiation
387                                 }
388                         }
389
390                         IAsyncResult asyncResult;
391
392                         lock (this.read)
393                         {
394                                 try
395                                 {
396                                         // If actual buffer is full readed reset it
397                                         if (this.inputBuffer.Position == this.inputBuffer.Length &&
398                                                 this.inputBuffer.Length > 0)
399                                         {
400                                                 this.resetBuffer();
401                                         }
402
403                                         if (!this.context.ConnectionEnd)
404                                         {
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)
408                                                 {
409                                                         // Read next record and write it into the inputBuffer
410                                                         long    position        = this.inputBuffer.Position;                                    
411                                                         byte[]  record          = this.protocol.ReceiveRecord();
412                                         
413                                                         if (record != null && record.Length > 0)
414                                                         {
415                                                                 // Write new data to the inputBuffer
416                                                                 this.inputBuffer.Seek(0, SeekOrigin.End);
417                                                                 this.inputBuffer.Write(record, 0, record.Length);
418
419                                                                 // Restore buffer position
420                                                                 this.inputBuffer.Seek(position, SeekOrigin.Begin);
421                                                         }
422                                                         else
423                                                         {
424                                                                 if (record == null)
425                                                                 {
426                                                                         break;
427                                                                 }
428                                                         }
429
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
433                                                         // for read
434                                                         if (this.innerStream is NetworkStream &&
435                                                                 !((NetworkStream)this.innerStream).DataAvailable)
436                                                         {
437                                                                 break;
438                                                         }
439                                                 }
440                                         }
441
442                                         asyncResult = this.inputBuffer.BeginRead(
443                                                 buffer, offset, count, callback, state);
444                                 }
445                                 catch (TlsException)
446                                 {
447                                         throw new IOException("The authentication or decryption has failed.");
448                                 }
449                                 catch (Exception)
450                                 {
451                                         throw new IOException("IO exception during read.");
452                                 }
453                         }
454
455                         return asyncResult;
456                 }
457
458                 public override IAsyncResult BeginWrite(
459                         byte[]                  buffer,
460                         int                             offset,
461                         int                             count,
462                         AsyncCallback   callback,
463                         object                  state)
464                 {
465                         this.checkDisposed();
466
467                         if (buffer == null)
468                         {
469                                 throw new ArgumentNullException("buffer is a null reference.");
470                         }
471                         if (offset < 0)
472                         {
473                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
474                         }
475                         if (offset > buffer.Length)
476                         {
477                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
478                         }
479                         if (count < 0)
480                         {
481                                 throw new ArgumentOutOfRangeException("count is less than 0.");
482                         }
483                         if (count > (buffer.Length - offset))
484                         {
485                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
486                         }
487
488                         lock (this)
489                         {
490                                 if (this.context.HandshakeState == HandshakeState.None)
491                                 {
492                                         // Start handshake negotiation
493                                         this.doHandshake();
494                                 }
495                         }
496
497                         IAsyncResult asyncResult;
498
499                         lock (this.write)
500                         {
501                                 try
502                                 {
503                                         // Send the buffer as a TLS record
504                                         
505                                         byte[] record = this.protocol.EncodeRecord(
506                                                 TlsContentType.ApplicationData, buffer, offset, count);
507                                 
508                                         asyncResult = this.innerStream.BeginWrite(
509                                                 record, 0, record.Length, callback, state);
510                                 }
511                                 catch (TlsException)
512                                 {
513                                         throw new IOException("The authentication or decryption has failed.");
514                                 }
515                                 catch (Exception)
516                                 {
517                                         throw new IOException("IO exception during Write.");
518                                 }
519                         }
520
521                         return asyncResult;
522                 }
523
524                 public override int EndRead(IAsyncResult asyncResult)
525                 {
526                         this.checkDisposed();
527
528                         if (asyncResult == null)
529                         {
530                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
531                         }
532
533                         return this.inputBuffer.EndRead(asyncResult);
534                 }
535
536                 public override void EndWrite(IAsyncResult asyncResult)
537                 {
538                         this.checkDisposed();
539
540                         if (asyncResult == null)
541                         {
542                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
543                         }
544
545                         this.innerStream.EndWrite (asyncResult);
546                 }
547
548                 public override void Close()
549                 {
550                         ((IDisposable)this).Dispose();
551                 }
552
553                 public override void Flush()
554                 {
555                         this.checkDisposed();
556
557                         this.innerStream.Flush();
558                 }
559
560                 public int Read(byte[] buffer)
561                 {
562                         return this.Read(buffer, 0, buffer.Length);
563                 }
564
565                 public override int Read(byte[] buffer, int offset, int count)
566                 {
567                         IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
568
569                         return this.EndRead(res);
570                 }
571
572                 public override long Seek(long offset, SeekOrigin origin)
573                 {
574                         throw new NotSupportedException();
575                 }
576                 
577                 public override void SetLength(long value)
578                 {
579                         throw new NotSupportedException();
580                 }
581
582                 public void Write(byte[] buffer)
583                 {
584                         this.Write(buffer, 0, buffer.Length);
585                 }
586
587                 public override void Write(byte[] buffer, int offset, int count)
588                 {
589                         IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null);
590
591                         this.EndWrite(res);
592                 }
593
594                 #endregion
595
596                 #region Misc Methods
597
598                 private void resetBuffer()
599                 {
600                         this.inputBuffer.SetLength(0);
601                         this.inputBuffer.Position = 0;
602                 }
603
604                 private void checkDisposed()
605                 {
606                         if (this.disposed)
607                         {
608                                 throw new ObjectDisposedException("The SslClientStream is closed.");
609                         }
610                 }
611
612                 #endregion
613
614                 #region Handsake Methods
615
616                 /*
617                         Client                                                                                  Server
618
619                         ClientHello                 -------->
620                                                                                                                         ServerHello
621                                                                                                                         Certificate*
622                                                                                                                         ServerKeyExchange*
623                                                                                                                         CertificateRequest*
624                                                                                 <--------                       ServerHelloDone
625                         Certificate*
626                         ClientKeyExchange
627                         CertificateVerify*
628                         [ChangeCipherSpec]
629                         Finished                    -------->
630                                                                                                                         [ChangeCipherSpec]
631                                                                                 <--------           Finished
632                         Application Data            <------->                   Application Data
633
634                                         Fig. 1 - Message flow for a full handshake              
635                 */
636
637                 private void doHandshake()
638                 {
639                         try
640                         {
641 #warning "Implement server handshake logic"
642
643                                 // Obtain supported cipher suites
644                                 this.context.SupportedCiphers = TlsCipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
645
646                                 // Clear Key Info
647                                 this.context.ClearKeyInfo();
648
649                                 throw new NotSupportedException();
650                         }
651                         catch
652                         {
653                                 throw new IOException("The authentication or decryption has failed.");
654                         }
655                 }
656
657                 #endregion
658         }
659 }