1 /**
2 * TLS Channel
3 * 
4 * Copyright:
5 * (C) 2011,2012,2014 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.tls.channel;
12 
13 import botan.constants;
14 static if (BOTAN_HAS_TLS):
15 package:
16 
17 public import botan.cert.x509.x509cert;
18 public import botan.tls.policy;
19 public import botan.tls.session;
20 public import botan.tls.alert;
21 public import botan.tls.session_manager;
22 public import botan.tls.version_;
23 public import botan.tls.exceptn;
24 public import botan.rng.rng;
25 import core.thread : Thread;
26 import botan.tls.handshake_state;
27 import botan.tls.messages;
28 import botan.tls.heartbeats;
29 import botan.tls.record;
30 import botan.tls.seq_numbers;
31 import botan.utils.rounding;
32 import memutils.dictionarylist;
33 import botan.utils.loadstor;
34 import botan.utils.types;
35 import botan.utils.get_byte;
36 import memutils.hashmap;
37 import std..string : toStringz;
38 
39 alias DataWriter = void delegate(in ubyte[]);
40 alias OnClearData = void delegate(in ubyte[]);
41 alias OnAlert = void delegate(in TLSAlert, in ubyte[]);
42 alias OnHandshakeComplete = bool delegate(in TLSSession);
43 
44 /**
45 * Generic interface for TLS endpoint
46 */
47 class TLSChannel
48 {
49 public:
50 
51 	this(DataWriter output_fn,
52 		OnClearData data_cb,
53 		OnAlert alert_cb,
54 		OnHandshakeComplete handshake_cb,
55 		TLSSessionManager session_manager,
56 		RandomNumberGenerator rng,
57 		bool is_datagram,
58 		size_t reserved_io_buffer_size)
59 	{
60 		m_owner = Thread.getThis();
61 		m_handshake_cb = handshake_cb;
62 		m_data_cb = data_cb;
63 		m_alert_cb = alert_cb;
64 		m_output_fn = output_fn;
65 		m_rng = rng;
66 		m_session_manager = session_manager;
67 		/* epoch 0 is plaintext, thus null cipher state */
68 		//m_write_cipher_states[cast(ushort)0] = ConnectionCipherState.init;
69 		//m_read_cipher_states[cast(ushort)0] = ConnectionCipherState.init;
70 		
71 		m_writebuf.reserve(reserved_io_buffer_size);
72 		m_readbuf.reserve(reserved_io_buffer_size);
73 	}
74 
75     /**
76     * Inject TLS traffic received from counterparty
77     * Returns: a hint as the how many more bytes we need to process the
78     *            current record (this may be 0 if on a record boundary)
79     */
80     size_t receivedData(const(ubyte)* input, size_t input_size)
81     {
82         
83         const size_t max_fragment_size = maximumFragmentSize();
84         
85         try
86         {
87             while (!isClosed() && input_size)
88             {
89                 SecureVector!ubyte record;
90                 ulong record_sequence = 0;
91                 RecordType record_type = NO_RECORD;
92                 TLSProtocolVersion record_version;
93 
94                 size_t consumed = 0;
95                 const size_t needed = .readRecord(m_readbuf,
96                                                   input,
97                                                   input_size,
98                                                   m_is_datagram,
99                                                   consumed,
100                                                   record,
101                                                   record_sequence,
102                                                   record_version,
103                                                   record_type,
104                                                   *m_sequence_numbers,
105                                                   &readCipherStateEpoch);
106                 assert(consumed > 0, "Got to eat something");
107                 assert(consumed <= input_size, "Record reader consumed sane amount");
108                 
109                 input += consumed;
110                 input_size -= consumed;
111                 
112                 assert(input_size == 0 || needed == 0, "Got a full record or consumed all input");
113                 
114                 if (input_size == 0 && needed != 0)
115                     return needed; // need more data to complete record
116                 
117                 if (record.length > max_fragment_size)
118                     throw new TLSException(TLSAlert.RECORD_OVERFLOW, "Plaintext record is too large");
119                 if (record_type == HANDSHAKE || record_type == CHANGE_CIPHER_SPEC)
120                 {
121                     if (!m_pending_state)
122                     {
123                         if (record_version.isDatagramProtocol())
124                         {
125                             if (m_sequence_numbers)
126                             {
127 
128                                 /*
129                                 * Might be a peer retransmit under epoch - 1 in which
130                                 * case we must retransmit last flight
131                                 */
132 
133                                 (*m_sequence_numbers).readAccept(record_sequence);
134                                           
135                                 const ushort epoch = record_sequence >> 48;
136                                 
137                                 if (epoch == sequenceNumbers().currentReadEpoch())
138                                 {
139                                     createHandshakeState(record_version);
140                                 }
141                                 else if (epoch == sequenceNumbers().currentReadEpoch() - 1)
142                                 {
143                                     assert(m_active_state, "Have active state here");
144                                     auto rec = unlock(record);
145                                     m_active_state.handshakeIo().addRecord(rec, record_type, record_sequence);
146                                 }
147                             }
148                             else if (record_sequence == 0)
149                             {
150                                 createHandshakeState(record_version);
151                             }
152                         }
153                         else
154                         {
155                             createHandshakeState(record_version);
156                         }
157                         
158                     }
159 
160                     if (m_pending_state)
161                     {
162                         auto rec = unlock(record);
163                         m_pending_state.handshakeIo().addRecord(rec, record_type, record_sequence);
164                         
165                         while (true) {
166                             if (auto pending = *m_pending_state) {
167                                 auto msg = pending.getNextHandshakeMsg();
168                                 
169                                 if (msg.type == HANDSHAKE_NONE) // no full handshake yet
170                                     break;
171 
172                                 processHandshakeMsg(activeState(), pending, msg.type, msg.data);
173                             } else break;
174                         }
175                     }
176                 }
177                 else if (record_type == HEARTBEAT && peerSupportsHeartbeats())
178                 {
179                     if (!activeState())
180                         throw new TLSUnexpectedMessage("Heartbeat sent before handshake done");
181                     
182                     HeartbeatMessage heartbeat = HeartbeatMessage(unlock(record));
183                     
184                     const Vector!ubyte* payload = &heartbeat.payload();
185                     
186                     if (heartbeat.isRequest())
187                     {
188                         if (!pendingState())
189                         {
190                             HeartbeatMessage response = HeartbeatMessage(HeartbeatMessage.RESPONSE, payload.ptr, payload.length);
191                             auto rec = response.contents();
192                             sendRecord(HEARTBEAT, rec);
193                         }
194                     }
195                     else
196                     {
197                         m_alert_cb(TLSAlert(TLSAlert.HEARTBEAT_PAYLOAD), cast(ubyte[])(*payload)[]);
198                     }
199                 }
200                 else if (record_type == APPLICATION_DATA)
201                 {
202                     if (!activeState())
203                         throw new TLSUnexpectedMessage("Application data before handshake done");
204                             
205                     /*
206                     * OpenSSL among others sends empty records in versions
207                     * before TLS v1.1 in order to randomize the IV of the
208                     * following record. Avoid spurious callbacks.
209                     */
210                     if (record.length > 0)
211                         m_data_cb(cast(ubyte[])record[]);
212                 }
213                 else if (record_type == ALERT)
214                 {
215                     TLSAlert alert_msg = TLSAlert(record);
216                     
217                     if (alert_msg.type() == TLSAlert.NO_RENEGOTIATION)
218                     m_pending_state.free();
219                     
220                     m_alert_cb(alert_msg, null);
221                     
222                     if (alert_msg.isFatal())
223                     {
224                         if (auto active = activeState()) {
225                             auto entry = &active.serverHello().sessionId();
226                             m_session_manager.removeEntry(*entry);
227                         }
228                     }
229                             
230                     if (alert_msg.type() == TLSAlert.CLOSE_NOTIFY)
231                         sendWarningAlert(TLSAlert.CLOSE_NOTIFY); // reply in kind
232                                 
233                     if (alert_msg.type() == TLSAlert.CLOSE_NOTIFY || alert_msg.isFatal())
234                     {
235                         resetState();
236                         return 0;
237                     }
238                 }
239                 else if (record_type != NO_RECORD)
240                     throw new TLSUnexpectedMessage("Unexpected record type " ~ to!string(record_type) ~ " from counterparty");
241             }
242                         
243             return 0; // on a record boundary
244         }
245         catch(TLSException e)
246         {
247             sendFatalAlert(e.type());
248             throw e;
249         }
250         catch(IntegrityFailure e)
251         {
252             sendFatalAlert(TLSAlert.BAD_RECORD_MAC);
253             throw e;
254         }
255         catch(DecodingError e)
256         {
257             sendFatalAlert(TLSAlert.DECODE_ERROR);
258             throw e;
259         }
260         catch(Exception e)
261         {
262             sendFatalAlert(TLSAlert.INTERNAL_ERROR);
263             throw e;
264         }
265     }
266 
267     /**
268     * Inject TLS traffic received from counterparty
269     * Returns: a hint as the how many more bytes we need to process the
270     *            current record (this may be 0 if on a record boundary)
271     */
272     size_t receivedData(const ref Vector!ubyte buf)
273     {
274         return this.receivedData(buf.ptr, buf.length);
275     }
276 
277     /**
278     * Inject plaintext intended for counterparty
279     * Throws an exception if isActive() is false
280     */
281     void send(const(ubyte)* buf, size_t buf_size)
282     {
283         if (!isActive())
284             throw new TLSClosedException("Data cannot be sent on inactive TLS connection");
285         
286         sendRecordArray(sequenceNumbers().currentWriteEpoch(), APPLICATION_DATA, buf, buf_size);
287     }
288 
289     /**
290     * Inject plaintext intended for counterparty
291     * Throws an exception if isActive() is false
292     */
293     void send(in string str)
294     {
295         this.send(cast(const(ubyte)*)(str.toStringz), str.length);
296     }
297 
298     /**
299     * Inject plaintext intended for counterparty
300     * Throws an exception if isActive() is false
301     */
302     void send(Alloc)(const ref Vector!( char, Alloc ) val)
303     {
304         send(val.ptr, val.length);
305     }
306 
307     /**
308     * Send a TLS alert message. If the alert is fatal, the internal
309     * state (keys, etc) will be reset.
310     *
311     * Params:
312     *  alert = the TLSAlert to send
313     */
314     void sendAlert(in TLSAlert alert)
315     {
316         if (alert.isValid() && !isClosed())
317         {
318             try
319             {
320                 auto rec = alert.serialize();
321                 sendRecord(ALERT, rec);
322             }
323             catch (Exception) { /* swallow it */ }
324         }
325         
326         if (alert.type() == TLSAlert.NO_RENEGOTIATION)
327             m_pending_state.free();
328         
329         if (alert.isFatal()) {
330             if (auto active = activeState()) {
331                 auto entry = &active.serverHello().sessionId();
332                 m_session_manager.removeEntry(*entry);
333             }
334         }
335         if (alert.type() == TLSAlert.CLOSE_NOTIFY || alert.isFatal())
336             resetState();
337     }
338 
339     /**
340     * Send a warning alert
341     */
342     void sendWarningAlert(TLSAlertType type) { sendAlert(TLSAlert(type, false)); }
343 
344     /**
345     * Send a fatal alert
346     */
347     void sendFatalAlert(TLSAlertType type) { sendAlert(TLSAlert(type, true)); }
348 
349     /**
350     * Send a close notification alert
351     */
352     void close() { sendWarningAlert(TLSAlert.CLOSE_NOTIFY); }
353 
354     /**
355     * Returns: true iff the connection is active for sending application data
356     */
357     bool isActive() const
358     {
359         return (activeState() !is null);
360     }
361 
362     /**
363     * Returns: true iff the connection has been definitely closed
364     */
365     bool isClosed() const
366     {
367         if (activeState() || pendingState())
368             return false;
369         
370         /*
371         * If no active or pending state, then either we had a connection
372         * and it has been closed, or we are a server which has never
373         * received a connection. This case is detectable by also lacking
374         * m_sequence_numbers
375         */
376         return (*m_sequence_numbers !is null);
377     }
378 
379     /**
380     * Attempt to renegotiate the session
381     * Params:
382     *  force_full_renegotiation = if true, require a full renegotiation,
383     *                                            otherwise allow session resumption
384     */
385     void renegotiate(bool force_full_renegotiation = false)
386     {
387         if (pendingState()) // currently in handshake?
388             return;
389         
390         if (const HandshakeState active = activeState())
391             initiateHandshake(createHandshakeState(active.Version()),
392                                force_full_renegotiation);
393         else
394             throw new Exception("Cannot renegotiate on inactive connection");
395     }
396 
397     /**
398     * Returns: true iff the peer supports heartbeat messages
399     */
400     bool peerSupportsHeartbeats() const
401     {
402         if (const HandshakeState active = activeState())
403             return active.serverHello().supportsHeartbeats();
404         return false;
405     }
406 
407     /**
408     * Returns: true iff we are allowed to send heartbeat messages
409     */
410     bool heartbeatSendingAllowed() const
411     {
412         if (const HandshakeState active = activeState())
413             return active.serverHello().peerCanSendHeartbeats();
414         return false;
415     }
416 
417     /**
418     * Attempt to send a heartbeat message (if negotiated with counterparty)
419     * Params:
420     *  payload = will be echoed back
421     *  payload_size = size of payload in bytes
422     */
423     void heartbeat(const(ubyte)* payload, size_t payload_size)
424     {
425         if (heartbeatSendingAllowed())
426         {
427             HeartbeatMessage heartbeat = HeartbeatMessage(HeartbeatMessage.REQUEST, payload, payload_size);
428             auto rec = heartbeat.contents();
429             sendRecord(HEARTBEAT, rec);
430         }
431     }
432 
433     /**
434     * Attempt to send a heartbeat message (if negotiated with counterparty)
435     */
436     void heartbeat() { heartbeat(null, 0); }
437 
438     /**
439     * Returns: certificate chain of the peer (may be empty)
440     */
441     Vector!X509Certificate peerCertChain() const
442     {
443         if (const HandshakeState active = activeState())
444             return getPeerCertChain(active).dup;
445         return Vector!X509Certificate();
446     }
447 
448     /**
449     * Key material export (RFC 5705)
450     * Params:
451     *  label = a disambiguating label string
452     *  context = a per-association context value
453     *  length = the length of the desired key in bytes
454     * Returns: key of length bytes
455     */
456     const(SymmetricKey) keyMaterialExport(in string label,
457                                    in string context,
458                                    size_t length) const
459     {
460         if (auto active = activeState())
461         {
462             Unique!KDF prf = active.protocolSpecificPrf();
463             
464             const(SecureVector!ubyte)* master_secret = &active.sessionKeys().masterSecret();
465             
466             Vector!ubyte salt;
467             salt ~= label;
468             salt ~= active.clientHello().random()[];
469             salt ~= active.serverHello().random()[];
470 
471             if (context != "")
472             {
473                 size_t context_size = context.length;
474                 if (context_size > 0xFFFF)
475                     throw new Exception("key_material_export context is too long");
476                 salt.pushBack(get_byte(0, cast(ushort) context_size));
477                 salt.pushBack(get_byte(1, cast(ushort) context_size));
478                 salt ~= context;
479             }
480             
481             return SymmetricKey(prf.deriveKey(length, *master_secret, salt));
482         }
483         else
484             throw new Exception("key_material_export connection not active");
485     }
486 
487 	/// Returns the ALPN chosen in the ServerHello with the ALPN extention
488 	const(string) applicationProtocol() const { return m_application_protocol; }
489 
490 	/// Returns the current session ID
491 	const(ubyte[]) sessionId() const {
492 		if (auto active = activeState()) {
493 			return active.serverHello().sessionId()[];
494 		}
495 		return null;
496 	}
497 
498     ~this()
499     {
500 
501 		version(TLSGC) if (m_owner != Thread.getThis()) return;
502 
503         resetState();
504     }
505 
506 protected:
507 
508     abstract void processHandshakeMsg(in HandshakeState active_state,
509                                       HandshakeState pending_state,
510                                       HandshakeType type,
511                                       const ref Vector!ubyte contents);
512 
513     abstract void initiateHandshake(HandshakeState state,
514                                     bool force_full_renegotiation);
515 
516     abstract Vector!X509Certificate getPeerCertChain(in HandshakeState state) const;
517 
518     abstract HandshakeState newHandshakeState(HandshakeIO io);
519 
520     HandshakeState createHandshakeState(TLSProtocolVersion _version)
521     {
522         if (pendingState())
523             throw new InternalError("createHandshakeState called during handshake");
524         
525         if (const HandshakeState active = activeState())
526         {
527             TLSProtocolVersion active_version = active.Version();
528             
529             if (active_version.isDatagramProtocol() != _version.isDatagramProtocol())
530                 throw new Exception("Active state using version " ~ active_version.toString() ~
531                                     " cannot change to " ~ _version.toString() ~ " in pending");
532         }
533         
534         if (!m_sequence_numbers)
535         {
536             if (_version.isDatagramProtocol())
537                 m_sequence_numbers = new DatagramSequenceNumbers;
538             else
539                 m_sequence_numbers = new StreamSequenceNumbers;
540         }
541         
542         Unique!HandshakeIO io;
543         if (_version.isDatagramProtocol()) {
544 			// default MTU is IPv6 min MTU minus UDP/IP headers (TODO: make configurable)
545 			const ushort mtu = 1280 - 40 - 8;
546             io = new DatagramHandshakeIO(*m_sequence_numbers, &sendRecordUnderEpoch, mtu);
547         }
548         else
549             io = new StreamHandshakeIO(&sendRecord);
550 
551         m_pending_state = newHandshakeState(io.release());
552         
553         if (auto active = activeState())
554             m_pending_state.setVersion(active.Version());
555         
556         return *m_pending_state;
557     }
558 
559     /**
560     * Perform a handshake timeout check. This does nothing unless
561     * this is a DTLS channel with a pending handshake state, in
562     * which case we check for timeout and potentially retransmit
563     * handshake packets.
564     */
565     bool timeoutCheck() {
566         if (m_pending_state)
567             return m_pending_state.handshakeIo().timeoutCheck();
568         //FIXME: scan cipher suites and remove epochs older than 2*MSL
569         return false;
570     }
571 
572     void activateSession()
573     {
574         std.algorithm.swap(m_active_state, m_pending_state);
575         m_pending_state.free();
576         
577         if (!m_active_state.Version().isDatagramProtocol())
578         {
579             // TLS is easy just remove all but the current state
580             auto current_epoch = sequenceNumbers().currentWriteEpoch();
581 
582             foreach (const ref ushort k, const ref ConnectionCipherState v; m_write_cipher_states) {
583                 if (k != current_epoch) {
584                     v.destroy();
585                     m_write_cipher_states.remove(k);
586                 }
587             }
588             foreach (const ref ushort k, const ref ConnectionCipherState v; m_read_cipher_states) {
589                 if (k != current_epoch) {
590                     v.destroy();
591                     m_write_cipher_states.remove(k);                    
592                 }
593             }
594         }
595     }
596 
597     void changeCipherSpecReader(ConnectionSide side)
598     {
599         auto pending = pendingState();
600         
601         assert(pending && pending.serverHello(), "Have received server hello");
602         
603         if (pending.serverHello().compressionMethod() != NO_COMPRESSION)
604             throw new InternalError("Negotiated unknown compression algorithm");
605         
606         (*m_sequence_numbers).newReadCipherState();
607         
608         const ushort epoch = sequenceNumbers().currentReadEpoch();
609 
610         assert(m_read_cipher_states.get(epoch, ConnectionCipherState.init) is ConnectionCipherState.init, 
611                "No read cipher state currently set for next epoch");
612         
613         // flip side as we are reading
614         ConnectionCipherState read_state = new ConnectionCipherState(pending.Version(),
615                                                                  (side == CLIENT) ? SERVER : CLIENT,
616                                                                  false,
617                                                                  pending.ciphersuite(),
618                                                                  pending.sessionKeys());
619         
620         m_read_cipher_states[epoch] = read_state;
621     }
622 
623     void changeCipherSpecWriter(ConnectionSide side)
624     {
625         auto pending = pendingState();
626         
627         assert(pending && pending.serverHello(), "Have received server hello");
628         
629         if (pending.serverHello().compressionMethod() != NO_COMPRESSION)
630             throw new InternalError("Negotiated unknown compression algorithm");
631         
632         (*m_sequence_numbers).newWriteCipherState();
633         
634         const ushort epoch = sequenceNumbers().currentWriteEpoch();
635         
636         assert(m_write_cipher_states.get(epoch, ConnectionCipherState.init) is ConnectionCipherState.init, "No write cipher state currently set for next epoch");
637         
638         ConnectionCipherState write_state = new ConnectionCipherState(pending.Version(),
639                                                                   side,
640                                                                   true,
641                                                                   pending.ciphersuite(),
642                                                                   pending.sessionKeys());
643         
644         m_write_cipher_states[epoch] = write_state;
645     }
646 
647     /* secure renegotiation handling */
648     void secureRenegotiationCheck(const ClientHello client_hello)
649     {
650         const bool secure_renegotiation = client_hello.secureRenegotiation();
651         
652         if (auto active = activeState())
653         {
654             const bool active_sr = active.clientHello().secureRenegotiation();
655             
656             if (active_sr != secure_renegotiation)
657                 throw new TLSException(TLSAlert.HANDSHAKE_FAILURE, "TLSClient changed its mind about secure renegotiation");
658         }
659         
660         if (secure_renegotiation)
661         {
662             Vector!ubyte data = client_hello.renegotiationInfo();
663             
664             if (data != secureRenegotiationDataForClientHello())
665                 throw new TLSException(TLSAlert.HANDSHAKE_FAILURE, "TLSClient sent bad values for secure renegotiation");
666         }
667     }
668 
669     void secureRenegotiationCheck(const ServerHello server_hello)
670     {
671         const bool secure_renegotiation = server_hello.secureRenegotiation();
672         
673         if (auto active = activeState())
674         {
675             const bool active_sr = active.clientHello().secureRenegotiation();
676             
677             if (active_sr != secure_renegotiation)
678                 throw new TLSException(TLSAlert.HANDSHAKE_FAILURE, "TLSServer changed its mind about secure renegotiation");
679         }
680         
681         if (secure_renegotiation)
682         {
683             const Vector!ubyte data = server_hello.renegotiationInfo();
684             
685             if (data != secureRenegotiationDataForServerHello())
686                 throw new TLSException(TLSAlert.HANDSHAKE_FAILURE, "TLSServer sent bad values for secure renegotiation");
687         }
688     }
689 
690     Vector!ubyte secureRenegotiationDataForClientHello() const
691     {
692         if (auto active = activeState())
693             return active.clientFinished().verifyData().dup;
694         return Vector!ubyte();
695     }
696 
697     Vector!ubyte secureRenegotiationDataForServerHello() const
698     {
699         if (auto active = activeState())
700         {
701             Vector!ubyte buf = active.clientFinished().verifyData().dup;
702             buf ~= active.serverFinished().verifyData()[];
703             return buf.move();
704         }
705         
706         return Vector!ubyte();
707     }
708 
709     /**
710     * Returns: true iff the counterparty supports the secure
711     * renegotiation extensions.
712     */
713     bool secureRenegotiationSupported() const
714     {
715         if (auto active = activeState())
716             return active.serverHello().secureRenegotiation();
717         
718         if (auto pending = pendingState())
719             if (auto hello = pending.serverHello())
720                 return hello.secureRenegotiation();
721         
722         return false;
723     }
724 
725     RandomNumberGenerator rng() { return m_rng; }
726 
727     TLSSessionManager sessionManager() { return m_session_manager; }
728 
729     bool saveSession(in TLSSession session) const { return m_handshake_cb(session); }
730 
731 private:
732 
733     size_t maximumFragmentSize() const
734     {
735         // should we be caching this value?
736         
737         if (auto pending = pendingState())
738             if (auto server_hello = pending.serverHello())
739                 if (size_t frag = server_hello.fragmentSize())
740                     return frag;
741         
742         if (auto active = activeState())
743             if (size_t frag = active.serverHello().fragmentSize())
744                 return frag;
745         
746         return MAX_PLAINTEXT_SIZE;
747     }
748 
749     void sendRecord(ubyte record_type, const ref Vector!ubyte record)
750     {
751         sendRecordArray(sequenceNumbers().currentWriteEpoch(), record_type, record.ptr, record.length);
752     }
753 
754     void sendRecordUnderEpoch(ushort epoch, ubyte record_type, const ref Vector!ubyte record)
755     {
756         sendRecordArray(epoch, record_type, record.ptr, record.length);
757     }
758 
759     void sendRecordArray(ushort epoch, ubyte type, const(ubyte)* input, size_t length)
760     {
761         if (length == 0)
762             return;
763         /*
764         * If using CBC mode without an explicit IV (SSL v3 or TLS v1.0),
765         * send a single ubyte of plaintext to randomize the (implicit) IV of
766         * the following main block. If using a stream cipher, or TLS v1.1
767         * or higher, this isn't necessary.
768         *
769         * An empty record also works but apparently some implementations do
770         * not like this (https://bugzilla.mozilla.org/show_bug.cgi?id=665814)
771         *
772         * See http://www.openssl.org/~bodo/tls-cbc.txt for background.
773         */
774         
775         auto cipher_state = cast(ConnectionCipherState)writeCipherStateEpoch(epoch);
776         
777         if (type == APPLICATION_DATA && cipher_state.cbcWithoutExplicitIv())
778         {
779             writeRecord(cipher_state, epoch, type, input, 1);
780             input += 1;
781             length -= 1;
782         }
783         
784         const size_t max_fragment_size = maximumFragmentSize();
785         
786         while (length)
787         {
788             const size_t sending = std.algorithm.min(length, max_fragment_size);
789             writeRecord(cipher_state, epoch, type, input, sending);
790             
791             input += sending;
792             length -= sending;
793         }
794     }
795 
796     void writeRecord(ConnectionCipherState cipher_state, ushort epoch, ubyte record_type, const(ubyte)* input, size_t length)
797     {
798         assert(m_pending_state || m_active_state, "Some connection state exists");
799         
800         TLSProtocolVersion record_version = (m_pending_state) ? (m_pending_state.Version()) : (m_active_state.Version());
801         
802         .writeRecord(m_writebuf,
803                      record_type,
804                      input,
805                      length,
806                      record_version,
807                      (*m_sequence_numbers).nextWriteSequence(epoch),
808                      cipher_state,
809                      m_rng);
810         
811         m_output_fn(cast(ubyte[]) m_writebuf[]);
812     }
813 
814     const(ConnectionSequenceNumbers) sequenceNumbers() const
815     {
816         assert(m_sequence_numbers, "Have a sequence numbers object");
817         return *m_sequence_numbers;
818     }
819 
820     const(ConnectionCipherState) readCipherStateEpoch(ushort epoch) const
821     {
822         auto state = m_read_cipher_states.get(epoch, ConnectionCipherState.init);
823         
824         assert(state !is ConnectionCipherState.init || epoch == 0, "Have a cipher state for the specified epoch");
825         
826         return state;
827     }
828 
829     const(ConnectionCipherState) writeCipherStateEpoch(ushort epoch) const
830     {
831         auto state = m_write_cipher_states.get(epoch, ConnectionCipherState.init);
832         
833         assert(state !is ConnectionCipherState.init || epoch == 0, "Have a cipher state for the specified epoch");
834         
835         return state;
836     }
837 
838     protected void resetState()
839     {
840         m_active_state.free();
841         m_pending_state.free();
842         m_readbuf.destroy();
843 		m_writebuf.destroy();
844         foreach (const ref k, const ref v; m_write_cipher_states)
845         {
846             v.destroy();
847         }
848         m_write_cipher_states.clear();
849         foreach (const ref k, const ref v; m_read_cipher_states)
850         {
851             v.destroy();
852         }
853         m_read_cipher_states.clear();
854     }
855 
856     const(HandshakeState) activeState() const { return *m_active_state; }
857 
858     const(HandshakeState) pendingState() const { return *m_pending_state; }
859 
860 	Thread m_owner;
861 	package string m_application_protocol;
862     bool m_is_datagram;
863 
864     /* callbacks */
865     OnHandshakeComplete m_handshake_cb;
866     OnClearData m_data_cb;
867     OnAlert m_alert_cb;
868     DataWriter m_output_fn;
869 
870     /* external state */
871     RandomNumberGenerator m_rng;
872     package TLSSessionManager m_session_manager; // fixme: package protection for switchContext, use protected: method instead
873 
874     /* sequence number state */
875     Unique!ConnectionSequenceNumbers m_sequence_numbers;
876 
877     /* pending and active connection states */
878     Unique!HandshakeState m_active_state;
879     Unique!HandshakeState m_pending_state;
880 
881     /* cipher states for each epoch */
882     HashMap!(ushort, ConnectionCipherState) m_write_cipher_states;
883     HashMap!(ushort, ConnectionCipherState) m_read_cipher_states;
884 
885     /* I/O buffers */
886     SecureVector!ubyte m_writebuf;
887     SecureVector!ubyte m_readbuf;
888 }