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