#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
using System;
using System.Collections;
using System.IO;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
namespace Org.BouncyCastle.Crypto.Tls
{
public abstract class TlsProtocol
{
private static readonly string TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack";
/*
* Our Connection states
*/
protected const short CS_START = 0;
protected const short CS_CLIENT_HELLO = 1;
protected const short CS_SERVER_HELLO = 2;
protected const short CS_SERVER_SUPPLEMENTAL_DATA = 3;
protected const short CS_SERVER_CERTIFICATE = 4;
protected const short CS_CERTIFICATE_STATUS = 5;
protected const short CS_SERVER_KEY_EXCHANGE = 6;
protected const short CS_CERTIFICATE_REQUEST = 7;
protected const short CS_SERVER_HELLO_DONE = 8;
protected const short CS_CLIENT_SUPPLEMENTAL_DATA = 9;
protected const short CS_CLIENT_CERTIFICATE = 10;
protected const short CS_CLIENT_KEY_EXCHANGE = 11;
protected const short CS_CERTIFICATE_VERIFY = 12;
protected const short CS_CLIENT_FINISHED = 13;
protected const short CS_SERVER_SESSION_TICKET = 14;
protected const short CS_SERVER_FINISHED = 15;
protected const short CS_END = 16;
/*
* Different modes to handle the known IV weakness
*/
protected const short ADS_MODE_1_Nsub1 = 0; // 1/n-1 record splitting
protected const short ADS_MODE_0_N = 1; // 0/n record splitting
protected const short ADS_MODE_0_N_FIRSTONLY = 2; // 0/n record splitting on first data fragment only
/*
* Queues for data from some protocols.
*/
private ByteQueue mApplicationDataQueue = new ByteQueue();
private ByteQueue mAlertQueue = new ByteQueue(2);
private ByteQueue mHandshakeQueue = new ByteQueue();
// private ByteQueue mHeartbeatQueue = new ByteQueue();
/*
* The Record Stream we use
*/
internal RecordStream mRecordStream;
protected SecureRandom mSecureRandom;
private TlsStream mTlsStream = null;
private volatile bool mClosed = false;
private volatile bool mFailedWithError = false;
private volatile bool mAppDataReady = false;
private volatile bool mAppDataSplitEnabled = true;
private volatile int mAppDataSplitMode = ADS_MODE_1_Nsub1;
private byte[] mExpectedVerifyData = null;
protected TlsSession mTlsSession = null;
protected SessionParameters mSessionParameters = null;
protected SecurityParameters mSecurityParameters = null;
protected Certificate mPeerCertificate = null;
protected int[] mOfferedCipherSuites = null;
protected byte[] mOfferedCompressionMethods = null;
protected IDictionary mClientExtensions = null;
protected IDictionary mServerExtensions = null;
protected short mConnectionState = CS_START;
protected bool mResumedSession = false;
protected bool mReceivedChangeCipherSpec = false;
protected bool mSecureRenegotiation = false;
protected bool mAllowCertificateStatus = false;
protected bool mExpectSessionTicket = false;
protected bool mBlocking = true;
protected ByteQueueStream mInputBuffers = null;
protected ByteQueueStream mOutputBuffer = null;
public TlsProtocol(Stream stream, SecureRandom secureRandom)
: this(stream, stream, secureRandom)
{
}
public TlsProtocol(Stream input, Stream output, SecureRandom secureRandom)
{
this.mRecordStream = new RecordStream(this, input, output);
this.mSecureRandom = secureRandom;
}
public TlsProtocol(SecureRandom secureRandom)
{
this.mBlocking = false;
this.mInputBuffers = new ByteQueueStream();
this.mOutputBuffer = new ByteQueueStream();
this.mRecordStream = new RecordStream(this, mInputBuffers, mOutputBuffer);
this.mSecureRandom = secureRandom;
}
protected abstract TlsContext Context { get; }
internal abstract AbstractTlsContext ContextAdmin { get; }
protected abstract TlsPeer Peer { get; }
protected virtual void HandleChangeCipherSpecMessage()
{
}
protected abstract void HandleHandshakeMessage(byte type, byte[] buf);
protected virtual void HandleWarningMessage(byte description)
{
}
protected virtual void ApplyMaxFragmentLengthExtension()
{
if (mSecurityParameters.maxFragmentLength >= 0)
{
if (!MaxFragmentLength.IsValid((byte)mSecurityParameters.maxFragmentLength))
throw new TlsFatalAlert(AlertDescription.internal_error);
int plainTextLimit = 1 << (8 + mSecurityParameters.maxFragmentLength);
mRecordStream.SetPlaintextLimit(plainTextLimit);
}
}
protected virtual void CheckReceivedChangeCipherSpec(bool expected)
{
if (expected != mReceivedChangeCipherSpec)
throw new TlsFatalAlert(AlertDescription.unexpected_message);
}
protected virtual void CleanupHandshake()
{
if (this.mExpectedVerifyData != null)
{
Arrays.Fill(this.mExpectedVerifyData, (byte)0);
this.mExpectedVerifyData = null;
}
this.mSecurityParameters.Clear();
this.mPeerCertificate = null;
this.mOfferedCipherSuites = null;
this.mOfferedCompressionMethods = null;
this.mClientExtensions = null;
this.mServerExtensions = null;
this.mResumedSession = false;
this.mReceivedChangeCipherSpec = false;
this.mSecureRenegotiation = false;
this.mAllowCertificateStatus = false;
this.mExpectSessionTicket = false;
}
protected virtual void BlockForHandshake()
{
if (mBlocking)
{
while (this.mConnectionState != CS_END)
{
if (this.mClosed)
{
// TODO What kind of exception/alert?
}
SafeReadRecord();
}
}
}
protected virtual void CompleteHandshake()
{
try
{
this.mRecordStream.FinaliseHandshake();
this.mAppDataSplitEnabled = !TlsUtilities.IsTlsV11(Context);
/*
* If this was an initial handshake, we are now ready to send and receive application data.
*/
if (!mAppDataReady)
{
this.mAppDataReady = true;
if (mBlocking)
{
this.mTlsStream = new TlsStream(this);
}
}
if (this.mTlsSession != null)
{
if (this.mSessionParameters == null)
{
this.mSessionParameters = new SessionParameters.Builder()
.SetCipherSuite(this.mSecurityParameters.CipherSuite)
.SetCompressionAlgorithm(this.mSecurityParameters.CompressionAlgorithm)
.SetMasterSecret(this.mSecurityParameters.MasterSecret)
.SetPeerCertificate(this.mPeerCertificate)
.SetPskIdentity(this.mSecurityParameters.PskIdentity)
.SetSrpIdentity(this.mSecurityParameters.SrpIdentity)
// TODO Consider filtering extensions that aren't relevant to resumed sessions
.SetServerExtensions(this.mServerExtensions)
.Build();
this.mTlsSession = new TlsSessionImpl(this.mTlsSession.SessionID, this.mSessionParameters);
}
ContextAdmin.SetResumableSession(this.mTlsSession);
}
Peer.NotifyHandshakeComplete();
}
finally
{
CleanupHandshake();
}
}
protected internal void ProcessRecord(byte protocol, byte[] buf, int offset, int len)
{
/*
* Have a look at the protocol type, and add it to the correct queue.
*/
switch (protocol)
{
case ContentType.alert:
{
mAlertQueue.AddData(buf, offset, len);
ProcessAlert();
break;
}
case ContentType.application_data:
{
if (!mAppDataReady)
throw new TlsFatalAlert(AlertDescription.unexpected_message);
mApplicationDataQueue.AddData(buf, offset, len);
ProcessApplicationData();
break;
}
case ContentType.change_cipher_spec:
{
ProcessChangeCipherSpec(buf, offset, len);
break;
}
case ContentType.handshake:
{
mHandshakeQueue.AddData(buf, offset, len);
ProcessHandshake();
break;
}
case ContentType.heartbeat:
{
if (!mAppDataReady)
throw new TlsFatalAlert(AlertDescription.unexpected_message);
// TODO[RFC 6520]
// mHeartbeatQueue.AddData(buf, offset, len);
// ProcessHeartbeat();
break;
}
default:
/*
* Uh, we don't know this protocol.
*
* RFC2246 defines on page 13, that we should ignore this.
*/
break;
}
}
private void ProcessHandshake()
{
bool read;
do
{
read = false;
/*
* We need the first 4 bytes, they contain type and length of the message.
*/
if (mHandshakeQueue.Available >= 4)
{
byte[] beginning = new byte[4];
mHandshakeQueue.Read(beginning, 0, 4, 0);
byte type = TlsUtilities.ReadUint8(beginning, 0);
int len = TlsUtilities.ReadUint24(beginning, 1);
/*
* Check if we have enough bytes in the buffer to read the full message.
*/
if (mHandshakeQueue.Available >= (len + 4))
{
/*
* Read the message.
*/
byte[] buf = mHandshakeQueue.RemoveData(len, 4);
CheckReceivedChangeCipherSpec(mConnectionState == CS_END || type == HandshakeType.finished);
/*
* RFC 2246 7.4.9. The value handshake_messages includes all handshake messages
* starting at client hello up to, but not including, this finished message.
* [..] Note: [Also,] Hello Request messages are omitted from handshake hashes.
*/
switch (type)
{
case HandshakeType.hello_request:
break;
case HandshakeType.finished:
default:
{
TlsContext ctx = Context;
if (type == HandshakeType.finished
&& this.mExpectedVerifyData == null
&& ctx.SecurityParameters.MasterSecret != null)
{
this.mExpectedVerifyData = CreateVerifyData(!ctx.IsServer);
}
mRecordStream.UpdateHandshakeData(beginning, 0, 4);
mRecordStream.UpdateHandshakeData(buf, 0, len);
break;
}
}
/*
* Now, parse the message.
*/
HandleHandshakeMessage(type, buf);
read = true;
}
}
}
while (read);
}
private void ProcessApplicationData()
{
/*
* There is nothing we need to do here.
*
* This function could be used for callbacks when application data arrives in the future.
*/
}
private void ProcessAlert()
{
while (mAlertQueue.Available >= 2)
{
/*
* An alert is always 2 bytes. Read the alert.
*/
byte[] tmp = mAlertQueue.RemoveData(2, 0);
byte level = tmp[0];
byte description = tmp[1];
Peer.NotifyAlertReceived(level, description);
if (level == AlertLevel.fatal)
{
/*
* RFC 2246 7.2.1. The session becomes unresumable if any connection is terminated
* without proper close_notify messages with level equal to warning.
*/
InvalidateSession();
this.mFailedWithError = true;
this.mClosed = true;
mRecordStream.SafeClose();
throw new IOException(TLS_ERROR_MESSAGE);
}
else
{
/*
* RFC 5246 7.2.1. The other party MUST respond with a close_notify alert of its own
* and close down the connection immediately, discarding any pending writes.
*/
// TODO Can close_notify be a fatal alert?
if (description == AlertDescription.close_notify)
{
HandleClose(false);
}
/*
* If it is just a warning, we continue.
*/
HandleWarningMessage(description);
}
}
}
/**
* This method is called, when a change cipher spec message is received.
*
* @throws IOException If the message has an invalid content or the handshake is not in the correct
* state.
*/
private void ProcessChangeCipherSpec(byte[] buf, int off, int len)
{
for (int i = 0; i < len; ++i)
{
byte message = TlsUtilities.ReadUint8(buf, off + i);
if (message != ChangeCipherSpec.change_cipher_spec)
throw new TlsFatalAlert(AlertDescription.decode_error);
if (this.mReceivedChangeCipherSpec
|| mAlertQueue.Available > 0
|| mHandshakeQueue.Available > 0)
{
throw new TlsFatalAlert(AlertDescription.unexpected_message);
}
mRecordStream.ReceivedReadCipherSpec();
this.mReceivedChangeCipherSpec = true;
HandleChangeCipherSpecMessage();
}
}
protected internal virtual int ApplicationDataAvailable()
{
return mApplicationDataQueue.Available;
}
/**
* Read data from the network. The method will return immediately, if there is still some data
* left in the buffer, or block until some application data has been read from the network.
*
* @param buf The buffer where the data will be copied to.
* @param offset The position where the data will be placed in the buffer.
* @param len The maximum number of bytes to read.
* @return The number of bytes read.
* @throws IOException If something goes wrong during reading data.
*/
protected internal virtual int ReadApplicationData(byte[] buf, int offset, int len)
{
if (len < 1)
return 0;
while (mApplicationDataQueue.Available == 0)
{
/*
* We need to read some data.
*/
if (this.mClosed)
{
if (this.mFailedWithError)
{
/*
* Something went terribly wrong, we should throw an IOException
*/
throw new IOException(TLS_ERROR_MESSAGE);
}
/*
* Connection has been closed, there is no more data to read.
*/
return 0;
}
SafeReadRecord();
}
len = System.Math.Min(len, mApplicationDataQueue.Available);
mApplicationDataQueue.RemoveData(buf, offset, len, 0);
return len;
}
protected virtual void SafeReadRecord()
{
try
{
if (!mRecordStream.ReadRecord())
{
// TODO It would be nicer to allow graceful connection close if between records
// this.FailWithError(AlertLevel.warning, AlertDescription.close_notify);
throw new EndOfStreamException();
}
}
catch (TlsFatalAlert e)
{
if (!mClosed)
{
this.FailWithError(AlertLevel.fatal, e.AlertDescription, "Failed to read record", e);
}
throw e;
}
catch (Exception e)
{
if (!mClosed)
{
this.FailWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to read record", e);
}
throw e;
}
}
protected virtual void SafeWriteRecord(byte type, byte[] buf, int offset, int len)
{
try
{
mRecordStream.WriteRecord(type, buf, offset, len);
}
catch (TlsFatalAlert e)
{
if (!mClosed)
{
this.FailWithError(AlertLevel.fatal, e.AlertDescription, "Failed to write record", e);
}
throw e;
}
catch (Exception e)
{
if (!mClosed)
{
this.FailWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to write record", e);
}
throw e;
}
}
/**
* Send some application data to the remote system.
*
* The method will handle fragmentation internally.
*
* @param buf The buffer with the data.
* @param offset The position in the buffer where the data is placed.
* @param len The length of the data.
* @throws IOException If something goes wrong during sending.
*/
protected internal virtual void WriteData(byte[] buf, int offset, int len)
{
if (this.mClosed)
{
if (this.mFailedWithError)
throw new IOException(TLS_ERROR_MESSAGE);
throw new IOException("Sorry, connection has been closed, you cannot write more data");
}
while (len > 0)
{
/*
* RFC 5246 6.2.1. Zero-length fragments of Application data MAY be sent as they are
* potentially useful as a traffic analysis countermeasure.
*
* NOTE: Actually, implementations appear to have settled on 1/n-1 record splitting.
*/
if (this.mAppDataSplitEnabled)
{
/*
* Protect against known IV attack!
*
* DO NOT REMOVE THIS CODE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE.
*/
switch (mAppDataSplitMode)
{
case ADS_MODE_0_N:
SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0);
break;
case ADS_MODE_0_N_FIRSTONLY:
this.mAppDataSplitEnabled = false;
SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0);
break;
case ADS_MODE_1_Nsub1:
default:
SafeWriteRecord(ContentType.application_data, buf, offset, 1);
++offset;
--len;
break;
}
}
if (len > 0)
{
// Fragment data according to the current fragment limit.
int toWrite = System.Math.Min(len, mRecordStream.GetPlaintextLimit());
SafeWriteRecord(ContentType.application_data, buf, offset, toWrite);
offset += toWrite;
len -= toWrite;
}
}
}
protected virtual void SetAppDataSplitMode(int appDataSplitMode)
{
if (appDataSplitMode < ADS_MODE_1_Nsub1 || appDataSplitMode > ADS_MODE_0_N_FIRSTONLY)
throw new ArgumentException("Illegal appDataSplitMode mode: " + appDataSplitMode, "appDataSplitMode");
this.mAppDataSplitMode = appDataSplitMode;
}
protected virtual void WriteHandshakeMessage(byte[] buf, int off, int len)
{
while (len > 0)
{
// Fragment data according to the current fragment limit.
int toWrite = System.Math.Min(len, mRecordStream.GetPlaintextLimit());
SafeWriteRecord(ContentType.handshake, buf, off, toWrite);
off += toWrite;
len -= toWrite;
}
}
/// The secure bidirectional stream for this connection
/// Only allowed in blocking mode.
public virtual Stream Stream
{
get
{
if (!mBlocking)
throw new InvalidOperationException("Cannot use Stream in non-blocking mode! Use OfferInput()/OfferOutput() instead.");
return this.mTlsStream;
}
}
/**
* Offer input from an arbitrary source. Only allowed in non-blocking mode.
*
* After this method returns, the input buffer is "owned" by this object. Other code
* must not attempt to do anything with it.
*
* This method will decrypt and process all records that are fully available.
* If only part of a record is available, the buffer will be retained until the
* remainder of the record is offered.
*
* If any records containing application data were processed, the decrypted data
* can be obtained using {@link #readInput(byte[], int, int)}. If any records
* containing protocol data were processed, a response may have been generated.
* You should always check to see if there is any available output after calling
* this method by calling {@link #getAvailableOutputBytes()}.
* @param input The input buffer to offer
* @throws IOException If an error occurs while decrypting or processing a record
*/
public virtual void OfferInput(byte[] input)
{
if (mBlocking)
throw new InvalidOperationException("Cannot use OfferInput() in blocking mode! Use Stream instead.");
if (mClosed)
throw new IOException("Connection is closed, cannot accept any more input");
mInputBuffers.Write(input);
// loop while there are enough bytes to read the length of the next record
while (mInputBuffers.Available >= RecordStream.TLS_HEADER_SIZE)
{
byte[] header = new byte[RecordStream.TLS_HEADER_SIZE];
mInputBuffers.Peek(header);
int totalLength = TlsUtilities.ReadUint16(header, RecordStream.TLS_HEADER_LENGTH_OFFSET) + RecordStream.TLS_HEADER_SIZE;
if (mInputBuffers.Available < totalLength)
{
// not enough bytes to read a whole record
break;
}
SafeReadRecord();
}
}
/**
* Gets the amount of received application data. A call to {@link #readInput(byte[], int, int)}
* is guaranteed to be able to return at least this much data.
*
* Only allowed in non-blocking mode.
* @return The number of bytes of available application data
*/
public virtual int GetAvailableInputBytes()
{
if (mBlocking)
throw new InvalidOperationException("Cannot use GetAvailableInputBytes() in blocking mode! Use ApplicationDataAvailable() instead.");
return ApplicationDataAvailable();
}
/**
* Retrieves received application data. Use {@link #getAvailableInputBytes()} to check
* how much application data is currently available. This method functions similarly to
* {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data
* is available, nothing will be copied and zero will be returned.
*
* Only allowed in non-blocking mode.
* @param buffer The buffer to hold the application data
* @param offset The start offset in the buffer at which the data is written
* @param length The maximum number of bytes to read
* @return The total number of bytes copied to the buffer. May be less than the
* length specified if the length was greater than the amount of available data.
*/
public virtual int ReadInput(byte[] buffer, int offset, int length)
{
if (mBlocking)
throw new InvalidOperationException("Cannot use ReadInput() in blocking mode! Use Stream instead.");
return ReadApplicationData(buffer, offset, System.Math.Min(length, ApplicationDataAvailable()));
}
/**
* Offer output from an arbitrary source. Only allowed in non-blocking mode.
*
* After this method returns, the specified section of the buffer will have been
* processed. Use {@link #readOutput(byte[], int, int)} to get the bytes to
* transmit to the other peer.
*
* This method must not be called until after the handshake is complete! Attempting
* to call it before the handshake is complete will result in an exception.
* @param buffer The buffer containing application data to encrypt
* @param offset The offset at which to begin reading data
* @param length The number of bytes of data to read
* @throws IOException If an error occurs encrypting the data, or the handshake is not complete
*/
public virtual void OfferOutput(byte[] buffer, int offset, int length)
{
if (mBlocking)
throw new InvalidOperationException("Cannot use OfferOutput() in blocking mode! Use Stream instead.");
if (!mAppDataReady)
throw new IOException("Application data cannot be sent until the handshake is complete!");
WriteData(buffer, offset, length);
}
/**
* Gets the amount of encrypted data available to be sent. A call to
* {@link #readOutput(byte[], int, int)} is guaranteed to be able to return at
* least this much data.
*
* Only allowed in non-blocking mode.
* @return The number of bytes of available encrypted data
*/
public virtual int GetAvailableOutputBytes()
{
if (mBlocking)
throw new InvalidOperationException("Cannot use GetAvailableOutputBytes() in blocking mode! Use Stream instead.");
return mOutputBuffer.Available;
}
/**
* Retrieves encrypted data to be sent. Use {@link #getAvailableOutputBytes()} to check
* how much encrypted data is currently available. This method functions similarly to
* {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data
* is available, nothing will be copied and zero will be returned.
*
* Only allowed in non-blocking mode.
* @param buffer The buffer to hold the encrypted data
* @param offset The start offset in the buffer at which the data is written
* @param length The maximum number of bytes to read
* @return The total number of bytes copied to the buffer. May be less than the
* length specified if the length was greater than the amount of available data.
*/
public virtual int ReadOutput(byte[] buffer, int offset, int length)
{
if (mBlocking)
throw new InvalidOperationException("Cannot use ReadOutput() in blocking mode! Use Stream instead.");
return mOutputBuffer.Read(buffer, offset, length);
}
/**
* Terminate this connection with an alert. Can be used for normal closure too.
*
* @param alertLevel
* See {@link AlertLevel} for values.
* @param alertDescription
* See {@link AlertDescription} for values.
* @throws IOException
* If alert was fatal.
*/
protected virtual void FailWithError(byte alertLevel, byte alertDescription, string message, Exception cause)
{
/*
* Check if the connection is still open.
*/
if (!mClosed)
{
/*
* Prepare the message
*/
this.mClosed = true;
if (alertLevel == AlertLevel.fatal)
{
/*
* RFC 2246 7.2.1. The session becomes unresumable if any connection is terminated
* without proper close_notify messages with level equal to warning.
*/
// TODO This isn't quite in the right place. Also, as of TLS 1.1 the above is obsolete.
InvalidateSession();
this.mFailedWithError = true;
}
RaiseAlert(alertLevel, alertDescription, message, cause);
mRecordStream.SafeClose();
if (alertLevel != AlertLevel.fatal)
{
return;
}
}
throw new IOException(TLS_ERROR_MESSAGE);
}
protected virtual void InvalidateSession()
{
if (this.mSessionParameters != null)
{
this.mSessionParameters.Clear();
this.mSessionParameters = null;
}
if (this.mTlsSession != null)
{
this.mTlsSession.Invalidate();
this.mTlsSession = null;
}
}
protected virtual void ProcessFinishedMessage(MemoryStream buf)
{
if (mExpectedVerifyData == null)
throw new TlsFatalAlert(AlertDescription.internal_error);
byte[] verify_data = TlsUtilities.ReadFully(mExpectedVerifyData.Length, buf);
AssertEmpty(buf);
/*
* Compare both checksums.
*/
if (!Arrays.ConstantTimeAreEqual(mExpectedVerifyData, verify_data))
{
/*
* Wrong checksum in the finished message.
*/
throw new TlsFatalAlert(AlertDescription.decrypt_error);
}
}
protected virtual void RaiseAlert(byte alertLevel, byte alertDescription, string message, Exception cause)
{
Peer.NotifyAlertRaised(alertLevel, alertDescription, message, cause);
byte[] error = new byte[]{ alertLevel, alertDescription };
SafeWriteRecord(ContentType.alert, error, 0, 2);
}
protected virtual void RaiseWarning(byte alertDescription, string message)
{
RaiseAlert(AlertLevel.warning, alertDescription, message, null);
}
protected virtual void SendCertificateMessage(Certificate certificate)
{
if (certificate == null)
{
certificate = Certificate.EmptyChain;
}
if (certificate.IsEmpty)
{
TlsContext context = Context;
if (!context.IsServer)
{
ProtocolVersion serverVersion = Context.ServerVersion;
if (serverVersion.IsSsl)
{
string errorMessage = serverVersion.ToString() + " client didn't provide credentials";
RaiseWarning(AlertDescription.no_certificate, errorMessage);
return;
}
}
}
HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate);
certificate.Encode(message);
message.WriteToRecordStream(this);
}
protected virtual void SendChangeCipherSpecMessage()
{
byte[] message = new byte[]{ 1 };
SafeWriteRecord(ContentType.change_cipher_spec, message, 0, message.Length);
mRecordStream.SentWriteCipherSpec();
}
protected virtual void SendFinishedMessage()
{
byte[] verify_data = CreateVerifyData(Context.IsServer);
HandshakeMessage message = new HandshakeMessage(HandshakeType.finished, verify_data.Length);
message.Write(verify_data, 0, verify_data.Length);
message.WriteToRecordStream(this);
}
protected virtual void SendSupplementalDataMessage(IList supplementalData)
{
HandshakeMessage message = new HandshakeMessage(HandshakeType.supplemental_data);
WriteSupplementalData(message, supplementalData);
message.WriteToRecordStream(this);
}
protected virtual byte[] CreateVerifyData(bool isServer)
{
TlsContext context = Context;
string asciiLabel = isServer ? ExporterLabel.server_finished : ExporterLabel.client_finished;
byte[] sslSender = isServer ? TlsUtilities.SSL_SERVER : TlsUtilities.SSL_CLIENT;
byte[] hash = GetCurrentPrfHash(context, mRecordStream.HandshakeHash, sslSender);
return TlsUtilities.CalculateVerifyData(context, asciiLabel, hash);
}
/**
* Closes this connection.
*
* @throws IOException If something goes wrong during closing.
*/
public virtual void Close()
{
HandleClose(true);
}
protected virtual void HandleClose(bool user_canceled)
{
if (!mClosed)
{
if (user_canceled && !mAppDataReady)
{
RaiseWarning(AlertDescription.user_canceled, "User canceled handshake");
}
this.FailWithError(AlertLevel.warning, AlertDescription.close_notify, "Connection closed", null);
}
}
protected internal virtual void Flush()
{
mRecordStream.Flush();
}
public virtual bool IsClosed
{
get { return mClosed; }
}
protected virtual short ProcessMaxFragmentLengthExtension(IDictionary clientExtensions, IDictionary serverExtensions,
byte alertDescription)
{
short maxFragmentLength = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(serverExtensions);
if (maxFragmentLength >= 0)
{
if (!MaxFragmentLength.IsValid((byte)maxFragmentLength)
|| (!this.mResumedSession && maxFragmentLength != TlsExtensionsUtilities
.GetMaxFragmentLengthExtension(clientExtensions)))
{
throw new TlsFatalAlert(alertDescription);
}
}
return maxFragmentLength;
}
protected virtual void RefuseRenegotiation()
{
/*
* RFC 5746 4.5 SSLv3 clients that refuse renegotiation SHOULD use a fatal
* handshake_failure alert.
*/
if (TlsUtilities.IsSsl(Context))
throw new TlsFatalAlert(AlertDescription.handshake_failure);
RaiseWarning(AlertDescription.no_renegotiation, "Renegotiation not supported");
}
/**
* Make sure the InputStream 'buf' now empty. Fail otherwise.
*
* @param buf The InputStream to check.
* @throws IOException If 'buf' is not empty.
*/
protected internal static void AssertEmpty(MemoryStream buf)
{
if (buf.Position < buf.Length)
throw new TlsFatalAlert(AlertDescription.decode_error);
}
protected internal static byte[] CreateRandomBlock(bool useGmtUnixTime, IRandomGenerator randomGenerator)
{
byte[] result = new byte[32];
randomGenerator.NextBytes(result);
if (useGmtUnixTime)
{
TlsUtilities.WriteGmtUnixTime(result, 0);
}
return result;
}
protected internal static byte[] CreateRenegotiationInfo(byte[] renegotiated_connection)
{
return TlsUtilities.EncodeOpaque8(renegotiated_connection);
}
protected internal static void EstablishMasterSecret(TlsContext context, TlsKeyExchange keyExchange)
{
byte[] pre_master_secret = keyExchange.GeneratePremasterSecret();
try
{
context.SecurityParameters.masterSecret = TlsUtilities.CalculateMasterSecret(context, pre_master_secret);
}
finally
{
// TODO Is there a way to ensure the data is really overwritten?
/*
* RFC 2246 8.1. The pre_master_secret should be deleted from memory once the
* master_secret has been computed.
*/
if (pre_master_secret != null)
{
Arrays.Fill(pre_master_secret, (byte)0);
}
}
}
/**
* 'sender' only relevant to SSLv3
*/
protected internal static byte[] GetCurrentPrfHash(TlsContext context, TlsHandshakeHash handshakeHash, byte[] sslSender)
{
IDigest d = handshakeHash.ForkPrfHash();
if (sslSender != null && TlsUtilities.IsSsl(context))
{
d.BlockUpdate(sslSender, 0, sslSender.Length);
}
return DigestUtilities.DoFinal(d);
}
protected internal static IDictionary ReadExtensions(MemoryStream input)
{
if (input.Position >= input.Length)
return null;
byte[] extBytes = TlsUtilities.ReadOpaque16(input);
AssertEmpty(input);
MemoryStream buf = new MemoryStream(extBytes, false);
// Integer -> byte[]
IDictionary extensions = Org.BouncyCastle.Utilities.Platform.CreateHashtable();
while (buf.Position < buf.Length)
{
int extension_type = TlsUtilities.ReadUint16(buf);
byte[] extension_data = TlsUtilities.ReadOpaque16(buf);
/*
* RFC 3546 2.3 There MUST NOT be more than one extension of the same type.
*/
if (extensions.Contains(extension_type))
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
extensions.Add(extension_type, extension_data);
}
return extensions;
}
protected internal static IList ReadSupplementalDataMessage(MemoryStream input)
{
byte[] supp_data = TlsUtilities.ReadOpaque24(input);
AssertEmpty(input);
MemoryStream buf = new MemoryStream(supp_data, false);
IList supplementalData = Org.BouncyCastle.Utilities.Platform.CreateArrayList();
while (buf.Position < buf.Length)
{
int supp_data_type = TlsUtilities.ReadUint16(buf);
byte[] data = TlsUtilities.ReadOpaque16(buf);
supplementalData.Add(new SupplementalDataEntry(supp_data_type, data));
}
return supplementalData;
}
protected internal static void WriteExtensions(Stream output, IDictionary extensions)
{
MemoryStream buf = new MemoryStream();
/*
* NOTE: There are reports of servers that don't accept a zero-length extension as the last
* one, so we write out any zero-length ones first as a best-effort workaround.
*/
WriteSelectedExtensions(buf, extensions, true);
WriteSelectedExtensions(buf, extensions, false);
byte[] extBytes = buf.ToArray();
TlsUtilities.WriteOpaque16(extBytes, output);
}
protected internal static void WriteSelectedExtensions(Stream output, IDictionary extensions, bool selectEmpty)
{
foreach (int extension_type in extensions.Keys)
{
byte[] extension_data = (byte[])extensions[extension_type];
if (selectEmpty == (extension_data.Length == 0))
{
TlsUtilities.CheckUint16(extension_type);
TlsUtilities.WriteUint16(extension_type, output);
TlsUtilities.WriteOpaque16(extension_data, output);
}
}
}
protected internal static void WriteSupplementalData(Stream output, IList supplementalData)
{
MemoryStream buf = new MemoryStream();
foreach (SupplementalDataEntry entry in supplementalData)
{
int supp_data_type = entry.DataType;
TlsUtilities.CheckUint16(supp_data_type);
TlsUtilities.WriteUint16(supp_data_type, buf);
TlsUtilities.WriteOpaque16(entry.Data, buf);
}
byte[] supp_data = buf.ToArray();
TlsUtilities.WriteOpaque24(supp_data, output);
}
protected internal static int GetPrfAlgorithm(TlsContext context, int ciphersuite)
{
bool isTLSv12 = TlsUtilities.IsTlsV12(context);
switch (ciphersuite)
{
case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256:
case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256:
case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256:
case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_128_OCB:
case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_256_OCB:
case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_128_OCB:
case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_256_OCB:
case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_128_OCB:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_256_OCB:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_128_OCB:
case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_256_OCB:
case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_128_OCB:
case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_256_OCB:
case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8:
case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_PSK_WITH_AES_128_OCB:
case CipherSuite.TLS_PSK_WITH_AES_256_CCM:
case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
case CipherSuite.DRAFT_TLS_PSK_WITH_AES_256_OCB:
case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.DRAFT_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256:
case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256:
case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
{
if (isTLSv12)
{
return PrfAlgorithm.tls_prf_sha256;
}
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
}
case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384:
case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384:
{
if (isTLSv12)
{
return PrfAlgorithm.tls_prf_sha384;
}
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
}
case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384:
case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384:
case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_PSK_WITH_NULL_SHA384:
case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384:
case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384:
{
if (isTLSv12)
{
return PrfAlgorithm.tls_prf_sha384;
}
return PrfAlgorithm.tls_prf_legacy;
}
default:
{
if (isTLSv12)
{
return PrfAlgorithm.tls_prf_sha256;
}
return PrfAlgorithm.tls_prf_legacy;
}
}
}
internal class HandshakeMessage
: MemoryStream
{
internal HandshakeMessage(byte handshakeType)
: this(handshakeType, 60)
{
}
internal HandshakeMessage(byte handshakeType, int length)
: base(length + 4)
{
TlsUtilities.WriteUint8(handshakeType, this);
// Reserve space for length
TlsUtilities.WriteUint24(0, this);
}
internal void Write(byte[] data)
{
Write(data, 0, data.Length);
}
internal void WriteToRecordStream(TlsProtocol protocol)
{
// Patch actual length back in
long length = Length - 4;
TlsUtilities.CheckUint24(length);
this.Position = 1;
TlsUtilities.WriteUint24((int)length, this);
#if PORTABLE || NETFX_CORE
byte[] buf = ToArray();
int bufLen = buf.Length;
#else
byte[] buf = GetBuffer();
int bufLen = (int)Length;
#endif
protocol.WriteHandshakeMessage(buf, 0, bufLen);
Org.BouncyCastle.Utilities.Platform.Dispose(this);
}
}
}
}
#endif