1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008 |
- #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
- #pragma warning disable
- using System;
- using System.Collections;
- using System.IO;
- using BestHTTP.Connections.TLS;
- 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);
- }
- 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;
- 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;
- }
- public int TestApplicationData()
- {
- 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()
- {
- 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
|