
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
/* Transport Security Layer (TLS)
 * Copyright (c) 2003-2004 Carlos Guzman Alvarez
 * 
 * Permission is hereby granted, free of charge, to any person 
 * obtaining a copy of this software and associated documentation 
 * files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, 
 * publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, 
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 */

using System;
using System.Text;
using System.Collections;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

using Mono.Security.Cryptography;
using Mono.Security.Protocol.Tls.Handshake;

namespace Mono.Security.Protocol.Tls
{
	internal abstract class Context
	{
		#region Internal Constants

		internal const short MAX_FRAGMENT_SIZE	= 16384; // 2^14
		internal const short TLS1_PROTOCOL_CODE = (0x03 << 8) | 0x01;
		internal const short SSL3_PROTOCOL_CODE = (0x03 << 8) | 0x00;
		internal const long  UNIX_BASE_TICKS	= 621355968000000000;

		#endregion

		#region Fields
		
		// Protocol version
		private SecurityProtocolType securityProtocol;
		
		// Sesison ID
		private byte[] sessionId;

		// Compression method
		private SecurityCompressionType compressionMethod;

		// Information sent and request by the server in the Handshake protocol
		private TlsServerSettings serverSettings;

		// Client configuration
		private TlsClientSettings clientSettings;

		// Cipher suite information
		private CipherSuite				cipher;
		private CipherSuiteCollection	supportedCiphers;

		// Last handshake message received
		private HandshakeType lastHandshakeMsg;

		// Handshake negotiation state
		private	HandshakeState handshakeState;

		// Misc
		private bool	isActual;
		private bool	connectionEnd;
		private bool	protocolNegotiated;
		
		// Sequence numbers
		private long	writeSequenceNumber;
		private long	readSequenceNumber;

		// Random data
		private byte[]	clientRandom;
		private byte[]	serverRandom;
		private byte[]	randomCS;
		private byte[]	randomSC;

		// Key information
		private byte[]	masterSecret;
		private byte[]	clientWriteMAC;
		private byte[]	serverWriteMAC;
		private byte[]	clientWriteKey;
		private byte[]	serverWriteKey;
		private byte[]	clientWriteIV;
		private byte[]	serverWriteIV;
		
		// Handshake hashes
		private TlsStream handshakeMessages;
		
		// Secure Random generator		
		private RandomNumberGenerator random;

		// Record protocol
		private RecordProtocol recordProtocol;

		#endregion

		#region Properties

		public bool	ProtocolNegotiated
		{
			get { return this.protocolNegotiated; }
			set { this.protocolNegotiated = value; }
		}

		public SecurityProtocolType SecurityProtocol
		{
			get 
			{
				if ((this.securityProtocol & SecurityProtocolType.Tls) == SecurityProtocolType.Tls ||	
					(this.securityProtocol & SecurityProtocolType.Default) == SecurityProtocolType.Default)
				{
					return SecurityProtocolType.Tls;
				}
				else
				{
					if ((this.securityProtocol & SecurityProtocolType.Ssl3) == SecurityProtocolType.Ssl3)
					{
						return SecurityProtocolType.Ssl3;
					}
				}

				throw new NotSupportedException("Unsupported security protocol type");
			}

			set { this.securityProtocol = value; }
		}

		public SecurityProtocolType SecurityProtocolFlags
		{
			get { return this.securityProtocol; }
		}

		public short Protocol
		{
			get 
			{ 
				switch (this.SecurityProtocol)
				{
					case SecurityProtocolType.Tls:
					case SecurityProtocolType.Default:
						return Context.TLS1_PROTOCOL_CODE;

					case SecurityProtocolType.Ssl3:
						return Context.SSL3_PROTOCOL_CODE;

					case SecurityProtocolType.Ssl2:
					default:
						throw new NotSupportedException("Unsupported security protocol type");
				}
			}
		}

		public byte[] SessionId
		{
			get { return this.sessionId; }
			set { this.sessionId = value; }
		}

		public SecurityCompressionType CompressionMethod
		{
			get { return this.compressionMethod; }
			set { this.compressionMethod = value; }
		}

		public TlsServerSettings ServerSettings
		{
			get { return this.serverSettings; }
		}

		public TlsClientSettings ClientSettings
		{
			get { return this.clientSettings; }
		}

		public bool	IsActual
		{
			get { return this.isActual; }
			set { this.isActual = value; }
		}

		public HandshakeType LastHandshakeMsg
		{
			get { return this.lastHandshakeMsg; }
			set { this.lastHandshakeMsg = value; }
		}

		public	HandshakeState HandshakeState
		{
			get { return this.handshakeState; }
			set { this.handshakeState = value; }
		}

		public bool ConnectionEnd
		{
			get { return this.connectionEnd; }
			set { this.connectionEnd = value; }
		}

		public CipherSuite Cipher
		{
			get { return this.cipher; }
			set 
			{ 
				this.cipher			= value; 
				this.cipher.Context = this;
			}
		}

		public CipherSuiteCollection SupportedCiphers
		{
			get { return supportedCiphers; }
			set { supportedCiphers = value; }
		}

		public TlsStream HandshakeMessages
		{
			get { return this.handshakeMessages; }
		}

		public long WriteSequenceNumber
		{
			get { return this.writeSequenceNumber; }
			set { this.writeSequenceNumber = value; }
		}

		public long ReadSequenceNumber
		{
			get { return this.readSequenceNumber; }
			set { this.readSequenceNumber = value; }
		}

		public byte[] ClientRandom
		{
			get { return this.clientRandom; }
			set { this.clientRandom = value; }
		}

		public byte[] ServerRandom
		{
			get { return this.serverRandom; }
			set { this.serverRandom = value; }
		}

		public byte[] RandomCS
		{
			get { return this.randomCS; }
			set { this.randomCS = value; }
		}

		public byte[] RandomSC
		{
			get { return this.randomSC; }
			set { this.randomSC = value; }
		}

		public byte[] MasterSecret
		{
			get { return this.masterSecret; }
			set { this.masterSecret = value; }
		}

		public byte[] ClientWriteMAC
		{
			get { return this.clientWriteMAC; }
			set { this.clientWriteMAC = value; }
		}

		public byte[] ServerWriteMAC
		{
			get { return this.serverWriteMAC; }
			set { this.serverWriteMAC = value; }
		}

		public byte[] ClientWriteKey
		{
			get { return this.clientWriteKey; }
			set { this.clientWriteKey = value; }
		}

		public byte[] ServerWriteKey
		{
			get { return this.serverWriteKey; }
			set { this.serverWriteKey = value; }
		}

		public byte[] ClientWriteIV
		{
			get { return this.clientWriteIV; }
			set { this.clientWriteIV = value; }
		}

		public byte[] ServerWriteIV
		{
			get { return this.serverWriteIV; }
			set { this.serverWriteIV = value; }
		}

		public RecordProtocol RecordProtocol
		{
			get { return this.recordProtocol; }
			set { this.recordProtocol = value; }
		}

		#endregion

		#region Constructors

		public Context(SecurityProtocolType securityProtocolType)
		{
			this.SecurityProtocol	= securityProtocolType;
			this.compressionMethod	= SecurityCompressionType.None;
			this.serverSettings		= new TlsServerSettings();
			this.clientSettings		= new TlsClientSettings();
			this.handshakeMessages	= new TlsStream();
			this.sessionId			= null;
			this.handshakeState		= HandshakeState.None;
			this.random				= RandomNumberGenerator.Create();
		}

		#endregion

		#region Methods
		
		public int GetUnixTime()
		{
			DateTime now = DateTime.UtcNow;
																		     
			return (int)(now.Ticks - UNIX_BASE_TICKS / TimeSpan.TicksPerSecond);
		}

		public byte[] GetSecureRandomBytes(int count)
		{
			byte[] secureBytes = new byte[count];

			this.random.GetNonZeroBytes(secureBytes);
			
			return secureBytes;
		}

		public virtual void Clear()
		{
			this.compressionMethod	= SecurityCompressionType.None;
			this.serverSettings		= new TlsServerSettings();
			this.clientSettings		= new TlsClientSettings();
			this.handshakeMessages	= new TlsStream();
			this.sessionId			= null;
			this.handshakeState		= HandshakeState.None;

			this.ClearKeyInfo();
		}

		public virtual void ClearKeyInfo()
		{
			// Clear Master Secret
			this.masterSecret	= null;

			// Clear client and server random
			this.clientRandom	= null;
			this.serverRandom	= null;
			this.randomCS		= null;
			this.randomSC		= null;

			// Clear client keys
			this.clientWriteKey	= null;
			this.clientWriteIV	= null;
			
			// Clear server keys
			this.serverWriteKey	= null;
			this.serverWriteIV	= null;

			// Reset handshake messages
			this.handshakeMessages.Reset();

			// Clear MAC keys if protocol is different than Ssl3
			if (this.securityProtocol != SecurityProtocolType.Ssl3)
			{
				this.clientWriteMAC = null;
				this.serverWriteMAC = null;
			}
		}

		public SecurityProtocolType DecodeProtocolCode(short code)
		{
			switch (code)
			{
				case Context.TLS1_PROTOCOL_CODE:
					return SecurityProtocolType.Tls;

				case Context.SSL3_PROTOCOL_CODE:
					return SecurityProtocolType.Ssl3;

				default:
					throw new NotSupportedException("Unsupported security protocol type");
			}
		}

		#endregion
	}
}
