[tor-commits] [stegotorus/master] Break up chop.cc in preparation for adding more control blocks.
zwol at torproject.org
zwol at torproject.org
Fri Jul 20 23:17:08 UTC 2012
commit 17a1e39d73c4767bdc4c6a89a30b5378a4f0a0b9
Author: Zack Weinberg <zackw at cmu.edu>
Date: Sun Jul 8 18:39:39 2012 +0200
Break up chop.cc in preparation for adding more control blocks.
All of the header formatting code and the reassembly queue move to their
own file. Some of the low-level transmission and reception code may follow.
This will make it easier to add new types of control blocks (for retransmit,
in-band connection close, the long-awaited rekeying and handshake logic, etc)
and will also make it possible to unit test the separated code.
---
Makefile.am | 2 +
src/protocol/chop.cc | 292 +---------------------------------------------
src/protocol/chop_blk.cc | 104 ++++++++++++++++
src/protocol/chop_blk.h | 247 +++++++++++++++++++++++++++++++++++++++
4 files changed, 358 insertions(+), 287 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 174e09c..b449619 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -14,6 +14,7 @@ bin_PROGRAMS = stegotorus
PROTOCOLS = \
src/protocol/chop.cc \
+ src/protocol/chop_blk.cc \
src/protocol/null.cc
STEGANOGRAPHERS = \
@@ -78,6 +79,7 @@ noinst_HEADERS = \
src/socks.h \
src/steg.h \
src/util.h \
+ src/protocol/chop_blk.h \
src/test/tinytest.h \
src/test/tinytest_macros.h \
src/test/unittest.h
diff --git a/src/protocol/chop.cc b/src/protocol/chop.cc
index 9109295..9e3a605 100644
--- a/src/protocol/chop.cc
+++ b/src/protocol/chop.cc
@@ -1,10 +1,10 @@
-/* Copyright 2011, SRI International
+/* Copyright 2011, 2012 SRI International
* See LICENSE for other credits and copying information
*/
#include "util.h"
+#include "chop_blk.h"
#include "connections.h"
-#include "crypt.h"
#include "protocol.h"
#include "rng.h"
#include "steg.h"
@@ -14,7 +14,6 @@
#include <vector>
#include <event2/event.h>
-#include <event2/buffer.h>
/* The chopper is the core StegoTorus protocol implementation.
For its design, see doc/chopper.txt. Note that it is still
@@ -25,286 +24,7 @@ using std::tr1::unordered_set;
using std::vector;
using std::make_pair;
-namespace
-{
-
-/* Packets on the wire have a 16-byte header, consisting of a 32-bit
- sequence number, two 16-bit length fields ("D" and "P"), an 8-bit
- opcode ("F"), and a 56-bit check field. All numbers in this header
- are serialized in network byte order.
-
- | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
- |Sequence Number| D | P | F | Check |
-
- The header is encrypted with AES in ECB mode: this is safe because
- the header is exactly one AES block long, the sequence number is
- never repeated, the header-encryption key is not used for anything
- else, and the high 24 bits of the sequence number, plus the check
- field, constitute an 80-bit MAC. The receiver maintains a
- 256-element sliding window of acceptable sequence numbers, which
- begins one after the highest sequence number so far _processed_
- (not received). If the sequence number is outside this window, or
- the check field is not all-bits-zero, the packet is discarded. An
- attacker's odds of being able to manipulate the D, P, or F fields
- or the low bits of the sequence number are therefore less than one
- in 2^80. Unlike TCP, our sequence numbers always start at zero on
- a new (or freshly rekeyed) circuit, and increment by one per
- _block_, not per byte of data. Furthermore, they do not wrap: a
- rekeying cycle (which resets the sequence number) is required to
- occur before the highest-received sequence number reaches 2^32.
-
- Following the header are two variable-length payload sections,
- "data" and "padding", whose length in bytes are given by the D and
- P fields, respectively. These sections are encrypted, using a
- different key, with AES in GCM mode. The *encrypted* packet header
- doubles as the GCM nonce. The semantics of the "data" section's
- contents, if any, are defined by the opcode F. The "padding"
- section SHOULD be filled with zeroes by the sender; regardless, its
- contents MUST be ignored by the receiver. Following these sections
- is a 16-byte GCM authentication tag, computed over the data and
- padding sections only, NOT the message header. */
-
-const size_t HEADER_LEN = 16;
-const size_t TRAILER_LEN = 16;
-const size_t SECTION_LEN = UINT16_MAX;
-const size_t MIN_BLOCK_SIZE = HEADER_LEN + TRAILER_LEN;
-const size_t MAX_BLOCK_SIZE = MIN_BLOCK_SIZE + SECTION_LEN*2;
-
-const size_t HANDSHAKE_LEN = sizeof(uint32_t);
-
-enum opcode_t
-{
- op_DAT = 0, // Pass data section along to upstream
- op_FIN = 1, // No further transmissions (pass data along if any)
- op_RST = 2, // Protocol error, close circuit now
- op_RESERVED0 = 3, // 3 -- 127 reserved for future definition
- op_STEG0 = 128, // 128 -- 255 reserved for steganography modules
- op_LAST = 255
-};
-
-static const char *
-opname(opcode_t o, char fallbackbuf[4])
-{
- switch (o) {
- case op_DAT: return "DAT";
- case op_FIN: return "FIN";
- case op_RST: return "RST";
- default: {
- unsigned int x = o;
- if (x < op_STEG0)
- xsnprintf(fallbackbuf, sizeof fallbackbuf, "R%02x", x);
- else
- xsnprintf(fallbackbuf, sizeof fallbackbuf, "S%02x", x - op_STEG0);
- return fallbackbuf;
- }
- }
-}
-
-class block_header
-{
- uint8_t clear[16];
- uint8_t ciphr[16];
-
-public:
- block_header(uint32_t s, uint16_t d, uint16_t p, opcode_t f,
- ecb_encryptor &ec)
- {
- if (f > op_LAST || (f >= op_RESERVED0 && f < op_STEG0)) {
- memset(clear, 0xFF, sizeof clear); // invalid!
- memset(ciphr, 0xFF, sizeof ciphr);
- return;
- }
-
- // sequence number
- clear[0] = (s >> 24) & 0xFF;
- clear[1] = (s >> 16) & 0xFF;
- clear[2] = (s >> 8) & 0xFF;
- clear[3] = (s ) & 0xFF;
-
- // D field
- clear[4] = (d >> 8) & 0xFF;
- clear[5] = (d ) & 0xFF;
-
- // P field
- clear[6] = (p >> 8) & 0xFF;
- clear[7] = (p ) & 0xFF;
-
- // F field
- clear[8] = uint8_t(f);
-
- // Check field
- memset(clear + 9, 0, 7);
-
- ec.encrypt(ciphr, clear);
- }
-
- block_header(evbuffer *buf, ecb_decryptor &dc)
- {
- if (evbuffer_copyout(buf, ciphr, sizeof ciphr) != sizeof ciphr) {
- memset(clear, 0xFF, sizeof clear);
- memset(ciphr, 0xFF, sizeof ciphr);
- return;
- }
- dc.decrypt(clear, ciphr);
- }
-
- uint32_t seqno() const
- {
- return ((uint32_t(clear[0]) << 24) |
- (uint32_t(clear[1]) << 16) |
- (uint32_t(clear[2]) << 8) |
- (uint32_t(clear[3]) ));
-
- }
-
- size_t dlen() const
- {
- return ((uint16_t(clear[4]) << 8) |
- (uint16_t(clear[5]) ));
- }
-
- size_t plen() const
- {
- return ((uint16_t(clear[6]) << 8) |
- (uint16_t(clear[7]) ));
- }
-
- size_t total_len() const
- {
- return HEADER_LEN + TRAILER_LEN + dlen() + plen();
- }
-
- opcode_t opcode() const
- {
- return opcode_t(clear[8]);
- }
-
- bool valid(uint64_t window) const
- {
- // This check must run in constant time.
- uint8_t ck = (clear[ 9] | clear[10] | clear[11] | clear[12] |
- clear[13] | clear[14] | clear[15]);
- uint32_t delta = seqno() - window;
- ck |= !!(delta & ~uint32_t(0xFF));
- return !ck;
- }
-
- const uint8_t *nonce() const
- {
- return ciphr;
- }
-
- const uint8_t *cleartext() const
- {
- return clear;
- }
-};
-
-/* Most of a block's header information is processed before it reaches
- the reassembly queue; the only things the queue needs to record are
- the sequence number (which is stored implictly), the opcode, and an
- evbuffer holding the data section. Zero-data blocks still get an
- evbuffer, for simplicity's sake: a reassembly queue element holds a
- received block if and only if its data pointer is non-null.
-
- The reassembly queue is a 256-element circular buffer of
- 'reassembly_elt' structs. This corresponds to the 256-element
- sliding window of sequence numbers which may legitimately be
- received at any time. */
-
-struct reassembly_elt
-{
- evbuffer *data;
- opcode_t op;
-};
-
-class reassembly_queue
-{
- reassembly_elt cbuf[256];
- uint32_t next_to_process;
-
- reassembly_queue(const reassembly_queue&) DELETE_METHOD;
- reassembly_queue& operator=(const reassembly_queue&) DELETE_METHOD;
-
-public:
- reassembly_queue()
- : next_to_process(0)
- {
- memset(cbuf, 0, sizeof cbuf);
- }
-
- ~reassembly_queue()
- {
- for (int i = 0; i < 256; i++)
- if (cbuf[i].data)
- evbuffer_free(cbuf[i].data);
- }
-
- // Remove the next block to be processed from the reassembly queue
- // and return it. If we are out of blocks or the next block to
- // process has not yet arrived, return an empty reassembly_elt.
- // Caller is responsible for freeing the evbuffer in the
- // reassembly_elt, if any.
- reassembly_elt
- remove_next()
- {
- reassembly_elt rv = { 0, op_DAT };
- uint8_t front = next_to_process & 0xFF;
- log_debug("next_to_process=%d data=%p op=%02x",
- next_to_process, cbuf[front].data, cbuf[front].op);
- if (cbuf[front].data) {
- rv = cbuf[front];
- cbuf[front].data = 0;
- cbuf[front].op = op_DAT;
- next_to_process++;
- }
- return rv;
- }
-
- // Insert a block into the reassembly queue at sequence number
- // SEQNO, with opcode OP and data section DATA. Returns true if the
- // block was successfully added to the queue, false if it is either
- // outside the acceptable window or duplicates a block already on
- // the queue (both of these cases indicate protocol errors).
- // DATA is consumed no matter what the return value is.
- bool
- insert(uint32_t seqno, opcode_t op, evbuffer *data, conn_t *conn)
- {
- if (seqno - window() > 255) {
- log_info(conn, "block outside receive window");
- evbuffer_free(data);
- return false;
- }
- uint8_t front = next_to_process & 0xFF;
- uint8_t pos = front + (seqno - window());
- if (cbuf[pos].data) {
- log_info(conn, "duplicate block");
- evbuffer_free(data);
- return false;
- }
-
- cbuf[pos].data = data;
- cbuf[pos].op = op;
- return true;
- }
-
- // Return the current lowest acceptable sequence number in the
- // receive window. This is the value to be passed to
- // block_header::valid().
- uint32_t window() const { return next_to_process; }
-
- // As the last step of a rekeying cycle, the expected next sequence number
- // is reset to zero.
- void reset()
- {
- for (int i = 0; i < 256; i++) {
- log_assert(!cbuf[i].data);
- }
- next_to_process = 0;
- }
-};
-
-// Protocol objects
+using namespace chop_blk;
struct chop_config_t;
struct chop_circuit_t;
@@ -883,7 +603,7 @@ chop_circuit_t::send_targeted(chop_conn_t *conn, size_t d, size_t p, opcode_t f,
}
v.iov_len = blocksize;
- block_header hdr(send_seq, d, p, f, *send_hdr_crypt);
+ header hdr(send_seq, d, p, f, *send_hdr_crypt);
log_assert(hdr.valid(send_seq));
memcpy(v.iov_base, hdr.nonce(), HEADER_LEN);
@@ -1335,7 +1055,7 @@ chop_conn_t::recv()
break;
}
- block_header hdr(recv_pending, *upstream->recv_hdr_crypt);
+ header hdr(recv_pending, *upstream->recv_hdr_crypt);
if (!hdr.valid(upstream->recv_queue.window())) {
const uint8_t *c = hdr.cleartext();
char fallbackbuf[4];
@@ -1543,8 +1263,6 @@ chop_conn_t::must_send_timeout(evutil_socket_t, short, void *arg)
static_cast<chop_conn_t *>(arg)->send();
}
-} // anonymous namespace
-
PROTO_DEFINE_MODULE(chop);
// Local Variables:
diff --git a/src/protocol/chop_blk.cc b/src/protocol/chop_blk.cc
new file mode 100644
index 0000000..460097d
--- /dev/null
+++ b/src/protocol/chop_blk.cc
@@ -0,0 +1,104 @@
+/* Copyright 2011, 2012 SRI International
+ * See LICENSE for other credits and copying information
+ */
+
+#include "util.h"
+#include "chop_blk.h"
+
+/* The chopper is the core StegoTorus protocol implementation.
+ For its design, see doc/chopper.txt. Note that it is still
+ being implemented, and may change incompatibly. */
+
+namespace chop_blk
+{
+
+const char *
+opname(opcode_t o, char fallbackbuf[4])
+{
+ switch (o) {
+ case op_DAT: return "DAT";
+ case op_FIN: return "FIN";
+ case op_RST: return "RST";
+ default: {
+ unsigned int x = o;
+ if (x < op_STEG0)
+ xsnprintf(fallbackbuf, sizeof fallbackbuf, "R%02x", x);
+ else
+ xsnprintf(fallbackbuf, sizeof fallbackbuf, "S%02x", x - op_STEG0);
+ return fallbackbuf;
+ }
+ }
+}
+
+reassembly_queue::reassembly_queue()
+ : next_to_process(0)
+{
+ memset(cbuf, 0, sizeof cbuf);
+}
+
+reassembly_queue::~reassembly_queue()
+{
+ for (int i = 0; i < 256; i++)
+ if (cbuf[i].data)
+ evbuffer_free(cbuf[i].data);
+}
+
+reassembly_elt
+reassembly_queue::remove_next()
+{
+ reassembly_elt rv = { 0, op_DAT };
+ uint8_t front = next_to_process & 0xFF;
+ char fallbackbuf[4];
+
+ log_debug("next_to_process=%d data=%p op=%s",
+ next_to_process, cbuf[front].data,
+ opname(cbuf[front].op, fallbackbuf));
+
+ if (cbuf[front].data) {
+ rv = cbuf[front];
+ cbuf[front].data = 0;
+ cbuf[front].op = op_DAT;
+ next_to_process++;
+ }
+ return rv;
+}
+
+bool
+reassembly_queue::insert(uint32_t seqno, opcode_t op,
+ evbuffer *data, conn_t *conn)
+{
+ if (seqno - window() > 255) {
+ log_info(conn, "block outside receive window");
+ evbuffer_free(data);
+ return false;
+ }
+ uint8_t front = next_to_process & 0xFF;
+ uint8_t pos = front + (seqno - window());
+ if (cbuf[pos].data) {
+ log_info(conn, "duplicate block");
+ evbuffer_free(data);
+ return false;
+ }
+
+ cbuf[pos].data = data;
+ cbuf[pos].op = op;
+ return true;
+}
+
+void
+reassembly_queue::reset()
+{
+ for (int i = 0; i < 256; i++) {
+ log_assert(!cbuf[i].data);
+ }
+ next_to_process = 0;
+}
+
+} // namespace chop_blk
+
+// Local Variables:
+// mode: c++
+// c-basic-offset: 2
+// c-file-style: "gnu"
+// c-file-offsets: ((innamespace . 0) (brace-list-open . 0))
+// End:
diff --git a/src/protocol/chop_blk.h b/src/protocol/chop_blk.h
new file mode 100644
index 0000000..9776a70
--- /dev/null
+++ b/src/protocol/chop_blk.h
@@ -0,0 +1,247 @@
+/* Copyright 2011, 2012 SRI International
+ * See LICENSE for other credits and copying information
+ */
+
+#ifndef CHOP_BLK_H
+#define CHOP_BLK_H
+
+#include "crypt.h"
+#include <event2/buffer.h>
+
+namespace chop_blk
+{
+
+/* Packets on the wire have a 16-byte header, consisting of a 32-bit
+ sequence number, two 16-bit length fields ("D" and "P"), an 8-bit
+ opcode ("F"), and a 56-bit check field. All numbers in this header
+ are serialized in network byte order.
+
+ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
+ |Sequence Number| D | P | F | Check |
+
+ The header is encrypted with AES in ECB mode: this is safe because
+ the header is exactly one AES block long, the sequence number is
+ never repeated, the header-encryption key is not used for anything
+ else, and the high 24 bits of the sequence number, plus the check
+ field, constitute an 80-bit MAC. The receiver maintains a
+ 256-element sliding window of acceptable sequence numbers, which
+ begins one after the highest sequence number so far _processed_
+ (not received). If the sequence number is outside this window, or
+ the check field is not all-bits-zero, the packet is discarded. An
+ attacker's odds of being able to manipulate the D, P, or F fields
+ or the low bits of the sequence number are therefore less than one
+ in 2^80. Unlike TCP, our sequence numbers always start at zero on
+ a new (or freshly rekeyed) circuit, and increment by one per
+ _block_, not per byte of data. Furthermore, they do not wrap: a
+ rekeying cycle (which resets the sequence number) is required to
+ occur before the highest-received sequence number reaches 2^32.
+
+ Following the header are two variable-length payload sections,
+ "data" and "padding", whose length in bytes are given by the D and
+ P fields, respectively. These sections are encrypted, using a
+ different key, with AES in GCM mode. The *encrypted* packet header
+ doubles as the GCM nonce. The semantics of the "data" section's
+ contents, if any, are defined by the opcode F. The "padding"
+ section SHOULD be filled with zeroes by the sender; regardless, its
+ contents MUST be ignored by the receiver. Following these sections
+ is a 16-byte GCM authentication tag, computed over the data and
+ padding sections only, NOT the message header. */
+
+const size_t HEADER_LEN = 16;
+const size_t TRAILER_LEN = 16;
+const size_t SECTION_LEN = UINT16_MAX;
+const size_t MIN_BLOCK_SIZE = HEADER_LEN + TRAILER_LEN;
+const size_t MAX_BLOCK_SIZE = MIN_BLOCK_SIZE + SECTION_LEN*2;
+
+const size_t HANDSHAKE_LEN = sizeof(uint32_t);
+
+enum opcode_t
+{
+ op_DAT = 0, // Pass data section along to upstream
+ op_FIN = 1, // No further transmissions (pass data along if any)
+ op_RST = 2, // Protocol error, close circuit now
+ op_RESERVED0 = 3, // 3 -- 127 reserved for future definition
+ op_STEG0 = 128, // 128 -- 255 reserved for steganography modules
+ op_LAST = 255
+};
+
+/**
+ * Produce a human-readable codename for opcode O.
+ * FALLBACKBUF is used for opcodes that have no official assignment.
+ * Will either return FALLBACKBUF or a pointer to a string constant.
+ */
+extern const char *opname(opcode_t o, char fallbackbuf[4]);
+
+class header
+{
+ uint8_t clear[16];
+ uint8_t ciphr[16];
+
+public:
+ header(uint32_t s, uint16_t d, uint16_t p, opcode_t f, ecb_encryptor &ec)
+ {
+ if (f > op_LAST || (f >= op_RESERVED0 && f < op_STEG0)) {
+ memset(clear, 0xFF, sizeof clear); // invalid!
+ memset(ciphr, 0xFF, sizeof ciphr);
+ return;
+ }
+
+ // sequence number
+ clear[0] = (s >> 24) & 0xFF;
+ clear[1] = (s >> 16) & 0xFF;
+ clear[2] = (s >> 8) & 0xFF;
+ clear[3] = (s ) & 0xFF;
+
+ // D field
+ clear[4] = (d >> 8) & 0xFF;
+ clear[5] = (d ) & 0xFF;
+
+ // P field
+ clear[6] = (p >> 8) & 0xFF;
+ clear[7] = (p ) & 0xFF;
+
+ // F field
+ clear[8] = uint8_t(f);
+
+ // Check field
+ memset(clear + 9, 0, 7);
+
+ ec.encrypt(ciphr, clear);
+ }
+
+ header(evbuffer *buf, ecb_decryptor &dc)
+ {
+ if (evbuffer_copyout(buf, ciphr, sizeof ciphr) != sizeof ciphr) {
+ memset(clear, 0xFF, sizeof clear);
+ memset(ciphr, 0xFF, sizeof ciphr);
+ return;
+ }
+ dc.decrypt(clear, ciphr);
+ }
+
+ uint32_t seqno() const
+ {
+ return ((uint32_t(clear[0]) << 24) |
+ (uint32_t(clear[1]) << 16) |
+ (uint32_t(clear[2]) << 8) |
+ (uint32_t(clear[3]) ));
+
+ }
+
+ size_t dlen() const
+ {
+ return ((uint16_t(clear[4]) << 8) |
+ (uint16_t(clear[5]) ));
+ }
+
+ size_t plen() const
+ {
+ return ((uint16_t(clear[6]) << 8) |
+ (uint16_t(clear[7]) ));
+ }
+
+ size_t total_len() const
+ {
+ return HEADER_LEN + TRAILER_LEN + dlen() + plen();
+ }
+
+ opcode_t opcode() const
+ {
+ return opcode_t(clear[8]);
+ }
+
+ bool valid(uint64_t window) const
+ {
+ // This check must run in constant time.
+ uint8_t ck = (clear[ 9] | clear[10] | clear[11] | clear[12] |
+ clear[13] | clear[14] | clear[15]);
+ uint32_t delta = seqno() - window;
+ ck |= !!(delta & ~uint32_t(0xFF));
+ return !ck;
+ }
+
+ const uint8_t *nonce() const
+ {
+ return ciphr;
+ }
+
+ const uint8_t *cleartext() const
+ {
+ return clear;
+ }
+};
+
+
+/* Most of a block's header information is processed before it reaches
+ the reassembly queue; the only things the queue needs to record are
+ the sequence number (which is stored implictly), the opcode, and an
+ evbuffer holding the data section. Zero-data blocks still get an
+ evbuffer, for simplicity's sake: a reassembly queue element holds a
+ received block if and only if its data pointer is non-null.
+
+ The reassembly queue is a 256-element circular buffer of
+ 'reassembly_elt' structs. This corresponds to the 256-element
+ sliding window of sequence numbers which may legitimately be
+ received at any time. */
+
+struct reassembly_elt
+{
+ evbuffer *data;
+ opcode_t op;
+};
+
+class reassembly_queue
+{
+ reassembly_elt cbuf[256];
+ uint32_t next_to_process;
+
+ reassembly_queue(const reassembly_queue&) DELETE_METHOD;
+ reassembly_queue& operator=(const reassembly_queue&) DELETE_METHOD;
+
+public:
+ reassembly_queue();
+ ~reassembly_queue();
+
+ /**
+ * Remove the next block to be processed from the reassembly queue
+ * and return it. If we are out of blocks or the next block to
+ * process has not yet arrived, return an empty reassembly_elt.
+ * Caller is responsible for freeing the evbuffer in the
+ * reassembly_elt, if any.
+ */
+ reassembly_elt remove_next();
+
+ /**
+ * Insert a block into the reassembly queue at sequence number
+ * SEQNO, with opcode OP and data section DATA. Returns true if the
+ * block was successfully added to the queue, false if it is either
+ * outside the acceptable window or duplicates a block already on
+ * the queue (both of these cases indicate protocol errors).
+ * DATA is consumed no matter what the return value is.
+ */
+ bool insert(uint32_t seqno, opcode_t op, evbuffer *data, conn_t *conn);
+
+ /**
+ * Return the current lowest acceptable sequence number in the
+ * receive window. This is the value to be passed to
+ * block_header::valid().
+ */
+ uint32_t window() const { return next_to_process; }
+
+ /**
+ * Reset the expected next sequence number to zero. The queue must
+ * be empty. This is done as the last step of a rekeying cycle.
+ */
+ void reset();
+};
+
+} // namespace chop_blk
+
+#endif /* chop_blk.h */
+
+// Local Variables:
+// mode: c++
+// c-basic-offset: 2
+// c-file-style: "gnu"
+// c-file-offsets: ((innamespace . 0) (brace-list-open . 0))
+// End:
More information about the tor-commits
mailing list