#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
#pragma warning disable
using System;
using System.Collections;
using System.IO;
using System.Threading;
using BestHTTP.Connections.TLS;
using BestHTTP.PlatformSupport.Threading;
using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto;
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Tls
{
public abstract class TlsProtocol
: TlsCloseable
{
/*
* Connection States.
*
* NOTE: Redirection of handshake messages to TLS 1.3 handlers assumes CS_START, CS_CLIENT_HELLO
* are lower than any of the other values.
*/
protected const short CS_START = 0;
protected const short CS_CLIENT_HELLO = 1;
protected const short CS_SERVER_HELLO_RETRY_REQUEST = 2;
protected const short CS_CLIENT_HELLO_RETRY = 3;
protected const short CS_SERVER_HELLO = 4;
protected const short CS_SERVER_ENCRYPTED_EXTENSIONS = 5;
protected const short CS_SERVER_SUPPLEMENTAL_DATA = 6;
protected const short CS_SERVER_CERTIFICATE = 7;
protected const short CS_SERVER_CERTIFICATE_STATUS = 8;
protected const short CS_SERVER_CERTIFICATE_VERIFY = 9;
protected const short CS_SERVER_KEY_EXCHANGE = 10;
protected const short CS_SERVER_CERTIFICATE_REQUEST = 11;
protected const short CS_SERVER_HELLO_DONE = 12;
protected const short CS_CLIENT_END_OF_EARLY_DATA = 13;
protected const short CS_CLIENT_SUPPLEMENTAL_DATA = 14;
protected const short CS_CLIENT_CERTIFICATE = 15;
protected const short CS_CLIENT_KEY_EXCHANGE = 16;
protected const short CS_CLIENT_CERTIFICATE_VERIFY = 17;
protected const short CS_CLIENT_FINISHED = 18;
protected const short CS_SERVER_SESSION_TICKET = 19;
protected const short CS_SERVER_FINISHED = 20;
protected const short CS_END = 21;
protected bool IsLegacyConnectionState()
{
switch (m_connectionState)
{
case CS_START:
case CS_CLIENT_HELLO:
case CS_SERVER_HELLO:
case CS_SERVER_SUPPLEMENTAL_DATA:
case CS_SERVER_CERTIFICATE:
case CS_SERVER_CERTIFICATE_STATUS:
case CS_SERVER_KEY_EXCHANGE:
case CS_SERVER_CERTIFICATE_REQUEST:
case CS_SERVER_HELLO_DONE:
case CS_CLIENT_SUPPLEMENTAL_DATA:
case CS_CLIENT_CERTIFICATE:
case CS_CLIENT_KEY_EXCHANGE:
case CS_CLIENT_CERTIFICATE_VERIFY:
case CS_CLIENT_FINISHED:
case CS_SERVER_SESSION_TICKET:
case CS_SERVER_FINISHED:
case CS_END:
return true;
case CS_SERVER_HELLO_RETRY_REQUEST:
case CS_CLIENT_HELLO_RETRY:
case CS_SERVER_ENCRYPTED_EXTENSIONS:
case CS_SERVER_CERTIFICATE_VERIFY:
case CS_CLIENT_END_OF_EARLY_DATA:
default:
return false;
}
}
protected bool IsTlsV13ConnectionState()
{
switch (m_connectionState)
{
case CS_START:
case CS_CLIENT_HELLO:
case CS_SERVER_HELLO_RETRY_REQUEST:
case CS_CLIENT_HELLO_RETRY:
case CS_SERVER_HELLO:
case CS_SERVER_ENCRYPTED_EXTENSIONS:
case CS_SERVER_CERTIFICATE_REQUEST:
case CS_SERVER_CERTIFICATE:
case CS_SERVER_CERTIFICATE_VERIFY:
case CS_SERVER_FINISHED:
case CS_CLIENT_END_OF_EARLY_DATA:
case CS_CLIENT_CERTIFICATE:
case CS_CLIENT_CERTIFICATE_VERIFY:
case CS_CLIENT_FINISHED:
case CS_END:
return true;
case CS_SERVER_SUPPLEMENTAL_DATA:
case CS_SERVER_CERTIFICATE_STATUS:
case CS_SERVER_KEY_EXCHANGE:
case CS_SERVER_HELLO_DONE:
case CS_CLIENT_SUPPLEMENTAL_DATA:
case CS_CLIENT_KEY_EXCHANGE:
case CS_SERVER_SESSION_TICKET:
default:
return false;
}
}
/*
* 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 readonly ByteQueue m_applicationDataQueue = new ByteQueue(0);
private readonly ByteQueue m_alertQueue = new ByteQueue(2);
private readonly ByteQueue m_handshakeQueue = new ByteQueue(0);
//private readonly ByteQueue m_heartbeatQueue = new ByteQueue(0);
internal readonly RecordStream m_recordStream;
internal readonly object m_recordWriteLock = new object();
private int m_maxHandshakeMessageSize = -1;
internal TlsHandshakeHash m_handshakeHash;
private TlsStream m_tlsStream = null;
private volatile bool m_closed = false;
private volatile bool m_failedWithError = false;
private volatile bool m_appDataReady = false;
private volatile bool m_appDataSplitEnabled = true;
private volatile bool m_keyUpdateEnabled = false;
//private volatile bool m_keyUpdatePendingReceive = false;
private volatile bool m_keyUpdatePendingSend = false;
private volatile bool m_resumableHandshake = false;
private volatile int m_appDataSplitMode = ADS_MODE_1_Nsub1;
protected TlsSession m_tlsSession = null;
protected SessionParameters m_sessionParameters = null;
protected TlsSecret m_sessionMasterSecret = null;
protected byte[] m_retryCookie = null;
protected int m_retryGroup = -1;
protected IDictionary m_clientExtensions = null;
protected IDictionary m_serverExtensions = null;
protected short m_connectionState = CS_START;
protected bool m_resumedSession = false;
protected bool m_selectedPsk13 = false;
protected bool m_receivedChangeCipherSpec = false;
protected bool m_expectSessionTicket = false;
protected readonly bool m_blocking;
protected readonly ByteQueueInputStream m_inputBuffers;
protected readonly ByteQueueOutputStream m_outputBuffer;
protected TlsProtocol()
{
this.m_blocking = false;
this.m_inputBuffers = new ByteQueueInputStream();
this.m_outputBuffer = new ByteQueueOutputStream();
this.m_recordStream = new RecordStream(this, m_inputBuffers, m_outputBuffer);
}
public TlsProtocol(Stream stream)
: this(stream, stream)
{
}
public TlsProtocol(Stream input, Stream output)
{
this.m_blocking = true;
this.m_inputBuffers = null;
this.m_outputBuffer = null;
this.m_recordStream = new RecordStream(this, input, output);
}
///
public virtual void ResumeHandshake()
{
if (!m_blocking)
throw new InvalidOperationException("Cannot use ResumeHandshake() in non-blocking mode!");
if (!IsHandshaking)
throw new InvalidOperationException("No handshake in progress");
BlockForHandshake();
}
///
protected virtual void CloseConnection()
{
m_recordStream.Close();
}
protected abstract TlsContext Context { get; }
internal abstract AbstractTlsContext ContextAdmin { get; }
protected abstract TlsPeer Peer { get; }
///
protected virtual void HandleAlertMessage(short alertLevel, short alertDescription)
{
Peer.NotifyAlertReceived(alertLevel, alertDescription);
if (alertLevel == AlertLevel.warning)
{
HandleAlertWarningMessage(alertDescription);
}
else
{
HandleFailure();
throw new TlsFatalAlertReceived(alertDescription);
}
}
///
protected virtual void HandleAlertWarningMessage(short alertDescription)
{
switch (alertDescription)
{
/*
* 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.
*/
case AlertDescription.close_notify:
{
if (!m_appDataReady)
throw new TlsFatalAlert(AlertDescription.handshake_failure);
HandleClose(false);
break;
}
case AlertDescription.no_certificate:
{
throw new TlsFatalAlert(AlertDescription.unexpected_message);
}
case AlertDescription.no_renegotiation:
{
// TODO[reneg] Give peer the option to tolerate this
throw new TlsFatalAlert(AlertDescription.handshake_failure);
}
}
}
///
protected virtual void HandleChangeCipherSpecMessage()
{
}
///
protected virtual void HandleClose(bool user_canceled)
{
if (!m_closed)
{
this.m_closed = true;
if (!m_appDataReady)
{
CleanupHandshake();
if (user_canceled)
{
RaiseAlertWarning(AlertDescription.user_canceled, "User canceled handshake");
}
}
RaiseAlertWarning(AlertDescription.close_notify, "Connection closed");
CloseConnection();
}
}
///
protected virtual void HandleException(short alertDescription, string message, Exception e)
{
// TODO[tls-port] Can we support interrupted IO on .NET?
//if ((m_appDataReady || IsResumableHandshake()) && (e is InterruptedIOException))
// return;
if (!m_closed)
{
RaiseAlertFatal(alertDescription, message, e);
HandleFailure();
}
}
///
protected virtual void HandleFailure()
{
this.m_closed = true;
this.m_failedWithError = true;
/*
* 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();
if (!m_appDataReady)
{
CleanupHandshake();
}
CloseConnection();
}
///
protected abstract void HandleHandshakeMessage(short type, HandshakeMessageInput buf);
///
protected virtual void ApplyMaxFragmentLengthExtension(short maxFragmentLength)
{
if (maxFragmentLength >= 0)
{
if (!MaxFragmentLength.IsValid(maxFragmentLength))
throw new TlsFatalAlert(AlertDescription.internal_error);
int plainTextLimit = 1 << (8 + maxFragmentLength);
m_recordStream.SetPlaintextLimit(plainTextLimit);
}
}
///
protected virtual void CheckReceivedChangeCipherSpec(bool expected)
{
if (expected != m_receivedChangeCipherSpec)
throw new TlsFatalAlert(AlertDescription.unexpected_message);
}
///
protected virtual void BlockForHandshake()
{
while (m_connectionState != CS_END)
{
if (IsClosed)
{
// NOTE: Any close during the handshake should have raised an exception.
throw new TlsFatalAlert(AlertDescription.internal_error);
}
using (new WriteLock(this.applicationDataLock))
SafeReadRecord();
}
}
protected virtual void handleRenegotiation()
{
// TODO: check whether renegotiation is enabled or not and call BeginHandshake/RefuseRenegotiation accordingly.
BeginHandshake(true);
}
///
protected virtual void BeginHandshake(bool renegotiation)
{
AbstractTlsContext context = ContextAdmin;
TlsPeer peer = Peer;
this.m_maxHandshakeMessageSize = System.Math.Max(1024, peer.GetMaxHandshakeMessageSize());
this.m_handshakeHash = new DeferredHash(context);
this.m_connectionState = CS_START;
this.m_resumedSession = false;
this.m_selectedPsk13 = false;
context.HandshakeBeginning(peer);
SecurityParameters securityParameters = context.SecurityParameters;
if (renegotiation != securityParameters.IsRenegotiating)
{
throw new TlsFatalAlert(AlertDescription.internal_error);
}
securityParameters.m_extendedPadding = peer.ShouldUseExtendedPadding();
}
protected virtual void CleanupHandshake()
{
TlsContext context = Context;
if (null != context)
{
SecurityParameters securityParameters = context.SecurityParameters;
if (null != securityParameters)
{
securityParameters.Clear();
}
}
this.m_tlsSession = null;
this.m_sessionParameters = null;
this.m_sessionMasterSecret = null;
this.m_retryCookie = null;
this.m_retryGroup = -1;
this.m_clientExtensions = null;
this.m_serverExtensions = null;
this.m_resumedSession = false;
this.m_selectedPsk13 = false;
this.m_receivedChangeCipherSpec = false;
this.m_expectSessionTicket = false;
}
///
protected virtual void CompleteHandshake()
{
try
{
AbstractTlsContext context = ContextAdmin;
SecurityParameters securityParameters = context.SecurityParameters;
if ((!context.IsHandshaking && !securityParameters.IsRenegotiating) ||
null == securityParameters.LocalVerifyData ||
null == securityParameters.PeerVerifyData)
{
throw new TlsFatalAlert(AlertDescription.internal_error);
}
m_recordStream.FinaliseHandshake();
this.m_connectionState = CS_END;
// TODO Prefer to set to null, but would need guards elsewhere
this.m_handshakeHash = new DeferredHash(context);
m_alertQueue.Shrink();
m_handshakeQueue.Shrink();
ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
this.m_appDataSplitEnabled = !TlsUtilities.IsTlsV11(negotiatedVersion);
this.m_appDataReady = true;
this.m_keyUpdateEnabled = TlsUtilities.IsTlsV13(negotiatedVersion);
if (m_blocking)
{
this.m_tlsStream = new TlsStream(this);
}
if (m_sessionParameters == null)
{
this.m_sessionMasterSecret = securityParameters.MasterSecret;
this.m_sessionParameters = new SessionParameters.Builder()
.SetCipherSuite(securityParameters.CipherSuite)
.SetExtendedMasterSecret(securityParameters.IsExtendedMasterSecret)
.SetLocalCertificate(securityParameters.LocalCertificate)
.SetMasterSecret(context.Crypto.AdoptSecret(m_sessionMasterSecret))
.SetNegotiatedVersion(securityParameters.NegotiatedVersion)
.SetPeerCertificate(securityParameters.PeerCertificate)
.SetPskIdentity(securityParameters.PskIdentity)
.SetSrpIdentity(securityParameters.SrpIdentity)
// TODO Consider filtering extensions that aren't relevant to resumed sessions
.SetServerExtensions(m_serverExtensions)
.Build();
this.m_tlsSession = TlsUtilities.ImportSession(securityParameters.SessionID, m_sessionParameters);
}
else
{
securityParameters.m_localCertificate = m_sessionParameters.LocalCertificate;
securityParameters.m_peerCertificate = m_sessionParameters.PeerCertificate;
securityParameters.m_pskIdentity = m_sessionParameters.PskIdentity;
securityParameters.m_srpIdentity = m_sessionParameters.SrpIdentity;
}
context.HandshakeComplete(Peer, m_tlsSession);
}
finally
{
CleanupHandshake();
}
}
///
internal void ProcessRecord(short protocol, byte[] buf, int off, int len)
{
/*
* Have a look at the protocol type, and add it to the correct queue.
*/
switch (protocol)
{
case ContentType.alert:
{
m_alertQueue.AddData(buf, off, len);
ProcessAlertQueue();
break;
}
case ContentType.application_data:
{
if (!m_appDataReady)
throw new TlsFatalAlert(AlertDescription.unexpected_message);
m_applicationDataQueue.AddData(buf, off, len);
ProcessApplicationDataQueue();
break;
}
case ContentType.change_cipher_spec:
{
ProcessChangeCipherSpec(buf, off, len);
break;
}
case ContentType.handshake:
{
if (m_handshakeQueue.Available > 0)
{
m_handshakeQueue.AddData(buf, off, len);
ProcessHandshakeQueue(m_handshakeQueue);
}
else
{
ByteQueue tmpQueue = new ByteQueue(buf, off, len);
ProcessHandshakeQueue(tmpQueue);
int remaining = tmpQueue.Available;
if (remaining > 0)
{
m_handshakeQueue.AddData(buf, off + len - remaining, remaining);
}
}
break;
}
//case ContentType.heartbeat:
//{
// if (!m_appDataReady)
// throw new TlsFatalAlert(AlertDescription.unexpected_message);
// // TODO[RFC 6520]
// m_heartbeatQueue.addData(buf, off, len);
// ProcessHeartbeatQueue();
// break;
//}
default:
throw new TlsFatalAlert(AlertDescription.unexpected_message);
}
}
///
private void ProcessHandshakeQueue(ByteQueue queue)
{
/*
* We need the first 4 bytes, they contain type and length of the message.
*/
while (queue.Available >= 4)
{
int header = queue.ReadInt32();
short type = (short)((uint)header >> 24);
if (!HandshakeType.IsRecognized(type))
{
throw new TlsFatalAlert(AlertDescription.unexpected_message,
"Handshake message of unrecognized type: " + type);
}
int length = header & 0x00FFFFFF;
if (length > m_maxHandshakeMessageSize)
{
throw new TlsFatalAlert(AlertDescription.internal_error,
"Handshake message length exceeds the maximum: " + HandshakeType.GetText(type) + ", " + length
+ " > " + m_maxHandshakeMessageSize);
}
int totalLength = 4 + length;
if (queue.Available < totalLength)
{
// Not enough bytes in the buffer to read the full message.
break;
}
/*
* Check ChangeCipherSpec status
*/
switch (type)
{
case HandshakeType.hello_request:
break;
default:
{
ProtocolVersion negotiatedVersion = Context.ServerVersion;
if (null != negotiatedVersion && TlsUtilities.IsTlsV13(negotiatedVersion))
break;
CheckReceivedChangeCipherSpec(HandshakeType.finished == type);
break;
}
}
HandshakeMessageInput buf = queue.ReadHandshakeMessage(totalLength);
switch (type)
{
/*
* These message types aren't included in the transcript.
*/
case HandshakeType.hello_request:
case HandshakeType.key_update:
break;
/*
* Not included in the transcript for (D)TLS 1.3+
*/
case HandshakeType.new_session_ticket:
{
ProtocolVersion negotiatedVersion = Context.ServerVersion;
if (null != negotiatedVersion && !TlsUtilities.IsTlsV13(negotiatedVersion))
{
buf.UpdateHash(m_handshakeHash);
}
break;
}
/*
* These message types are deferred to the handler to explicitly update the transcript.
*/
case HandshakeType.certificate_verify:
case HandshakeType.client_hello:
case HandshakeType.finished:
case HandshakeType.server_hello:
break;
/*
* For all others we automatically update the transcript immediately.
*/
default:
{
buf.UpdateHash(m_handshakeHash);
break;
}
}
buf.Seek(4L, SeekOrigin.Current);
HandleHandshakeMessage(type, buf);
}
}
private void ProcessApplicationDataQueue()
{
/*
* There is nothing we need to do here.
*
* This function could be used for callbacks when application data arrives in the future.
*/
}
///
private void ProcessAlertQueue()
{
while (m_alertQueue.Available >= 2)
{
/*
* An alert is always 2 bytes. Read the alert.
*/
byte[] alert = m_alertQueue.RemoveData(2, 0);
short alertLevel = alert[0];
short alertDescription = alert[1];
HandleAlertMessage(alertLevel, alertDescription);
}
}
/// This method is called, when a change cipher spec message is received.
/// 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)
{
ProtocolVersion negotiatedVersion = Context.ServerVersion;
if (null == negotiatedVersion || TlsUtilities.IsTlsV13(negotiatedVersion))
{
// See RFC 8446 D.4.
throw new TlsFatalAlert(AlertDescription.unexpected_message);
}
for (int i = 0; i < len; ++i)
{
short message = TlsUtilities.ReadUint8(buf, off + i);
if (message != ChangeCipherSpec.change_cipher_spec)
throw new TlsFatalAlert(AlertDescription.decode_error);
if (this.m_receivedChangeCipherSpec
|| m_alertQueue.Available > 0
|| m_handshakeQueue.Available > 0)
{
throw new TlsFatalAlert(AlertDescription.unexpected_message);
}
m_recordStream.NotifyChangeCipherSpecReceived();
this.m_receivedChangeCipherSpec = true;
HandleChangeCipherSpecMessage();
}
}
public virtual int ApplicationDataAvailable
{
get { return m_applicationDataQueue.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.
///
/// The buffer where the data will be copied to.
/// The position where the data will be placed in the buffer.
/// The maximum number of bytes to read.
/// The number of bytes read.
/// If something goes wrong during reading data.
public virtual int ReadApplicationData(byte[] buf, int off, int len)
{
if (len < 1)
return 0;
using (new WriteLock(this.applicationDataLock))
{
while (m_applicationDataQueue.Available == 0)
{
if (this.m_closed)
{
if (this.m_failedWithError)
throw new IOException("Cannot read application data on failed TLS connection");
return -1;
}
if (!m_appDataReady)
throw new InvalidOperationException("Cannot read application data until initial handshake completed.");
/*
* NOTE: Only called more than once when empty records are received, so no special
* InterruptedIOException handling is necessary.
*/
SafeReadRecord();
}
len = System.Math.Min(len, m_applicationDataQueue.Available);
m_applicationDataQueue.RemoveData(buf, off, len, 0);
return len;
}
}
ReaderWriterLockSlim applicationDataLock = new ReaderWriterLockSlim();
public bool TryEnterApplicationDataLock(int millisecondsTimeout)
{
return this.applicationDataLock.TryEnterWriteLock(millisecondsTimeout);
}
public void ExitApplicationDataLock()
{
this.applicationDataLock.ExitWriteLock();
}
public int TestApplicationData()
{
using (new WriteLock(this.applicationDataLock))
{
while (m_applicationDataQueue.Available == 0)
{
if (this.m_closed)
{
if (this.m_failedWithError)
throw new IOException("Cannot read application data on failed TLS connection");
return -1;
}
if (!m_appDataReady)
throw new InvalidOperationException("Cannot read application data until initial handshake completed.");
/*
* NOTE: Only called more than once when empty records are received, so no special
* InterruptedIOException handling is necessary.
*/
SafeReadRecord();
}
return m_applicationDataQueue.Available;
}
}
///
protected virtual RecordPreview SafePreviewRecordHeader(byte[] recordHeader)
{
try
{
return m_recordStream.PreviewRecordHeader(recordHeader);
}
catch (TlsFatalAlert e)
{
HandleException(e.AlertDescription, "Failed to read record", e);
throw e;
}
catch (IOException e)
{
HandleException(AlertDescription.internal_error, "Failed to read record", e);
throw e;
}
catch (Exception e)
{
HandleException(AlertDescription.internal_error, "Failed to read record", e);
throw new TlsFatalAlert(AlertDescription.internal_error, e);
}
}
///
protected virtual void SafeReadRecord()
{
try
{
if (m_recordStream.ReadRecord())
return;
if (!m_appDataReady)
throw new TlsFatalAlert(AlertDescription.handshake_failure);
if (!Peer.RequiresCloseNotify())
{
HandleClose(false);
return;
}
}
catch (TlsFatalAlertReceived e)
{
// Connection failure already handled at source
throw e;
}
catch (TlsFatalAlert e)
{
HandleException(e.AlertDescription, "Failed to read record", e);
throw e;
}
catch (IOException e)
{
HandleException(AlertDescription.internal_error, "Failed to read record", e);
throw e;
}
catch (Exception e)
{
HandleException(AlertDescription.internal_error, "Failed to read record", e);
throw new TlsFatalAlert(AlertDescription.internal_error, e);
}
HandleFailure();
throw new TlsNoCloseNotifyException();
}
///
protected virtual bool SafeReadFullRecord(byte[] input, int inputOff, int inputLen)
{
try
{
return m_recordStream.ReadFullRecord(input, inputOff, inputLen);
}
catch (TlsFatalAlert e)
{
HandleException(e.AlertDescription, "Failed to process record", e);
throw e;
}
catch (IOException e)
{
HandleException(AlertDescription.internal_error, "Failed to process record", e);
throw e;
}
catch (Exception e)
{
HandleException(AlertDescription.internal_error, "Failed to process record", e);
throw new TlsFatalAlert(AlertDescription.internal_error, e);
}
}
///
protected virtual void SafeWriteRecord(short type, byte[] buf, int offset, int len)
{
try
{
m_recordStream.WriteRecord(type, buf, offset, len);
}
catch (TlsFatalAlert e)
{
HandleException(e.AlertDescription, "Failed to write record", e);
throw e;
}
catch (IOException e)
{
HandleException(AlertDescription.internal_error, "Failed to write record", e);
throw e;
}
catch (Exception e)
{
HandleException(AlertDescription.internal_error, "Failed to write record", e);
throw new TlsFatalAlert(AlertDescription.internal_error, e);
}
}
/// Write some application data.
///
/// Fragmentation is handled internally. Usable in both blocking/non-blocking modes.
/// In blocking mode, the output will be automatically sent via the underlying transport. In non-blocking mode,
/// call to get the output bytes to send to the peer.
/// This method must not be called until after the initial handshake is complete. Attempting to call it earlier
/// will result in an .
///
/// The buffer containing application data to send.
/// The offset at which the application data begins
/// The number of bytes of application data.
/// If called before the initial handshake has completed.
///
/// If connection is already closed, or for encryption or transport errors.
///
public virtual void WriteApplicationData(byte[] buf, int off, int len)
{
if (!m_appDataReady)
throw new InvalidOperationException(
"Cannot write application data until initial handshake completed.");
lock (m_recordWriteLock)
{
while (len > 0)
{
if (m_closed)
throw new IOException("Cannot write application data on closed/failed TLS connection");
/*
* 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 (m_appDataSplitEnabled)
{
/*
* Protect against known IV attack!
*
* DO NOT REMOVE THIS CODE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE.
*/
switch (m_appDataSplitMode)
{
case ADS_MODE_0_N_FIRSTONLY:
{
this.m_appDataSplitEnabled = false;
SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0);
break;
}
case ADS_MODE_0_N:
{
SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0);
break;
}
case ADS_MODE_1_Nsub1:
default:
{
if (len > 1)
{
SafeWriteRecord(ContentType.application_data, buf, off, 1);
++off;
--len;
}
break;
}
}
}
else if (m_keyUpdateEnabled)
{
if (m_keyUpdatePendingSend)
{
Send13KeyUpdate(false);
}
else if (m_recordStream.NeedsKeyUpdate())
{
Send13KeyUpdate(true);
}
}
// Fragment data according to the current fragment limit.
int toWrite = System.Math.Min(len, m_recordStream.PlaintextLimit);
SafeWriteRecord(ContentType.application_data, buf, off, toWrite);
off += toWrite;
len -= toWrite;
}
}
}
public virtual int AppDataSplitMode
{
get { return m_appDataSplitMode; }
set
{
if (value < ADS_MODE_1_Nsub1 || value > ADS_MODE_0_N_FIRSTONLY)
throw new InvalidOperationException("Illegal appDataSplitMode mode: " + value);
this.m_appDataSplitMode = value;
}
}
public virtual bool IsResumableHandshake
{
get { return m_resumableHandshake; }
set { this.m_resumableHandshake = value; }
}
///
internal void WriteHandshakeMessage(byte[] buf, int off, int len)
{
if (len < 4)
throw new TlsFatalAlert(AlertDescription.internal_error);
short type = TlsUtilities.ReadUint8(buf, off);
switch (type)
{
/*
* These message types aren't included in the transcript.
*/
case HandshakeType.hello_request:
case HandshakeType.key_update:
break;
/*
* Not included in the transcript for (D)TLS 1.3+
*/
case HandshakeType.new_session_ticket:
{
ProtocolVersion negotiatedVersion = Context.ServerVersion;
if (null != negotiatedVersion && !TlsUtilities.IsTlsV13(negotiatedVersion))
{
m_handshakeHash.Update(buf, off, len);
}
break;
}
/*
* These message types are deferred to the writer to explicitly update the transcript.
*/
case HandshakeType.client_hello:
break;
/*
* For all others we automatically update the transcript.
*/
default:
{
m_handshakeHash.Update(buf, off, len);
break;
}
}
int total = 0;
do
{
// Fragment data according to the current fragment limit.
int toWrite = System.Math.Min(len - total, m_recordStream.PlaintextLimit);
SafeWriteRecord(ContentType.handshake, buf, off + total, toWrite);
total += toWrite;
}
while (total < len);
}
/// The secure bidirectional stream for this connection
/// Only allowed in blocking mode.
public virtual Stream Stream
{
get
{
if (!m_blocking)
throw new InvalidOperationException(
"Cannot use Stream in non-blocking mode! Use OfferInput()/OfferOutput() instead.");
return this.m_tlsStream;
}
}
/// Should be called in non-blocking mode when the input data reaches EOF.
///
public virtual void CloseInput()
{
if (m_blocking)
throw new InvalidOperationException("Cannot use CloseInput() in blocking mode!");
if (m_closed)
return;
if (m_inputBuffers.Available > 0)
throw new EndOfStreamException();
if (!m_appDataReady)
throw new TlsFatalAlert(AlertDescription.handshake_failure);
if (!Peer.RequiresCloseNotify())
{
HandleClose(false);
return;
}
HandleFailure();
throw new TlsNoCloseNotifyException();
}
///
public virtual RecordPreview PreviewInputRecord(byte[] recordHeader)
{
if (m_blocking)
throw new InvalidOperationException("Cannot use PreviewInputRecord() in blocking mode!");
if (m_inputBuffers.Available != 0)
throw new InvalidOperationException("Can only use PreviewInputRecord() for record-aligned input.");
if (m_closed)
throw new IOException("Connection is closed, cannot accept any more input");
return SafePreviewRecordHeader(recordHeader);
}
///
public virtual RecordPreview PreviewOutputRecord(int applicationDataSize)
{
if (!m_appDataReady)
throw new InvalidOperationException(
"Cannot use PreviewOutputRecord() until initial handshake completed.");
if (m_blocking)
throw new InvalidOperationException("Cannot use PreviewOutputRecord() in blocking mode!");
if (m_outputBuffer.Buffer.Available != 0)
throw new InvalidOperationException("Can only use PreviewOutputRecord() for record-aligned output.");
if (m_closed)
throw new IOException("Connection is closed, cannot produce any more output");
if (applicationDataSize < 1)
return new RecordPreview(0, 0);
if (m_appDataSplitEnabled)
{
switch (m_appDataSplitMode)
{
case ADS_MODE_0_N_FIRSTONLY:
case ADS_MODE_0_N:
{
RecordPreview a = m_recordStream.PreviewOutputRecord(0);
RecordPreview b = m_recordStream.PreviewOutputRecord(applicationDataSize);
return RecordPreview.CombineAppData(a, b);
}
case ADS_MODE_1_Nsub1:
default:
{
RecordPreview a = m_recordStream.PreviewOutputRecord(1);
if (applicationDataSize > 1)
{
RecordPreview b = m_recordStream.PreviewOutputRecord(applicationDataSize - 1);
a = RecordPreview.CombineAppData(a, b);
}
return a;
}
}
}
else
{
RecordPreview a = m_recordStream.PreviewOutputRecord(applicationDataSize);
if (m_keyUpdateEnabled && (m_keyUpdatePendingSend || m_recordStream.NeedsKeyUpdate()))
{
int keyUpdateLength = HandshakeMessageOutput.GetLength(1);
int recordSize = m_recordStream.PreviewOutputRecordSize(keyUpdateLength);
a = RecordPreview.ExtendRecordSize(a, recordSize);
}
return a;
}
}
/// Equivalent to OfferInput(input, 0, input.Length)
.
/// The input buffer to offer.
///
///
public virtual void OfferInput(byte[] input)
{
OfferInput(input, 0, input.Length);
}
/// Offer input from an arbitrary source.
/// Only allowed in non-blocking mode.
/// 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
/// . 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 .
///
/// The input buffer to offer.
/// The offset within the input buffer that input begins.
/// The number of bytes of input being offered.
/// If an error occurs while decrypting or processing a record.
public virtual void OfferInput(byte[] input, int inputOff, int inputLen)
{
if (m_blocking)
throw new InvalidOperationException("Cannot use OfferInput() in blocking mode! Use Stream instead.");
if (m_closed)
throw new IOException("Connection is closed, cannot accept any more input");
// Fast path if the input is arriving one record at a time
if (m_inputBuffers.Available == 0 && SafeReadFullRecord(input, inputOff, inputLen))
{
if (m_closed)
{
if (!m_appDataReady)
{
// NOTE: Any close during the handshake should have raised an exception.
throw new TlsFatalAlert(AlertDescription.internal_error);
}
}
return;
}
m_inputBuffers.AddBytes(input, inputOff, inputLen);
// loop while there are enough bytes to read the length of the next record
while (m_inputBuffers.Available >= RecordFormat.FragmentOffset)
{
byte[] recordHeader = new byte[RecordFormat.FragmentOffset];
if (RecordFormat.FragmentOffset != m_inputBuffers.Peek(recordHeader))
throw new TlsFatalAlert(AlertDescription.internal_error);
RecordPreview preview = SafePreviewRecordHeader(recordHeader);
if (m_inputBuffers.Available < preview.RecordSize)
{
// not enough bytes to read a whole record
break;
}
// NOTE: This is actually reading from inputBuffers, so InterruptedIOException shouldn't be possible
SafeReadRecord();
if (m_closed)
{
if (!m_appDataReady)
{
// NOTE: Any close during the handshake should have raised an exception.
throw new TlsFatalAlert(AlertDescription.internal_error);
}
break;
}
}
}
public virtual int ApplicationDataLimit
{
get { return m_recordStream.PlaintextLimit; }
}
/// Gets the amount of received application data.
/// A call to is guaranteed to be able to return at least
/// this much data.
/// Only allowed in non-blocking mode.
///
/// The number of bytes of available application data.
public virtual int GetAvailableInputBytes()
{
if (m_blocking)
throw new InvalidOperationException("Cannot use GetAvailableInputBytes() in blocking mode!");
return ApplicationDataAvailable;
}
/// Retrieves received application data.
///
/// Use to check how much application data is currently available. This
/// method functions similarly to , 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.
///
/// The buffer to hold the application data.
/// The start offset in the buffer at which the data is written.
/// The maximum number of bytes to read.
/// 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[] buf, int off, int len)
{
if (m_blocking)
throw new InvalidOperationException("Cannot use ReadInput() in blocking mode! Use Stream instead.");
len = System.Math.Min(len, ApplicationDataAvailable);
if (len < 1)
return 0;
m_applicationDataQueue.RemoveData(buf, off, len, 0);
return len;
}
/// Gets the amount of encrypted data available to be sent.
///
/// A call to is guaranteed to be able to return at least this much
/// data. Only allowed in non-blocking mode.
///
/// The number of bytes of available encrypted data.
public virtual int GetAvailableOutputBytes()
{
if (m_blocking)
throw new InvalidOperationException("Cannot use GetAvailableOutputBytes() in blocking mode! Use Stream instead.");
return m_outputBuffer.Buffer.Available;
}
/// Retrieves encrypted data to be sent.
///
/// Use to check how much encrypted data is currently available. This
/// method functions similarly to , 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.
///
/// The buffer to hold the encrypted data.
/// The start offset in the buffer at which the data is written.
/// The maximum number of bytes to read.
/// 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 (m_blocking)
throw new InvalidOperationException("Cannot use ReadOutput() in blocking mode! Use 'Stream() instead.");
int bytesToRead = System.Math.Min(GetAvailableOutputBytes(), length);
m_outputBuffer.Buffer.RemoveData(buffer, offset, bytesToRead, 0);
return bytesToRead;
}
protected virtual bool EstablishSession(TlsSession sessionToResume)
{
this.m_tlsSession = null;
this.m_sessionParameters = null;
this.m_sessionMasterSecret = null;
if (null == sessionToResume || !sessionToResume.IsResumable)
return false;
SessionParameters sessionParameters = sessionToResume.ExportSessionParameters();
if (null == sessionParameters)
return false;
if (!sessionParameters.IsExtendedMasterSecret)
{
TlsPeer peer = Peer;
if (!peer.AllowLegacyResumption() || peer.RequiresExtendedMasterSecret())
return false;
/*
* NOTE: For session resumption without extended_master_secret, renegotiation MUST be
* disabled (see RFC 7627 5.4). We currently do not implement renegotiation and it is
* unlikely we ever would since it was removed in TLS 1.3.
*/
}
TlsSecret sessionMasterSecret = TlsUtilities.GetSessionMasterSecret(Context.Crypto,
sessionParameters.MasterSecret);
if (null == sessionMasterSecret)
return false;
this.m_tlsSession = sessionToResume;
this.m_sessionParameters = sessionParameters;
this.m_sessionMasterSecret = sessionMasterSecret;
return true;
}
protected virtual void InvalidateSession()
{
if (m_sessionMasterSecret != null)
{
m_sessionMasterSecret.Destroy();
this.m_sessionMasterSecret = null;
}
if (m_sessionParameters != null)
{
m_sessionParameters.Clear();
this.m_sessionParameters = null;
}
if (m_tlsSession != null)
{
m_tlsSession.Invalidate();
this.m_tlsSession = null;
}
}
///
protected virtual void ProcessFinishedMessage(MemoryStream buf)
{
TlsContext context = Context;
SecurityParameters securityParameters = context.SecurityParameters;
bool isServerContext = context.IsServer;
byte[] verify_data = TlsUtilities.ReadFully(securityParameters.VerifyDataLength, buf);
AssertEmpty(buf);
byte[] expected_verify_data = TlsUtilities.CalculateVerifyData(context, m_handshakeHash, !isServerContext);
/*
* Compare both checksums.
*/
if (!Arrays.ConstantTimeAreEqual(expected_verify_data, verify_data))
{
/*
* Wrong checksum in the finished message.
*/
throw new TlsFatalAlert(AlertDescription.decrypt_error);
}
securityParameters.m_peerVerifyData = expected_verify_data;
if (!m_resumedSession || securityParameters.IsExtendedMasterSecret)
{
if (null == securityParameters.LocalVerifyData)
{
securityParameters.m_tlsUnique = expected_verify_data;
}
}
}
///
protected virtual void Process13FinishedMessage(MemoryStream buf)
{
TlsContext context = Context;
SecurityParameters securityParameters = context.SecurityParameters;
bool isServerContext = context.IsServer;
byte[] verify_data = TlsUtilities.ReadFully(securityParameters.VerifyDataLength, buf);
AssertEmpty(buf);
byte[] expected_verify_data = TlsUtilities.CalculateVerifyData(context, m_handshakeHash, !isServerContext);
/*
* Compare both checksums.
*/
if (!Arrays.ConstantTimeAreEqual(expected_verify_data, verify_data))
{
/*
* Wrong checksum in the finished message.
*/
throw new TlsFatalAlert(AlertDescription.decrypt_error);
}
securityParameters.m_peerVerifyData = expected_verify_data;
securityParameters.m_tlsUnique = null;
}
///
protected virtual void RaiseAlertFatal(short alertDescription, string message, Exception cause)
{
Peer.NotifyAlertRaised(AlertLevel.fatal, alertDescription, message, cause);
byte[] alert = new byte[]{ (byte)AlertLevel.fatal, (byte)alertDescription };
try
{
m_recordStream.WriteRecord(ContentType.alert, alert, 0, 2);
}
catch (Exception)
{
// We are already processing an exception, so just ignore this
}
}
///
protected virtual void RaiseAlertWarning(short alertDescription, string message)
{
Peer.NotifyAlertRaised(AlertLevel.warning, alertDescription, message, null);
byte[] alert = new byte[]{ (byte)AlertLevel.warning, (byte)alertDescription };
SafeWriteRecord(ContentType.alert, alert, 0, 2);
}
///
protected virtual void Receive13KeyUpdate(MemoryStream buf)
{
// TODO[tls13] This is interesting enough to notify the TlsPeer for possible logging/vetting
if (!(m_appDataReady && m_keyUpdateEnabled))
throw new TlsFatalAlert(AlertDescription.unexpected_message);
short requestUpdate = TlsUtilities.ReadUint8(buf);
AssertEmpty(buf);
if (!KeyUpdateRequest.IsValid(requestUpdate))
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
bool updateRequested = (KeyUpdateRequest.update_requested == requestUpdate);
TlsUtilities.Update13TrafficSecretPeer(Context);
m_recordStream.NotifyKeyUpdateReceived();
//this.m_keyUpdatePendingReceive &= updateRequested;
this.m_keyUpdatePendingSend |= updateRequested;
}
///
protected virtual void SendCertificateMessage(Certificate certificate, Stream endPointHash)
{
TlsContext context = Context;
SecurityParameters securityParameters = context.SecurityParameters;
if (null != securityParameters.LocalCertificate)
throw new TlsFatalAlert(AlertDescription.internal_error);
if (null == certificate)
{
certificate = Certificate.EmptyChain;
}
if (certificate.IsEmpty && !context.IsServer && securityParameters.NegotiatedVersion.IsSsl)
{
string message = "SSLv3 client didn't provide credentials";
RaiseAlertWarning(AlertDescription.no_certificate, message);
}
else
{
HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate);
certificate.Encode(context, message, endPointHash);
message.Send(this);
}
securityParameters.m_localCertificate = certificate;
}
///
protected virtual void Send13CertificateMessage(Certificate certificate)
{
if (null == certificate)
throw new TlsFatalAlert(AlertDescription.internal_error);
TlsContext context = Context;
SecurityParameters securityParameters = context.SecurityParameters;
if (null != securityParameters.LocalCertificate)
throw new TlsFatalAlert(AlertDescription.internal_error);
HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate);
certificate.Encode(context, message, null);
message.Send(this);
securityParameters.m_localCertificate = certificate;
}
///
protected virtual void Send13CertificateVerifyMessage(DigitallySigned certificateVerify)
{
HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate_verify);
certificateVerify.Encode(message);
message.Send(this);
}
///
protected virtual void SendChangeCipherSpec()
{
SendChangeCipherSpecMessage();
m_recordStream.EnablePendingCipherWrite();
}
///
protected virtual void SendChangeCipherSpecMessage()
{
byte[] message = new byte[]{ 1 };
SafeWriteRecord(ContentType.change_cipher_spec, message, 0, message.Length);
}
///
protected virtual void SendFinishedMessage()
{
TlsContext context = Context;
SecurityParameters securityParameters = context.SecurityParameters;
bool isServerContext = context.IsServer;
byte[] verify_data = TlsUtilities.CalculateVerifyData(context, m_handshakeHash, isServerContext);
securityParameters.m_localVerifyData = verify_data;
if (!m_resumedSession || securityParameters.IsExtendedMasterSecret)
{
if (null == securityParameters.PeerVerifyData)
{
securityParameters.m_tlsUnique = verify_data;
}
}
HandshakeMessageOutput.Send(this, HandshakeType.finished, verify_data);
}
///
protected virtual void Send13FinishedMessage()
{
TlsContext context = Context;
SecurityParameters securityParameters = context.SecurityParameters;
bool isServerContext = context.IsServer;
byte[] verify_data = TlsUtilities.CalculateVerifyData(context, m_handshakeHash, isServerContext);
securityParameters.m_localVerifyData = verify_data;
securityParameters.m_tlsUnique = null;
HandshakeMessageOutput.Send(this, HandshakeType.finished, verify_data);
}
///
protected virtual void Send13KeyUpdate(bool updateRequested)
{
// TODO[tls13] This is interesting enough to notify the TlsPeer for possible logging/vetting
if (!(m_appDataReady && m_keyUpdateEnabled))
throw new TlsFatalAlert(AlertDescription.internal_error);
short requestUpdate = updateRequested
? KeyUpdateRequest.update_requested
: KeyUpdateRequest.update_not_requested;
HandshakeMessageOutput.Send(this, HandshakeType.key_update, TlsUtilities.EncodeUint8(requestUpdate));
TlsUtilities.Update13TrafficSecretLocal(Context);
m_recordStream.NotifyKeyUpdateSent();
//this.m_keyUpdatePendingReceive |= updateRequested;
this.m_keyUpdatePendingSend &= updateRequested;
}
///
protected virtual void SendSupplementalDataMessage(IList supplementalData)
{
HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.supplemental_data);
WriteSupplementalData(message, supplementalData);
message.Send(this);
}
public virtual void Close()
{
applicationDataLock?.Dispose();
applicationDataLock = null;
HandleClose(true);
}
public virtual void Flush()
{
}
internal bool IsApplicationDataReady
{
get { return m_appDataReady; }
}
public virtual bool IsClosed
{
get { return m_closed; }
}
public virtual bool IsConnected
{
get
{
if (m_closed)
return false;
AbstractTlsContext context = ContextAdmin;
return null != context && context.IsConnected;
}
}
public virtual bool IsHandshaking
{
get
{
if (m_closed)
return false;
AbstractTlsContext context = ContextAdmin;
return null != context && context.IsHandshaking;
}
}
///
protected virtual short ProcessMaxFragmentLengthExtension(IDictionary clientExtensions,
IDictionary serverExtensions, short alertDescription)
{
short maxFragmentLength = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(serverExtensions);
if (maxFragmentLength >= 0)
{
if (!MaxFragmentLength.IsValid(maxFragmentLength)
|| (!m_resumedSession &&
maxFragmentLength != TlsExtensionsUtilities.GetMaxFragmentLengthExtension(clientExtensions)))
{
throw new TlsFatalAlert(alertDescription);
}
}
return maxFragmentLength;
}
///
protected virtual void RefuseRenegotiation()
{
/*
* RFC 5746 4.5 SSLv3 clients [..] SHOULD use a fatal handshake_failure alert.
*/
if (TlsUtilities.IsSsl(Context))
throw new TlsFatalAlert(AlertDescription.handshake_failure);
RaiseAlertWarning(AlertDescription.no_renegotiation, "Renegotiation not supported");
}
/// Make sure the 'buf' is now empty. Fail otherwise.
/// The to check.
///
internal static void AssertEmpty(MemoryStream buf)
{
if (buf.Position < buf.Length)
throw new TlsFatalAlert(AlertDescription.decode_error);
}
internal static byte[] CreateRandomBlock(bool useGmtUnixTime, TlsContext context)
{
byte[] result = context.NonceGenerator.GenerateNonce(32);
if (useGmtUnixTime)
{
TlsUtilities.WriteGmtUnixTime(result, 0);
}
return result;
}
///
internal static byte[] CreateRenegotiationInfo(byte[] renegotiated_connection)
{
return TlsUtilities.EncodeOpaque8(renegotiated_connection);
}
///
internal static void EstablishMasterSecret(TlsContext context, TlsKeyExchange keyExchange)
{
TlsSecret preMasterSecret = keyExchange.GeneratePreMasterSecret();
if (preMasterSecret == null)
throw new TlsFatalAlert(AlertDescription.internal_error);
try
{
context.SecurityParameters.m_masterSecret = TlsUtilities.CalculateMasterSecret(context,
preMasterSecret);
if (context.SecurityParameters.NegotiatedVersion != ProtocolVersion.TLSv13)
KeyLogFileWriter.WriteLabel(Labels.CLIENT_RANDOM, context.SecurityParameters);
}
finally
{
/*
* RFC 2246 8.1. The pre_master_secret should be deleted from memory once the
* master_secret has been computed.
*/
preMasterSecret.Destroy();
}
}
///
internal static IDictionary ReadExtensions(MemoryStream input)
{
if (input.Position >= input.Length)
return null;
byte[] extBytes = TlsUtilities.ReadOpaque16(input);
AssertEmpty(input);
return ReadExtensionsData(extBytes);
}
///
internal static IDictionary ReadExtensionsData(byte[] extBytes)
{
// Int32 -> byte[]
IDictionary extensions = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable();
if (extBytes.Length > 0)
{
MemoryStream buf = new MemoryStream(extBytes, false);
do
{
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.
*/
Int32 key = extension_type;
if (extensions.Contains(key))
throw new TlsFatalAlert(AlertDescription.illegal_parameter,
"Repeated extension: " + ExtensionType.GetText(extension_type));
extensions.Add(key, extension_data);
}
while (buf.Position < buf.Length);
}
return extensions;
}
///
internal static IDictionary ReadExtensionsData13(int handshakeType, byte[] extBytes)
{
// Int32 -> byte[]
IDictionary extensions = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable();
if (extBytes.Length > 0)
{
MemoryStream buf = new MemoryStream(extBytes, false);
do
{
int extension_type = TlsUtilities.ReadUint16(buf);
if (!TlsUtilities.IsPermittedExtensionType13(handshakeType, extension_type))
{
throw new TlsFatalAlert(AlertDescription.illegal_parameter,
"Invalid extension: " + ExtensionType.GetText(extension_type));
}
byte[] extension_data = TlsUtilities.ReadOpaque16(buf);
/*
* RFC 3546 2.3 There MUST NOT be more than one extension of the same type.
*/
Int32 key = extension_type;
if (extensions.Contains(key))
throw new TlsFatalAlert(AlertDescription.illegal_parameter,
"Repeated extension: " + ExtensionType.GetText(extension_type));
extensions.Add(key, extension_data);
}
while (buf.Position < buf.Length);
}
return extensions;
}
///
internal static IDictionary ReadExtensionsDataClientHello(byte[] extBytes)
{
/*
* TODO[tls13] We are currently allowing any extensions to appear in ClientHello. It is
* somewhat complicated to restrict what can appear based on the specific set of versions
* the client is offering, and anyway could be fragile since clients may take a
* "kitchen sink" approach to adding extensions independently of the offered versions.
*/
// Int32 -> byte[]
IDictionary extensions = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable();
if (extBytes.Length > 0)
{
MemoryStream buf = new MemoryStream(extBytes, false);
int extension_type;
bool pre_shared_key_found = false;
do
{
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.
*/
Int32 key = extension_type;
if (extensions.Contains(key))
throw new TlsFatalAlert(AlertDescription.illegal_parameter,
"Repeated extension: " + ExtensionType.GetText(extension_type));
extensions.Add(key, extension_data);
pre_shared_key_found |= (ExtensionType.pre_shared_key == extension_type);
}
while (buf.Position < buf.Length);
if (pre_shared_key_found && (ExtensionType.pre_shared_key != extension_type))
throw new TlsFatalAlert(AlertDescription.illegal_parameter,
"'pre_shared_key' MUST be last in ClientHello");
}
return extensions;
}
///
internal static IList ReadSupplementalDataMessage(MemoryStream input)
{
byte[] supp_data = TlsUtilities.ReadOpaque24(input, 1);
AssertEmpty(input);
MemoryStream buf = new MemoryStream(supp_data, false);
IList supplementalData = BestHTTP.SecureProtocol.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;
}
///
internal static void WriteExtensions(Stream output, IDictionary extensions)
{
WriteExtensions(output, extensions, 0);
}
///
internal static void WriteExtensions(Stream output, IDictionary extensions, int bindersSize)
{
if (null == extensions || extensions.Count < 1)
return;
byte[] extBytes = WriteExtensionsData(extensions, bindersSize);
int lengthWithBinders = extBytes.Length + bindersSize;
TlsUtilities.CheckUint16(lengthWithBinders);
TlsUtilities.WriteUint16(lengthWithBinders, output);
output.Write(extBytes, 0, extBytes.Length);
}
///
internal static byte[] WriteExtensionsData(IDictionary extensions)
{
return WriteExtensionsData(extensions, 0);
}
///
internal static byte[] WriteExtensionsData(IDictionary extensions, int bindersSize)
{
MemoryStream buf = new MemoryStream();
WriteExtensionsData(extensions, buf, bindersSize);
return buf.ToArray();
}
///
internal static void WriteExtensionsData(IDictionary extensions, MemoryStream buf)
{
WriteExtensionsData(extensions, buf, 0);
}
///
internal static void WriteExtensionsData(IDictionary extensions, MemoryStream buf, int bindersSize)
{
/*
* 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);
WritePreSharedKeyExtension(buf, extensions, bindersSize);
}
///
internal static void WritePreSharedKeyExtension(MemoryStream buf, IDictionary extensions, int bindersSize)
{
byte[] extension_data = (byte[])extensions[ExtensionType.pre_shared_key];
if (null != extension_data)
{
TlsUtilities.CheckUint16(ExtensionType.pre_shared_key);
TlsUtilities.WriteUint16(ExtensionType.pre_shared_key, buf);
int lengthWithBinders = extension_data.Length + bindersSize;
TlsUtilities.CheckUint16(lengthWithBinders);
TlsUtilities.WriteUint16(lengthWithBinders, buf);
buf.Write(extension_data, 0, extension_data.Length);
}
}
///
internal static void WriteSelectedExtensions(Stream output, IDictionary extensions, bool selectEmpty)
{
foreach (Int32 key in extensions.Keys)
{
int extension_type = key;
// NOTE: Must be last; handled by 'WritePreSharedKeyExtension'
if (ExtensionType.pre_shared_key == extension_type)
continue;
byte[] extension_data = (byte[])extensions[key];
if (selectEmpty == (extension_data.Length == 0))
{
TlsUtilities.CheckUint16(extension_type);
TlsUtilities.WriteUint16(extension_type, output);
TlsUtilities.WriteOpaque16(extension_data, output);
}
}
}
///
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);
}
}
}
#pragma warning restore
#endif