| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204 |
- #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
- #pragma warning disable
- using System;
- using System.Collections.Generic;
- using System.IO;
- using BestHTTP.Connections.TLS;
- using BestHTTP.PlatformSupport.Threading;
- using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto;
- using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
- using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.IO;
- 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<int, byte[]> m_clientExtensions = null;
- protected IDictionary<int, byte[]> m_serverExtensions = null;
- protected short m_connectionState = CS_START;
- 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_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_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="buffer">The buffer where the data will be copied to.</param>
- /// <param name="offset">The position where the data will be placed in the buffer.</param>
- /// <param name="count">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[] buffer, int offset, int count)
- {
- Streams.ValidateBufferArguments(buffer, offset, count);
- #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
- return ReadApplicationData(buffer.AsSpan(offset, count));
- #else
- if (!m_appDataReady)
- throw new InvalidOperationException("Cannot read application data until initial handshake completed.");
- using (new WriteLock(this.applicationDataLock))
- {
- while (m_applicationDataQueue.Available < 1)
- {
- if (this.m_closed)
- {
- if (this.m_failedWithError)
- throw new IOException("Cannot read application data on failed TLS connection");
- return 0;
- }
- /*
- * NOTE: Only called more than once when empty records are received, so no special
- * InterruptedIOException handling is necessary.
- */
- SafeReadRecord();
- }
- if (count > 0)
- {
- count = System.Math.Min(count, m_applicationDataQueue.Available);
- m_applicationDataQueue.RemoveData(buffer, offset, count, 0);
- }
- return count;
- }
- #endif
- }
- System.Threading.ReaderWriterLockSlim applicationDataLock = new System.Threading.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;
- }
- }
- #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
- public virtual int ReadApplicationData(Span<byte> buffer)
- {
- if (!m_appDataReady)
- throw new InvalidOperationException("Cannot read application data until initial handshake completed.");
- while (m_applicationDataQueue.Available < 1)
- {
- if (this.m_closed)
- {
- if (this.m_failedWithError)
- throw new IOException("Cannot read application data on failed TLS connection");
- return 0;
- }
- /*
- * NOTE: Only called more than once when empty records are received, so no special
- * InterruptedIOException handling is necessary.
- */
- SafeReadRecord();
- }
- int count = buffer.Length;
- if (count > 0)
- {
- count = System.Math.Min(count, m_applicationDataQueue.Available);
- m_applicationDataQueue.RemoveData(buffer[..count], 0);
- }
- return count;
- }
- #endif
- /// <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;
- }
- catch (TlsFatalAlert e)
- {
- HandleException(e.AlertDescription, "Failed to read record", e);
- throw;
- }
- catch (IOException e)
- {
- HandleException(AlertDescription.internal_error, "Failed to read record", e);
- throw;
- }
- 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);
- }
- }
- #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
- /// <exception cref="IOException"/>
- protected virtual void SafeWriteRecord(short type, ReadOnlySpan<byte> buffer)
- {
- try
- {
- m_recordStream.WriteRecord(type, buffer);
- }
- 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);
- }
- }
- #endif
- /// <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="buffer">The buffer containing application data to send.</param>
- /// <param name="offset">The offset at which the application data begins</param>
- /// <param name="count">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[] buffer, int offset, int count)
- {
- Streams.ValidateBufferArguments(buffer, offset, count);
- #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
- WriteApplicationData(buffer.AsSpan(offset, count));
- #else
- if (!m_appDataReady)
- throw new InvalidOperationException(
- "Cannot write application data until initial handshake completed.");
- //lock (m_recordWriteLock)
- {
- while (count > 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 (count > 1)
- {
- SafeWriteRecord(ContentType.application_data, buffer, offset, 1);
- ++offset;
- --count;
- }
- 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(count, m_recordStream.PlaintextLimit);
- SafeWriteRecord(ContentType.application_data, buffer, offset, toWrite);
- offset += toWrite;
- count -= toWrite;
- }
- }
- #endif
- }
- #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
- public virtual void WriteApplicationData(ReadOnlySpan<byte> buffer)
- {
- if (!m_appDataReady)
- throw new InvalidOperationException(
- "Cannot write application data until initial handshake completed.");
- //lock (m_recordWriteLock)
- {
- while (!buffer.IsEmpty)
- {
- 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 (buffer.Length > 1)
- {
- SafeWriteRecord(ContentType.application_data, buffer[..1]);
- buffer = buffer[1..];
- }
- 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(buffer.Length, m_recordStream.PlaintextLimit);
- SafeWriteRecord(ContentType.application_data, buffer[..toWrite]);
- buffer = buffer[toWrite..];
- }
- }
- }
- #endif
- 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);
- }
- public virtual int PreviewOutputRecord()
- {
- if (m_blocking)
- throw new InvalidOperationException("Cannot use PreviewOutputRecord() in blocking mode!");
- ByteQueue buffer = m_outputBuffer.Buffer;
- int available = buffer.Available;
- if (available < 1)
- return 0;
- if (available >= RecordFormat.FragmentOffset)
- {
- int length = buffer.ReadUint16(RecordFormat.LengthOffset);
- int recordSize = RecordFormat.FragmentOffset + length;
- if (available >= recordSize)
- return recordSize;
- }
- throw new InvalidOperationException("Can only use PreviewOutputRecord() for record-aligned output.");
- }
- /// <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).
- */
- }
- 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;
- #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
- Span<byte> verify_data = stackalloc byte[securityParameters.VerifyDataLength];
- TlsUtilities.ReadFully(verify_data, buf);
- #else
- byte[] verify_data = TlsUtilities.ReadFully(securityParameters.VerifyDataLength, buf);
- #endif
- 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 (!securityParameters.IsResumedSession || 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;
- #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || _UNITY_2021_2_OR_NEWER_
- Span<byte> verify_data = stackalloc byte[securityParameters.VerifyDataLength];
- TlsUtilities.ReadFully(verify_data, buf);
- #else
- byte[] verify_data = TlsUtilities.ReadFully(securityParameters.VerifyDataLength, buf);
- #endif
- 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 (!securityParameters.IsResumedSession || 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<SupplementalDataEntry> 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<int, byte[]> clientExtensions,
- IDictionary<int, byte[]> serverExtensions, short alertDescription)
- {
- short maxFragmentLength = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(serverExtensions);
- if (maxFragmentLength >= 0)
- {
- if (!MaxFragmentLength.IsValid(maxFragmentLength) ||
- (clientExtensions != null &&
- 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<int, byte[]> 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<int, byte[]> ReadExtensionsData(byte[] extBytes)
- {
- // Int32 -> byte[]
- var extensions = new Dictionary<int, byte[]>();
- 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.
- */
- if (extensions.ContainsKey(extension_type))
- throw new TlsFatalAlert(AlertDescription.illegal_parameter,
- "Repeated extension: " + ExtensionType.GetText(extension_type));
- extensions.Add(extension_type, extension_data);
- }
- while (buf.Position < buf.Length);
- }
- return extensions;
- }
- /// <exception cref="IOException"/>
- internal static IDictionary<int, byte[]> ReadExtensionsData13(int handshakeType, byte[] extBytes)
- {
- // Int32 -> byte[]
- var extensions = new Dictionary<int, byte[]>();
- 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.
- */
- if (extensions.ContainsKey(extension_type))
- throw new TlsFatalAlert(AlertDescription.illegal_parameter,
- "Repeated extension: " + ExtensionType.GetText(extension_type));
- extensions.Add(extension_type, extension_data);
- }
- while (buf.Position < buf.Length);
- }
- return extensions;
- }
- /// <exception cref="IOException"/>
- internal static IDictionary<int, byte[]> 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[]
- var extensions = new Dictionary<int, byte[]>();
- 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.
- */
- if (extensions.ContainsKey(extension_type))
- throw new TlsFatalAlert(AlertDescription.illegal_parameter,
- "Repeated extension: " + ExtensionType.GetText(extension_type));
- extensions.Add(extension_type, 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<SupplementalDataEntry> ReadSupplementalDataMessage(MemoryStream input)
- {
- byte[] supp_data = TlsUtilities.ReadOpaque24(input, 1);
- AssertEmpty(input);
- MemoryStream buf = new MemoryStream(supp_data, false);
- var supplementalData = new List<SupplementalDataEntry>();
- 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<int, byte[]> extensions)
- {
- WriteExtensions(output, extensions, 0);
- }
- /// <exception cref="IOException"/>
- internal static void WriteExtensions(Stream output, IDictionary<int, byte[]> 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<int, byte[]> extensions)
- {
- return WriteExtensionsData(extensions, 0);
- }
- /// <exception cref="IOException"/>
- internal static byte[] WriteExtensionsData(IDictionary<int, byte[]> extensions, int bindersSize)
- {
- MemoryStream buf = new MemoryStream();
- WriteExtensionsData(extensions, buf, bindersSize);
- return buf.ToArray();
- }
- /// <exception cref="IOException"/>
- internal static void WriteExtensionsData(IDictionary<int, byte[]> extensions, MemoryStream buf)
- {
- WriteExtensionsData(extensions, buf, 0);
- }
- /// <exception cref="IOException"/>
- internal static void WriteExtensionsData(IDictionary<int, byte[]> 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<int, byte[]> extensions,
- int bindersSize)
- {
- if (extensions.TryGetValue(ExtensionType.pre_shared_key, out var 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<int, byte[]> extensions,
- bool selectEmpty)
- {
- foreach (var extension in extensions)
- {
- int extension_type = extension.Key;
- // NOTE: Must be last; handled by 'WritePreSharedKeyExtension'
- if (ExtensionType.pre_shared_key == extension_type)
- continue;
- byte[] extension_data = extension.Value;
- 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<SupplementalDataEntry> 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
|