You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2032 lines
76 KiB
2032 lines
76 KiB
1 year ago
|
#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);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
protected virtual void CloseConnection()
|
||
|
{
|
||
|
m_recordStream.Close();
|
||
|
}
|
||
|
|
||
|
protected abstract TlsContext Context { get; }
|
||
|
|
||
|
internal abstract AbstractTlsContext ContextAdmin { get; }
|
||
|
|
||
|
protected abstract TlsPeer Peer { get; }
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
protected virtual void HandleAlertMessage(short alertLevel, short alertDescription)
|
||
|
{
|
||
|
Peer.NotifyAlertReceived(alertLevel, alertDescription);
|
||
|
|
||
|
if (alertLevel == AlertLevel.warning)
|
||
|
{
|
||
|
HandleAlertWarningMessage(alertDescription);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
HandleFailure();
|
||
|
|
||
|
throw new TlsFatalAlertReceived(alertDescription);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
protected virtual void HandleChangeCipherSpecMessage()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
protected abstract void HandleHandshakeMessage(short type, HandshakeMessageInput buf);
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
protected virtual void CheckReceivedChangeCipherSpec(bool expected)
|
||
|
{
|
||
|
if (expected != m_receivedChangeCipherSpec)
|
||
|
throw new TlsFatalAlert(AlertDescription.unexpected_message);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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.
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>This method is called, when a change cipher spec message is received.</summary>
|
||
|
/// <exception cref="IOException">If the message has an invalid content or the handshake is not in the correct
|
||
|
/// state.</exception>
|
||
|
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; }
|
||
|
}
|
||
|
|
||
|
/// <summary>Read data from the network.</summary>
|
||
|
/// <remarks>
|
||
|
/// 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.
|
||
|
/// </remarks>
|
||
|
/// <param name="buf">The buffer where the data will be copied to.</param>
|
||
|
/// <param name="off">The position where the data will be placed in the buffer.</param>
|
||
|
/// <param name="len">The maximum number of bytes to read.</param>
|
||
|
/// <returns>The number of bytes read.</returns>
|
||
|
/// <exception cref="IOException">If something goes wrong during reading data.</exception>
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Write some application data.</summary>
|
||
|
/// <remarks>
|
||
|
/// Fragmentation is handled internally. Usable in both blocking/non-blocking modes.<br/><br/>
|
||
|
/// In blocking mode, the output will be automatically sent via the underlying transport. In non-blocking mode,
|
||
|
/// call <see cref="ReadOutput(byte[], int, int)"/> to get the output bytes to send to the peer.<br/><br/>
|
||
|
/// This method must not be called until after the initial handshake is complete. Attempting to call it earlier
|
||
|
/// will result in an <see cref="InvalidOperationException"/>.
|
||
|
/// </remarks>
|
||
|
/// <param name="buf">The buffer containing application data to send.</param>
|
||
|
/// <param name="off">The offset at which the application data begins</param>
|
||
|
/// <param name="len">The number of bytes of application data.</param>
|
||
|
/// <exception cref="InvalidOperationException">If called before the initial handshake has completed.
|
||
|
/// </exception>
|
||
|
/// <exception cref="IOException">If connection is already closed, or for encryption or transport errors.
|
||
|
/// </exception>
|
||
|
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; }
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/// <summary>The secure bidirectional stream for this connection</summary>
|
||
|
/// <remarks>Only allowed in blocking mode.</remarks>
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Should be called in non-blocking mode when the input data reaches EOF.</summary>
|
||
|
/// <exception cref="IOException"/>
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Equivalent to <code>OfferInput(input, 0, input.Length)</code>.</summary>
|
||
|
/// <param name="input">The input buffer to offer.</param>
|
||
|
/// <exception cref="IOException"/>
|
||
|
/// <seealso cref="OfferInput(byte[], int, int)"/>
|
||
|
public virtual void OfferInput(byte[] input)
|
||
|
{
|
||
|
OfferInput(input, 0, input.Length);
|
||
|
}
|
||
|
|
||
|
/// <summary>Offer input from an arbitrary source.</summary>
|
||
|
/// <remarks>Only allowed in non-blocking mode.<br/><br/>
|
||
|
/// 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.<br/><br/>
|
||
|
/// If any records containing application data were processed, the decrypted data can be obtained using
|
||
|
/// <see cref="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 <see cref="GetAvailableOutputBytes"/>.
|
||
|
/// </remarks>
|
||
|
/// <param name="input">The input buffer to offer.</param>
|
||
|
/// <param name="inputOff">The offset within the input buffer that input begins.</param>
|
||
|
/// <param name="inputLen">The number of bytes of input being offered.</param>
|
||
|
/// <exception cref="IOException">If an error occurs while decrypting or processing a record.</exception>
|
||
|
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; }
|
||
|
}
|
||
|
|
||
|
/// <summary>Gets the amount of received application data.</summary>
|
||
|
/// <remarks>A call to <see cref="readInput(byte[], int, int)"/> is guaranteed to be able to return at least
|
||
|
/// this much data.<br/><br/>
|
||
|
/// Only allowed in non-blocking mode.
|
||
|
/// </remarks>
|
||
|
/// <returns>The number of bytes of available application data.</returns>
|
||
|
public virtual int GetAvailableInputBytes()
|
||
|
{
|
||
|
if (m_blocking)
|
||
|
throw new InvalidOperationException("Cannot use GetAvailableInputBytes() in blocking mode!");
|
||
|
|
||
|
return ApplicationDataAvailable;
|
||
|
}
|
||
|
|
||
|
/// <summary>Retrieves received application data.</summary>
|
||
|
/// <remarks>
|
||
|
/// Use <see cref="GetAvailableInputBytes"/> to check how much application data is currently available. This
|
||
|
/// method functions similarly to <see cref="Stream.Read(byte[], int, int)"/>, except that it never blocks. If
|
||
|
/// no data is available, nothing will be copied and zero will be returned.<br/><br/>
|
||
|
/// Only allowed in non-blocking mode.
|
||
|
/// </remarks>
|
||
|
/// <param name="buf">The buffer to hold the application data.</param>
|
||
|
/// <param name="off">The start offset in the buffer at which the data is written.</param>
|
||
|
/// <param name="len">The maximum number of bytes to read.</param>
|
||
|
/// <returns>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.</returns>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <summary>Gets the amount of encrypted data available to be sent.</summary>
|
||
|
/// <remarks>
|
||
|
/// A call to <see cref="ReadOutput(byte[], int, int)"/> is guaranteed to be able to return at least this much
|
||
|
/// data. Only allowed in non-blocking mode.
|
||
|
/// </remarks>
|
||
|
/// <returns>The number of bytes of available encrypted data.</returns>
|
||
|
public virtual int GetAvailableOutputBytes()
|
||
|
{
|
||
|
if (m_blocking)
|
||
|
throw new InvalidOperationException("Cannot use GetAvailableOutputBytes() in blocking mode! Use Stream instead.");
|
||
|
|
||
|
return m_outputBuffer.Buffer.Available;
|
||
|
}
|
||
|
|
||
|
/// <summary>Retrieves encrypted data to be sent.</summary>
|
||
|
/// <remarks>
|
||
|
/// Use <see cref="GetAvailableOutputBytes"/> to check how much encrypted data is currently available. This
|
||
|
/// method functions similarly to <see cref="Stream.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.
|
||
|
/// </remarks>
|
||
|
/// <param name="buffer">The buffer to hold the encrypted data.</param>
|
||
|
/// <param name="offset">The start offset in the buffer at which the data is written.</param>
|
||
|
/// <param name="length">The maximum number of bytes to read.</param>
|
||
|
/// <returns>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.</returns>
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
protected virtual void Send13CertificateVerifyMessage(DigitallySigned certificateVerify)
|
||
|
{
|
||
|
HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate_verify);
|
||
|
certificateVerify.Encode(message);
|
||
|
message.Send(this);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
protected virtual void SendChangeCipherSpec()
|
||
|
{
|
||
|
SendChangeCipherSpecMessage();
|
||
|
m_recordStream.EnablePendingCipherWrite();
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
protected virtual void SendChangeCipherSpecMessage()
|
||
|
{
|
||
|
byte[] message = new byte[]{ 1 };
|
||
|
SafeWriteRecord(ContentType.change_cipher_spec, message, 0, message.Length);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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");
|
||
|
}
|
||
|
|
||
|
/// <summary>Make sure the <see cref="Stream"/> 'buf' is now empty. Fail otherwise.</summary>
|
||
|
/// <param name="buf">The <see cref="Stream"/> to check.</param>
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
internal static byte[] CreateRenegotiationInfo(byte[] renegotiated_connection)
|
||
|
{
|
||
|
return TlsUtilities.EncodeOpaque8(renegotiated_connection);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
internal static IDictionary ReadExtensions(MemoryStream input)
|
||
|
{
|
||
|
if (input.Position >= input.Length)
|
||
|
return null;
|
||
|
|
||
|
byte[] extBytes = TlsUtilities.ReadOpaque16(input);
|
||
|
|
||
|
AssertEmpty(input);
|
||
|
|
||
|
return ReadExtensionsData(extBytes);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
internal static void WriteExtensions(Stream output, IDictionary extensions)
|
||
|
{
|
||
|
WriteExtensions(output, extensions, 0);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
internal static byte[] WriteExtensionsData(IDictionary extensions)
|
||
|
{
|
||
|
return WriteExtensionsData(extensions, 0);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
internal static byte[] WriteExtensionsData(IDictionary extensions, int bindersSize)
|
||
|
{
|
||
|
MemoryStream buf = new MemoryStream();
|
||
|
WriteExtensionsData(extensions, buf, bindersSize);
|
||
|
return buf.ToArray();
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
internal static void WriteExtensionsData(IDictionary extensions, MemoryStream buf)
|
||
|
{
|
||
|
WriteExtensionsData(extensions, buf, 0);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <exception cref="IOException"/>
|
||
|
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
|