Convert blocking operations in HttpWebRequest and SslClientStream to non-blocking...
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / RecordProtocol.cs
index 7806a39bcc0e7013ac5841264227475a66f544da..166f12f0d23c7d0790fc722c2d140494f75b1b11 100644 (file)
@@ -1,6 +1,6 @@
 // Transport Security Layer (TLS)
 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
-// Copyright (C) 2006 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2006-2007 Novell, Inc (http://www.novell.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -35,6 +35,8 @@ namespace Mono.Security.Protocol.Tls
        {
                #region Fields
 
+               private static ManualResetEvent record_processing = new ManualResetEvent (true);
+
                protected Stream        innerStream;
                protected Context       context;
 
@@ -309,13 +311,14 @@ namespace Mono.Security.Protocol.Tls
 
                public IAsyncResult BeginReceiveRecord(Stream record, AsyncCallback callback, object state)
                {
-                       if (this.context.ConnectionEnd)
+                       if (this.context.ReceivedConnectionEnd)
                        {
                                throw new TlsException(
                                        AlertDescription.InternalError,
                                        "The session is finished and it's no longer valid.");
                        }
 
+                       record_processing.Reset ();
                        byte[] recordTypeBuffer = new byte[1];
 
                        ReceiveRecordAsyncResult internalResult = new ReceiveRecordAsyncResult(callback, state, recordTypeBuffer, record);
@@ -426,16 +429,96 @@ namespace Mono.Security.Protocol.Tls
 
                        if (internalResult.CompletedWithError)
                                throw internalResult.AsyncException;
-                       else
-                               return internalResult.ResultingBuffer;
+
+                       byte[] result = internalResult.ResultingBuffer;
+                       record_processing.Set ();
+                       return result;
                }
 
                public byte[] ReceiveRecord(Stream record)
                {
+                       if (this.context.ReceivedConnectionEnd)
+                       {
+                               throw new TlsException(
+                                       AlertDescription.InternalError,
+                                       "The session is finished and it's no longer valid.");
+                       }
+
+                       record_processing.Reset ();
+                       byte[] recordTypeBuffer = new byte[1];
+
+                       int bytesRead = record.Read(recordTypeBuffer, 0, recordTypeBuffer.Length);
+
+                       //We're at the end of the stream. Time to bail.
+                       if (bytesRead == 0)
+                       {
+                               return null;
+                       }
+
+                       // Try to read the Record Content Type
+                       int type = recordTypeBuffer[0];
+
+                       // Set last handshake message received to None
+                       this.context.LastHandshakeMsg = HandshakeType.ClientHello;
+
+                       ContentType     contentType     = (ContentType)type;
+                       byte[] buffer = this.ReadRecordBuffer(type, record);
+                       if (buffer == null)
+                       {
+                               // record incomplete (at the moment)
+                               return null;
+                       }
+
+                       // Decrypt message contents if needed
+                       if (contentType == ContentType.Alert && buffer.Length == 2)
+                       {
+                       }
+                       else if ((this.Context.Read != null) && (this.Context.Read.Cipher != null))
+                       {
+                               buffer = this.decryptRecordFragment (contentType, buffer);
+                               DebugHelper.WriteLine ("Decrypted record data", buffer);
+                       }
+
+                       // Process record
+                       switch (contentType)
+                       {
+                       case ContentType.Alert:
+                               this.ProcessAlert((AlertLevel)buffer [0], (AlertDescription)buffer [1]);
+                               if (record.CanSeek) 
+                               {
+                                       // don't reprocess that memory block
+                                       record.SetLength (0); 
+                               }
+                               buffer = null;
+                               break;
+
+                       case ContentType.ChangeCipherSpec:
+                               this.ProcessChangeCipherSpec();
+                               break;
+
+                       case ContentType.ApplicationData:
+                               break;
+
+                       case ContentType.Handshake:
+                               TlsStream message = new TlsStream (buffer);
+                               while (!message.EOF)
+                               {
+                                       this.ProcessHandshakeMessage(message);
+                               }
+                               break;
 
-                       IAsyncResult ar = this.BeginReceiveRecord(record, null, null);
-                       return this.EndReceiveRecord(ar);
+                       case (ContentType)0x80:
+                               this.context.HandshakeMessages.Write (buffer);
+                               break;
 
+                       default:
+                               throw new TlsException(
+                                       AlertDescription.UnexpectedMessage,
+                                       "Unknown record received from server.");
+                       }
+
+                       record_processing.Set ();
+                       return buffer;
                }
 
                private byte[] ReadRecordBuffer (int contentType, Stream record)
@@ -575,7 +658,7 @@ namespace Mono.Security.Protocol.Tls
                                switch (alertDesc)
                                {
                                        case AlertDescription.CloseNotify:
-                                               this.context.ConnectionEnd = true;
+                                               this.context.ReceivedConnectionEnd = true;
                                                break;
                                }
                                break;
@@ -619,9 +702,8 @@ namespace Mono.Security.Protocol.Tls
                        // Write record
                        this.SendRecord (ContentType.Alert, new byte[2] { (byte) level, (byte) description });
 
-                       if (close)
-                       {
-                               this.context.ConnectionEnd = true;
+                       if (close) {
+                               this.context.SentConnectionEnd = true;
                        }
                }
 
@@ -651,6 +733,57 @@ namespace Mono.Security.Protocol.Tls
                        }
                }
 
+               public void SendChangeCipherSpec(Stream recordStream)
+               {
+                       DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
+
+                       byte[] record = this.EncodeRecord (ContentType.ChangeCipherSpec, new byte[] { 1 });
+
+                       // Send Change Cipher Spec message with the current cipher
+                       // or as plain text if this is the initial negotiation
+                       recordStream.Write(record, 0, record.Length);
+
+                       Context ctx = this.context;
+
+                       // Reset sequence numbers
+                       ctx.WriteSequenceNumber = 0;
+
+                       // all further data sent will be encrypted with the negotiated
+                       // security parameters (now the current parameters)
+                       if (ctx is ClientContext) {
+                               ctx.StartSwitchingSecurityParameters (true);
+                       } else {
+                               ctx.EndSwitchingSecurityParameters (false);
+                       }
+               }
+
+               public IAsyncResult BeginSendChangeCipherSpec(AsyncCallback callback, object state)
+               {
+                       DebugHelper.WriteLine (">>>> Write Change Cipher Spec");
+
+                       // Send Change Cipher Spec message with the current cipher
+                       // or as plain text if this is the initial negotiation
+                       return this.BeginSendRecord (ContentType.ChangeCipherSpec, new byte[] { 1 }, callback, state);
+               }
+
+               public void EndSendChangeCipherSpec (IAsyncResult asyncResult)
+               {
+                       this.EndSendRecord (asyncResult);
+
+                       Context ctx = this.context;
+
+                       // Reset sequence numbers
+                       ctx.WriteSequenceNumber = 0;
+
+                       // all further data sent will be encrypted with the negotiated
+                       // security parameters (now the current parameters)
+                       if (ctx is ClientContext) {
+                               ctx.StartSwitchingSecurityParameters (true);
+                       } else {
+                               ctx.EndSwitchingSecurityParameters (false);
+                       }
+               }
+
                public IAsyncResult BeginSendRecord(HandshakeType handshakeType, AsyncCallback callback, object state)
                {
                        HandshakeMessage msg = this.GetMessage(handshakeType);
@@ -690,7 +823,7 @@ namespace Mono.Security.Protocol.Tls
 
                public IAsyncResult BeginSendRecord(ContentType contentType, byte[] recordData, AsyncCallback callback, object state)
                {
-                       if (this.context.ConnectionEnd)
+                       if (this.context.SentConnectionEnd)
                        {
                                throw new TlsException(
                                        AlertDescription.InternalError,
@@ -740,7 +873,7 @@ namespace Mono.Security.Protocol.Tls
                        int                     offset,
                        int                     count)
                {
-                       if (this.context.ConnectionEnd)
+                       if (this.context.SentConnectionEnd)
                        {
                                throw new TlsException(
                                        AlertDescription.InternalError,
@@ -789,7 +922,22 @@ namespace Mono.Security.Protocol.Tls
 
                        return record.ToArray();
                }
-               
+
+               public byte[] EncodeHandshakeRecord(HandshakeType handshakeType)
+               {
+                       HandshakeMessage msg = this.GetMessage(handshakeType);
+
+                       msg.Process();
+
+                       var bytes = this.EncodeRecord (msg.ContentType, msg.EncodeMessage ());
+
+                       msg.Update();
+
+                       msg.Reset();
+
+                       return bytes;
+               }
+                               
                #endregion
 
                #region Cryptography Methods
@@ -838,7 +986,6 @@ namespace Mono.Security.Protocol.Tls
                                {
                                        this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
                                }
-
                                throw;
                        }