123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563 |
- #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
- #pragma warning disable
- using System;
- using System.Collections;
- using System.IO;
- using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
- using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Date;
- namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Tls
- {
- internal class DtlsReliableHandshake
- {
- private const int MAX_RECEIVE_AHEAD = 16;
- private const int MESSAGE_HEADER_LENGTH = 12;
- internal const int INITIAL_RESEND_MILLIS = 1000;
- private const int MAX_RESEND_MILLIS = 60000;
- /// <exception cref="IOException"/>
- internal static DtlsRequest ReadClientRequest(byte[] data, int dataOff, int dataLen, Stream dtlsOutput)
- {
- // TODO Support the possibility of a fragmented ClientHello datagram
- byte[] message = DtlsRecordLayer.ReceiveClientHelloRecord(data, dataOff, dataLen);
- if (null == message || message.Length < MESSAGE_HEADER_LENGTH)
- return null;
- long recordSeq = TlsUtilities.ReadUint48(data, dataOff + 5);
- short msgType = TlsUtilities.ReadUint8(message, 0);
- if (HandshakeType.client_hello != msgType)
- return null;
- int length = TlsUtilities.ReadUint24(message, 1);
- if (message.Length != MESSAGE_HEADER_LENGTH + length)
- return null;
- // TODO Consider stricter HelloVerifyRequest-related checks
- //int messageSeq = TlsUtilities.ReadUint16(message, 4);
- //if (messageSeq > 1)
- // return null;
- int fragmentOffset = TlsUtilities.ReadUint24(message, 6);
- if (0 != fragmentOffset)
- return null;
- int fragmentLength = TlsUtilities.ReadUint24(message, 9);
- if (length != fragmentLength)
- return null;
- ClientHello clientHello = ClientHello.Parse(
- new MemoryStream(message, MESSAGE_HEADER_LENGTH, length, false), dtlsOutput);
- return new DtlsRequest(recordSeq, message, clientHello);
- }
- /// <exception cref="IOException"/>
- internal static void SendHelloVerifyRequest(DatagramSender sender, long recordSeq, byte[] cookie)
- {
- TlsUtilities.CheckUint8(cookie.Length);
- int length = 3 + cookie.Length;
- byte[] message = new byte[MESSAGE_HEADER_LENGTH + length];
- TlsUtilities.WriteUint8(HandshakeType.hello_verify_request, message, 0);
- TlsUtilities.WriteUint24(length, message, 1);
- //TlsUtilities.WriteUint16(0, message, 4);
- //TlsUtilities.WriteUint24(0, message, 6);
- TlsUtilities.WriteUint24(length, message, 9);
- // HelloVerifyRequest fields
- TlsUtilities.WriteVersion(ProtocolVersion.DTLSv10, message, MESSAGE_HEADER_LENGTH + 0);
- TlsUtilities.WriteOpaque8(cookie, message, MESSAGE_HEADER_LENGTH + 2);
- DtlsRecordLayer.SendHelloVerifyRequestRecord(sender, recordSeq, message);
- }
- /*
- * No 'final' modifiers so that it works in earlier JDKs
- */
- private DtlsRecordLayer m_recordLayer;
- private Timeout m_handshakeTimeout;
- private TlsHandshakeHash m_handshakeHash;
- private IDictionary m_currentInboundFlight = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable();
- private IDictionary m_previousInboundFlight = null;
- private IList m_outboundFlight = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateArrayList();
- private int m_resendMillis = -1;
- private Timeout m_resendTimeout = null;
- private int m_next_send_seq = 0, m_next_receive_seq = 0;
- internal DtlsReliableHandshake(TlsContext context, DtlsRecordLayer transport, int timeoutMillis,
- DtlsRequest request)
- {
- this.m_recordLayer = transport;
- this.m_handshakeHash = new DeferredHash(context);
- this.m_handshakeTimeout = Timeout.ForWaitMillis(timeoutMillis);
- if (null != request)
- {
- this.m_resendMillis = INITIAL_RESEND_MILLIS;
- this.m_resendTimeout = new Timeout(m_resendMillis);
- long recordSeq = request.RecordSeq;
- int messageSeq = request.MessageSeq;
- byte[] message = request.Message;
- m_recordLayer.ResetAfterHelloVerifyRequestServer(recordSeq);
- // Simulate a previous flight consisting of the request ClientHello
- DtlsReassembler reassembler = new DtlsReassembler(HandshakeType.client_hello,
- message.Length - MESSAGE_HEADER_LENGTH);
- m_currentInboundFlight[messageSeq] = reassembler;
- // We sent HelloVerifyRequest with (message) sequence number 0
- this.m_next_send_seq = 1;
- this.m_next_receive_seq = messageSeq + 1;
- m_handshakeHash.Update(message, 0, message.Length);
- }
- }
- internal void ResetAfterHelloVerifyRequestClient()
- {
- this.m_currentInboundFlight = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable();
- this.m_previousInboundFlight = null;
- this.m_outboundFlight = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateArrayList();
- this.m_resendMillis = -1;
- this.m_resendTimeout = null;
- // We're waiting for ServerHello, always with (message) sequence number 1
- this.m_next_receive_seq = 1;
- m_handshakeHash.Reset();
- }
- internal TlsHandshakeHash HandshakeHash
- {
- get { return m_handshakeHash; }
- }
- internal TlsHandshakeHash PrepareToFinish()
- {
- TlsHandshakeHash result = m_handshakeHash;
- this.m_handshakeHash = m_handshakeHash.StopTracking();
- return result;
- }
- /// <exception cref="IOException"/>
- internal void SendMessage(short msg_type, byte[] body)
- {
- TlsUtilities.CheckUint24(body.Length);
- if (null != m_resendTimeout)
- {
- CheckInboundFlight();
- this.m_resendMillis = -1;
- this.m_resendTimeout = null;
- m_outboundFlight.Clear();
- }
- Message message = new Message(m_next_send_seq++, msg_type, body);
- m_outboundFlight.Add(message);
- WriteMessage(message);
- UpdateHandshakeMessagesDigest(message);
- }
- /// <exception cref="IOException"/>
- internal byte[] ReceiveMessageBody(short msg_type)
- {
- Message message = ReceiveMessage();
- if (message.Type != msg_type)
- throw new TlsFatalAlert(AlertDescription.unexpected_message);
- return message.Body;
- }
- /// <exception cref="IOException"/>
- internal Message ReceiveMessage()
- {
- long currentTimeMillis = DateTimeUtilities.CurrentUnixMs();
- if (null == m_resendTimeout)
- {
- m_resendMillis = INITIAL_RESEND_MILLIS;
- m_resendTimeout = new Timeout(m_resendMillis, currentTimeMillis);
- PrepareInboundFlight(BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable());
- }
- byte[] buf = null;
- for (;;)
- {
- if (m_recordLayer.IsClosed)
- throw new TlsFatalAlert(AlertDescription.user_canceled);
- Message pending = GetPendingMessage();
- if (pending != null)
- return pending;
- if (Timeout.HasExpired(m_handshakeTimeout, currentTimeMillis))
- throw new TlsTimeoutException("Handshake timed out");
- int waitMillis = Timeout.GetWaitMillis(m_handshakeTimeout, currentTimeMillis);
- waitMillis = Timeout.ConstrainWaitMillis(waitMillis, m_resendTimeout, currentTimeMillis);
- // NOTE: Ensure a finite wait, of at least 1ms
- if (waitMillis < 1)
- {
- waitMillis = 1;
- }
- int receiveLimit = m_recordLayer.GetReceiveLimit();
- if (buf == null || buf.Length < receiveLimit)
- {
- buf = new byte[receiveLimit];
- }
- int received = m_recordLayer.Receive(buf, 0, receiveLimit, waitMillis);
- if (received < 0)
- {
- ResendOutboundFlight();
- }
- else
- {
- ProcessRecord(MAX_RECEIVE_AHEAD, m_recordLayer.ReadEpoch, buf, 0, received);
- }
- currentTimeMillis = DateTimeUtilities.CurrentUnixMs();
- }
- }
- internal void Finish()
- {
- DtlsHandshakeRetransmit retransmit = null;
- if (null != m_resendTimeout)
- {
- CheckInboundFlight();
- }
- else
- {
- PrepareInboundFlight(null);
- if (m_previousInboundFlight != null)
- {
- /*
- * RFC 6347 4.2.4. In addition, for at least twice the default MSL defined for [TCP],
- * when in the FINISHED state, the node that transmits the last flight (the server in an
- * ordinary handshake or the client in a resumed handshake) MUST respond to a retransmit
- * of the peer's last flight with a retransmit of the last flight.
- */
- retransmit = new Retransmit(this);
- }
- }
- m_recordLayer.HandshakeSuccessful(retransmit);
- }
- internal static int BackOff(int timeoutMillis)
- {
- /*
- * TODO[DTLS] implementations SHOULD back off handshake packet size during the
- * retransmit backoff.
- */
- return System.Math.Min(timeoutMillis * 2, MAX_RESEND_MILLIS);
- }
- /**
- * Check that there are no "extra" messages left in the current inbound flight
- */
- private void CheckInboundFlight()
- {
- foreach (int key in m_currentInboundFlight.Keys)
- {
- if (key >= m_next_receive_seq)
- {
- // TODO Should this be considered an error?
- }
- }
- }
- /// <exception cref="IOException"/>
- private Message GetPendingMessage()
- {
- DtlsReassembler next = (DtlsReassembler)m_currentInboundFlight[m_next_receive_seq];
- if (next != null)
- {
- byte[] body = next.GetBodyIfComplete();
- if (body != null)
- {
- m_previousInboundFlight = null;
- return UpdateHandshakeMessagesDigest(new Message(m_next_receive_seq++, next.MsgType, body));
- }
- }
- return null;
- }
- private void PrepareInboundFlight(IDictionary nextFlight)
- {
- ResetAll(m_currentInboundFlight);
- m_previousInboundFlight = m_currentInboundFlight;
- m_currentInboundFlight = nextFlight;
- }
- /// <exception cref="IOException"/>
- private void ProcessRecord(int windowSize, int epoch, byte[] buf, int off, int len)
- {
- bool checkPreviousFlight = false;
- while (len >= MESSAGE_HEADER_LENGTH)
- {
- int fragment_length = TlsUtilities.ReadUint24(buf, off + 9);
- int message_length = fragment_length + MESSAGE_HEADER_LENGTH;
- if (len < message_length)
- {
- // NOTE: Truncated message - ignore it
- break;
- }
- int length = TlsUtilities.ReadUint24(buf, off + 1);
- int fragment_offset = TlsUtilities.ReadUint24(buf, off + 6);
- if (fragment_offset + fragment_length > length)
- {
- // NOTE: Malformed fragment - ignore it and the rest of the record
- break;
- }
- /*
- * NOTE: This very simple epoch check will only work until we want to support
- * renegotiation (and we're not likely to do that anyway).
- */
- short msg_type = TlsUtilities.ReadUint8(buf, off + 0);
- int expectedEpoch = msg_type == HandshakeType.finished ? 1 : 0;
- if (epoch != expectedEpoch)
- break;
- int message_seq = TlsUtilities.ReadUint16(buf, off + 4);
- if (message_seq >= (m_next_receive_seq + windowSize))
- {
- // NOTE: Too far ahead - ignore
- }
- else if (message_seq >= m_next_receive_seq)
- {
- DtlsReassembler reassembler = (DtlsReassembler)m_currentInboundFlight[message_seq];
- if (reassembler == null)
- {
- reassembler = new DtlsReassembler(msg_type, length);
- m_currentInboundFlight[message_seq] = reassembler;
- }
- reassembler.ContributeFragment(msg_type, length, buf, off + MESSAGE_HEADER_LENGTH, fragment_offset,
- fragment_length);
- }
- else if (m_previousInboundFlight != null)
- {
- /*
- * NOTE: If we receive the previous flight of incoming messages in full again,
- * retransmit our last flight
- */
- DtlsReassembler reassembler = (DtlsReassembler)m_previousInboundFlight[message_seq];
- if (reassembler != null)
- {
- reassembler.ContributeFragment(msg_type, length, buf, off + MESSAGE_HEADER_LENGTH,
- fragment_offset, fragment_length);
- checkPreviousFlight = true;
- }
- }
- off += message_length;
- len -= message_length;
- }
- if (checkPreviousFlight && CheckAll(m_previousInboundFlight))
- {
- ResendOutboundFlight();
- ResetAll(m_previousInboundFlight);
- }
- }
- /// <exception cref="IOException"/>
- private void ResendOutboundFlight()
- {
- m_recordLayer.ResetWriteEpoch();
- foreach (Message message in m_outboundFlight)
- {
- WriteMessage(message);
- }
- m_resendMillis = BackOff(m_resendMillis);
- m_resendTimeout = new Timeout(m_resendMillis);
- }
- /// <exception cref="IOException"/>
- private Message UpdateHandshakeMessagesDigest(Message message)
- {
- short msg_type = message.Type;
- switch (msg_type)
- {
- case HandshakeType.hello_request:
- case HandshakeType.hello_verify_request:
- case HandshakeType.key_update:
- break;
- // TODO[dtls13] Not included in the transcript for (D)TLS 1.3+
- case HandshakeType.new_session_ticket:
- default:
- {
- byte[] body = message.Body;
- byte[] buf = new byte[MESSAGE_HEADER_LENGTH];
- TlsUtilities.WriteUint8(msg_type, buf, 0);
- TlsUtilities.WriteUint24(body.Length, buf, 1);
- TlsUtilities.WriteUint16(message.Seq, buf, 4);
- TlsUtilities.WriteUint24(0, buf, 6);
- TlsUtilities.WriteUint24(body.Length, buf, 9);
- m_handshakeHash.Update(buf, 0, buf.Length);
- m_handshakeHash.Update(body, 0, body.Length);
- break;
- }
- }
- return message;
- }
- /// <exception cref="IOException"/>
- private void WriteMessage(Message message)
- {
- int sendLimit = m_recordLayer.GetSendLimit();
- int fragmentLimit = sendLimit - MESSAGE_HEADER_LENGTH;
- // TODO Support a higher minimum fragment size?
- if (fragmentLimit < 1)
- {
- // TODO Should we be throwing an exception here?
- throw new TlsFatalAlert(AlertDescription.internal_error);
- }
- int length = message.Body.Length;
- // NOTE: Must still send a fragment if body is empty
- int fragment_offset = 0;
- do
- {
- int fragment_length = System.Math.Min(length - fragment_offset, fragmentLimit);
- WriteHandshakeFragment(message, fragment_offset, fragment_length);
- fragment_offset += fragment_length;
- }
- while (fragment_offset < length);
- }
- /// <exception cref="IOException"/>
- private void WriteHandshakeFragment(Message message, int fragment_offset, int fragment_length)
- {
- RecordLayerBuffer fragment = new RecordLayerBuffer(MESSAGE_HEADER_LENGTH + fragment_length);
- TlsUtilities.WriteUint8(message.Type, fragment);
- TlsUtilities.WriteUint24(message.Body.Length, fragment);
- TlsUtilities.WriteUint16(message.Seq, fragment);
- TlsUtilities.WriteUint24(fragment_offset, fragment);
- TlsUtilities.WriteUint24(fragment_length, fragment);
- fragment.Write(message.Body, fragment_offset, fragment_length);
- fragment.SendToRecordLayer(m_recordLayer);
- }
- private static bool CheckAll(IDictionary inboundFlight)
- {
- foreach (DtlsReassembler r in inboundFlight.Values)
- {
- if (r.GetBodyIfComplete() == null)
- return false;
- }
- return true;
- }
- private static void ResetAll(IDictionary inboundFlight)
- {
- foreach (DtlsReassembler r in inboundFlight.Values)
- {
- r.Reset();
- }
- }
- internal class Message
- {
- private readonly int m_message_seq;
- private readonly short m_msg_type;
- private readonly byte[] m_body;
- internal Message(int message_seq, short msg_type, byte[] body)
- {
- this.m_message_seq = message_seq;
- this.m_msg_type = msg_type;
- this.m_body = body;
- }
- public int Seq
- {
- get { return m_message_seq; }
- }
- public short Type
- {
- get { return m_msg_type; }
- }
- public byte[] Body
- {
- get { return m_body; }
- }
- }
- internal class RecordLayerBuffer
- : MemoryStream
- {
- internal RecordLayerBuffer(int size)
- : base(size)
- {
- }
- internal void SendToRecordLayer(DtlsRecordLayer recordLayer)
- {
- #if PORTABLE || NETFX_CORE
- byte[] buf = ToArray();
- int bufLen = buf.Length;
- #else
- byte[] buf = GetBuffer();
- int bufLen = (int)Length;
- #endif
- recordLayer.Send(buf, 0, bufLen);
- BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.Dispose(this);
- }
- }
- internal class Retransmit
- : DtlsHandshakeRetransmit
- {
- private readonly DtlsReliableHandshake m_outer;
- internal Retransmit(DtlsReliableHandshake outer)
- {
- this.m_outer = outer;
- }
- public void ReceivedHandshakeRecord(int epoch, byte[] buf, int off, int len)
- {
- m_outer.ProcessRecord(0, epoch, buf, off, len);
- }
- }
- }
- }
- #pragma warning restore
- #endif
|