[tor-commits] [stegotorus/master] Overhaul base64 cookie generation. Add unit testing of base64.
zwol at torproject.org
zwol at torproject.org
Fri Jul 20 23:17:08 UTC 2012
commit 902aa5b3a46da72a3e080d3c1844cc65c8bd169d
Author: Zack Weinberg <zackw at cmu.edu>
Date: Fri Jun 15 13:38:34 2012 -0700
Overhaul base64 cookie generation. Add unit testing of base64.
This fixes the bug where an HTTP cookie header could have a newline in it.
---
Makefile.am | 1 +
src/base64.cc | 69 +++++++-----
src/base64.h | 21 +++-
src/steg/b64cookies.cc | 262 +++++++++---------------------------------
src/steg/b64cookies.h | 7 +-
src/steg/http.cc | 69 ++++--------
src/test/unittest_base64.cc | 194 ++++++++++++++++++++++++++++++++
7 files changed, 336 insertions(+), 287 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index d21fdde..513e848 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -54,6 +54,7 @@ stegotorus_SOURCES = \
src/main.cc
UTGROUPS = \
+ src/test/unittest_base64.cc \
src/test/unittest_compression.cc \
src/test/unittest_config.cc \
src/test/unittest_crypt.cc \
diff --git a/src/base64.cc b/src/base64.cc
index 8d53d0f..f742e2d 100644
--- a/src/base64.cc
+++ b/src/base64.cc
@@ -8,18 +8,25 @@
const int CHARS_PER_LINE = 72;
static char
-encode1(char v)
+encode1(unsigned int value, char plus, char slash, char eq)
{
const char encoding[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz0123456789+/";
- unsigned int value_in = v;
- if (value_in > sizeof(encoding)-1) return '=';
- return encoding[value_in];
+ if (value > sizeof encoding - 1)
+ return eq;
+
+ char rv = encoding[value];
+ if (rv == '+')
+ return plus;
+ else if (rv == '/')
+ return slash;
+ else
+ return rv;
}
/* assumes ASCII */
static int
-decode1(char v)
+decode1(unsigned int value, char plus, char slash)
{
const signed char decoding[] = {
// + , - . / 0 1 2 3 4 5 6 7 8 9
@@ -38,11 +45,15 @@ decode1(char v)
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
};
- unsigned int value_in = v;
- value_in -= 43;
- if (value_in > sizeof(decoding))
+ if (value == (unsigned int)plus)
+ value = '+';
+ else if (value == (unsigned int)slash)
+ value = '/';
+
+ value -= 43;
+ if (value > sizeof(decoding))
return -1;
- return decoding[value_in];
+ return decoding[value];
}
namespace base64
@@ -69,7 +80,7 @@ encoder::encode(const char* plaintext_in, size_t length_in, char* code_out)
}
fragment = *plainchar++;
result = (fragment & 0x0fc) >> 2;
- *codechar++ = encode1(result);
+ *codechar++ = encode1(result, plus, slash, equals);
result = (fragment & 0x003) << 4;
case step_B:
if (plainchar == plaintextend) {
@@ -79,7 +90,7 @@ encoder::encode(const char* plaintext_in, size_t length_in, char* code_out)
}
fragment = *plainchar++;
result |= (fragment & 0x0f0) >> 4;
- *codechar++ = encode1(result);
+ *codechar++ = encode1(result, plus, slash, equals);
result = (fragment & 0x00f) << 2;
case step_C:
if (plainchar == plaintextend) {
@@ -89,14 +100,16 @@ encoder::encode(const char* plaintext_in, size_t length_in, char* code_out)
}
fragment = *plainchar++;
result |= (fragment & 0x0c0) >> 6;
- *codechar++ = encode1(result);
+ *codechar++ = encode1(result, plus, slash, equals);
result = (fragment & 0x03f) >> 0;
- *codechar++ = encode1(result);
+ *codechar++ = encode1(result, plus, slash, equals);
- ++(this->stepcount);
- if (this->stepcount == CHARS_PER_LINE/4) {
- *codechar++ = '\n';
- this->stepcount = 0;
+ if (wrap) {
+ ++(this->stepcount);
+ if (this->stepcount == CHARS_PER_LINE/4) {
+ *codechar++ = '\n';
+ this->stepcount = 0;
+ }
}
}
default:
@@ -111,18 +124,20 @@ encoder::encode_end(char* code_out)
switch (this->step) {
case step_B:
- *codechar++ = encode1(this->result);
- *codechar++ = '=';
- *codechar++ = '=';
+ *codechar++ = encode1(this->result, plus, slash, equals);
+ *codechar++ = equals;
+ *codechar++ = equals;
break;
case step_C:
- *codechar++ = encode1(this->result);
- *codechar++ = '=';
+ *codechar++ = encode1(this->result, plus, slash, equals);
+ *codechar++ = equals;
break;
case step_A:
break;
}
- *codechar++ = '\n';
+ if (wrap)
+ *codechar++ = '\n';
+ *codechar = '\0';
/* reset */
this->step = step_A;
@@ -149,7 +164,7 @@ decoder::decode(const char* code_in, size_t length_in, char* plaintext_out)
this->plainchar = *plainchar;
return plainchar - plaintext_out;
}
- fragment = decode1(*codechar++);
+ fragment = decode1(*codechar++, plus, slash);
} while (fragment < 0);
*plainchar = (fragment & 0x03f) << 2;
case step_B:
@@ -159,7 +174,7 @@ decoder::decode(const char* code_in, size_t length_in, char* plaintext_out)
this->plainchar = *plainchar;
return plainchar - plaintext_out;
}
- fragment = decode1(*codechar++);
+ fragment = decode1(*codechar++, plus, slash);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x030) >> 4;
*plainchar = (fragment & 0x00f) << 4;
@@ -170,7 +185,7 @@ decoder::decode(const char* code_in, size_t length_in, char* plaintext_out)
this->plainchar = *plainchar;
return plainchar - plaintext_out;
}
- fragment = decode1(*codechar++);
+ fragment = decode1(*codechar++, plus, slash);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x03c) >> 2;
*plainchar = (fragment & 0x003) << 6;
@@ -181,7 +196,7 @@ decoder::decode(const char* code_in, size_t length_in, char* plaintext_out)
this->plainchar = *plainchar;
return plainchar - plaintext_out;
}
- fragment = decode1(*codechar++);
+ fragment = decode1(*codechar++, plus, slash);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x03f);
}
diff --git a/src/base64.h b/src/base64.h
index adc0dae..4526309 100644
--- a/src/base64.h
+++ b/src/base64.h
@@ -16,10 +16,18 @@ class encoder
encode_step step;
int stepcount;
char result;
+ char plus;
+ char slash;
+ char equals;
+ bool wrap;
public:
- encoder()
- : step(step_A), stepcount(0), result(0)
+ // The optional arguments to the constructor allow you to disable
+ // line-wrapping and/or replace the characters used to encode digits
+ // 62 and 63 and padding (normally '+', '/', and '=' respectively).
+ encoder(bool wr = true, char pl = '+', char sl = '/', char eq = '=')
+ : step(step_A), stepcount(0), result(0),
+ plus(pl), slash(sl), equals(eq), wrap(wr)
{}
ptrdiff_t encode(const char* plaintext_in, size_t length_in, char* code_out);
@@ -31,10 +39,15 @@ class decoder
enum decode_step { step_A, step_B, step_C, step_D };
decode_step step;
char plainchar;
+ char plus;
+ char slash;
+ char equals;
+ bool wrap;
public:
- decoder()
- : step(step_A), plainchar(0)
+ decoder(char pl = '+', char sl = '/', char eq = '=')
+ : step(step_A), plainchar(0),
+ plus(pl), slash(sl), equals(eq)
{}
ptrdiff_t decode(const char* code_in, size_t length_in, char* plaintext_out);
diff --git a/src/steg/b64cookies.cc b/src/steg/b64cookies.cc
index 4d85a8b..6f95004 100644
--- a/src/steg/b64cookies.cc
+++ b/src/steg/b64cookies.cc
@@ -5,230 +5,80 @@
#include "util.h"
#include "b64cookies.h"
-int unwrap_b64_cookie(char* inbuf, char* outbuf, int buflen) {
- int i,j;
- j = 0;
-
- for (i=0; i < buflen; i++) {
- char c = inbuf[i];
+size_t
+unwrap_b64_cookies(char *outbuf, const char *inbuf, size_t inlen)
+{
+ size_t i, j;
+ for (i = 0, j = 0; i < inlen; i++) {
+ char c = inbuf[i];
if (c != ' ' && c != ';' && c != '=')
- outbuf[j++] = c;
+ outbuf[j++] = c;
}
return j;
-
}
-
-
-
-
-int gen_one_b64cookie(char* outbuf, int& cookielen, char* data, int datalen) {
- int sofar = 0;
- int namelen;
- int data_consumed = 0;
-
- cookielen = 4 + rand() % (datalen - 3);
-
- if (cookielen > 13)
- namelen = rand() % 10 + 1;
- else
- namelen = rand() % (cookielen - 3) + 1;
-
-
- while (sofar < namelen) {
- outbuf[sofar++] = data[data_consumed++];
- }
-
-
- outbuf[sofar++] = '=';
-
- while (sofar < cookielen) {
- outbuf[sofar++] = data[data_consumed++];
+static size_t
+gen_one_cookie(char *&outbuf, const char *&inbuf, size_t inlen)
+{
+ size_t adv_in = 0;
+ size_t adv_out = 0;
+ size_t namelen, cookielen;
+
+ if (inlen < 5) {
+ memcpy(outbuf, inbuf, inlen);
+ outbuf += inlen;
+ inbuf += inlen;
+ return inlen;
}
-
- return data_consumed;
-
-}
-
-
-
-
-/* returns length of cookie */
-int gen_b64_cookie_field(char* outbuf, char* data, int datalen) {
- int onecookielen;
- int consumed = 0;
- int cookielen = 0;
-
-
-
- while (datalen - consumed > 0) {
- int cnt = gen_one_b64cookie(outbuf, onecookielen, data + consumed, datalen - consumed);
-
- if (cnt < 0) {
- log_warn("couldn't create cookie: %d\n", cnt);
- return cnt;
- }
-
- consumed += cnt;
- outbuf += onecookielen;
- cookielen += onecookielen;
-
- if (datalen - consumed < 5) {
- memcpy(outbuf, data+consumed, datalen-consumed);
- return cookielen + datalen - consumed;
- }
-
-
- if (datalen - consumed > 0) {
- outbuf[0] = ';';
- outbuf++;
- cookielen++;
- }
- }
-
-
- return cookielen;
-}
-
-
-
-void sanitize_b64(char* input, int len) {
- char* i = strchr(input, '/');
- int eqcnt = 0;
-
- printf("len = %d\n", len);
-
- while (i != NULL) {
- *i = '_';
- i = strchr(i+1, '/');
- }
-
- i = strchr(input, '+');
-
- while (i != NULL) {
- *i = '.';
- i = strchr(i+1, '+');
+ if (inlen < 10) {
+ namelen = rand() % 5 + 1;
+ } else {
+ namelen = rand() % 10 + 1;
}
- if (input[len-2] == '=')
- eqcnt = 2;
- else if (input[len-1] == '=')
- eqcnt = 1;
+ cookielen = rand() % (inlen * 2 / 3);
+ if (cookielen > inlen - namelen)
+ cookielen = inlen - namelen;
+ memcpy(outbuf, inbuf, namelen);
+ adv_in += namelen;
+ adv_out += namelen;
- while (eqcnt > 0) {
- int pos = rand() % (len - 1) + 1;
- if (pos >= len - eqcnt) {
- input[pos] = '-';
- eqcnt--;
- continue;
- }
-
- //shift characters up and insert '-' in the middle
- for (int j=len-eqcnt; j > pos; j--)
- input[j] = input[j-1];
+ outbuf[adv_out++] = '=';
- input[pos] = '-';
- eqcnt--;
+ memcpy(outbuf + adv_out, inbuf + adv_in, cookielen);
+ adv_in += cookielen;
+ adv_out += cookielen;
- }
-
+ outbuf += adv_out;
+ inbuf += adv_in;
+ return adv_in;
}
-
-void desanitize_b64(char* input, int len) {
- char* i = strchr(input, '_');
-
-
- printf("len = %d\n", len);
- while (i != NULL) {
- *i = '/';
- i = strchr(i+1, '_');
- }
-
- i = strchr(input, '.');
-
- while (i != NULL) {
- *i = '+';
- i = strchr(i+1, '.');
- }
-
-
- i = strchr(input, '-');
- if (i != NULL) {
- int j;
- for (j=i-input; j < len-1; j++)
- input[j] = input[j+1];
- input[len-1] = '=';
-
-
- i = strchr(input, '-');
-
- if (i != NULL) {
- for (j=i-input; j < len-2; j++)
- input[j] = input[j+1];
- input[len-2] = '=';
+/* returns length of cookie */
+size_t
+gen_b64_cookies(char *outbuf, const char *inbuf, size_t inlen)
+{
+ char *outp = outbuf;
+ const char *inp = inbuf;
+ size_t processed = 0;
+
+ while (processed < inlen) {
+ processed += gen_one_cookie(outp, inp, inlen - processed);
+
+ size_t remain = inlen - processed;
+ if (remain < 5) {
+ memcpy(outp, inp, remain);
+ outp += remain;
+ inp += remain;
+ break;
}
-
+ if (remain > 0)
+ *outp++ = ';';
}
+ return outp - outbuf;
}
-
-
-
-
-
-// int main () {
-// char outbuf[200];
-
-// char data[56] = "ba239023820389023802380389abc2322132321932847203aedfasd";
-// char data2[200];
-// int cookielen;
-
-// srand(time(NULL));
-
-// memset(data2, 0, sizeof(data2));
-// memset(outbuf, 0, sizeof(outbuf));
-
-// base64::encoder E;
-// E.encode(data, strlen(data), data2);
-// E.encode_end(data2+strlen(data2));
-// printf("%s", data2);
-// // remove trailing newline
-// data2[strlen(data2) - 1] = 0;
-
-// // /* substitute / with _, + with ., = with - that maybe inserted anywhere in the middle */
-
-// sanitize_b64(data2, strlen(data2));
-// printf("%s\n", data2);
-
-// cookielen = gen_b64_cookie_field((char*) outbuf, (char*) data2, strlen(data2));
-// printf("cookie=%s\n", outbuf);
-
-// memset(data2, 0, sizeof(data2));
-// cookielen = unwrap_b64_cookie((char*) outbuf, (char*) data2, strlen(outbuf));
-
-
-// desanitize_b64(data2, cookielen);
-// printf("%s\n", data2);
-// printf("%d\n", cookielen);
-
-// data2[strlen(data2)] = '\n';
-
-
-// memset(outbuf, 0, sizeof(outbuf));
-
-// base64::decoder D;
-// D.decode(data2, strlen(data2), outbuf);
-// printf("%s\n", outbuf);
-
-
-
-// }
-
-
-
-
diff --git a/src/steg/b64cookies.h b/src/steg/b64cookies.h
index 1556cdd..6723f57 100644
--- a/src/steg/b64cookies.h
+++ b/src/steg/b64cookies.h
@@ -5,10 +5,7 @@
#ifndef _B64_COOKIES_H
#define _B64_COOKIES_H
-int unwrap_b64_cookie(char* inbuf, char* outbuf, int buflen);
-int gen_b64_cookie_field(char* outbuf, char* data, int datalen);
-int gen_one_b64cookie(char* outbuf, int& cookielen, char* data, int datalen);
-void sanitize_b64(char* input, int len);
-void desanitize_b64(char* input, int len);
+size_t unwrap_b64_cookies(char *outbuf, const char *inbuf, size_t inlen);
+size_t gen_b64_cookies(char *outbuf, const char *inbuf, size_t inlen);
#endif
diff --git a/src/steg/http.cc b/src/steg/http.cc
index 171bdcb..d8dbf5e 100644
--- a/src/steg/http.cc
+++ b/src/steg/http.cc
@@ -262,12 +262,6 @@ int
http_client_cookie_transmit (http_steg_t *s, struct evbuffer *source,
conn_t *conn)
{
-
- /* On the client side, we have to embed the data in a GET query somehow;
- the only plausible places to put it are the URL and cookies. This
- presently uses the URL. And it can't be binary. */
-
-
struct evbuffer *dest = conn->outbound();
size_t sbuflen = evbuffer_get_length(source);
int bufsize = 10000;
@@ -276,24 +270,24 @@ http_client_cookie_transmit (http_steg_t *s, struct evbuffer *source,
char* data;
char* data2 = (char*) xmalloc (sbuflen*4);
char* cookiebuf = (char*) xmalloc (sbuflen*8);
- int payload_len = 0;
- int cnt = 0;
- int cookie_len = 0;
- int rval;
- int len = 0;
- base64::encoder E;
-
-
+ size_t payload_len = 0;
+ size_t cnt = 0;
+ size_t cookie_len = 0;
+ size_t rval;
+ size_t len = 0;
+ // '+' -> '-', '/' -> '_', '=' -> '.' per
+ // RFC4648 "Base 64 encoding with URL and filename safe alphabet"
+ // (which does not replace '=', but dot is an obvious choice; for
+ // this use case, the fact that some file systems don't allow more
+ // than one dot in a filename is irrelevant).
+ base64::encoder E(false, '-', '_', '.');
data = (char*) evbuffer_pullup(source, sbuflen);
-
- if (data == NULL) {
+ if (!data) {
log_debug("evbuffer_pullup failed");
goto err;
}
-
-
// retry up to 10 times
while (!payload_len) {
payload_len = find_client_payload(s->config->pl, buf, bufsize,
@@ -308,28 +302,15 @@ http_client_cookie_transmit (http_steg_t *s, struct evbuffer *source,
lookup_peer_name_from_ip(conn->peername, s->peer_dnsname);
memset(data2, 0, sbuflen*4);
- E.encode((char*) data, sbuflen, (char*) data2);
- E.encode_end(data2+strlen((char*) data2));
-
- len = (int) strlen(data2) - 1;
- // remove trailing newline
- data2[len] = 0;
-
- // substitute / with _, + with ., = with - that maybe inserted anywhere in the middle
- sanitize_b64(data2, len);
+ len = E.encode(data, sbuflen, data2);
+ len += E.encode_end(data2+len);
-
- cookie_len = gen_b64_cookie_field(cookiebuf, data2, len);
+ cookie_len = gen_b64_cookies(cookiebuf, data2, len);
cookiebuf[cookie_len] = 0;
-
-
- if (cookie_len < 0) {
- log_debug("cookie generation failed\n");
- return -1;
- }
- log_debug(conn, "cookie input %ld encoded %d final %d",
- (long)sbuflen, len, cookie_len);
+ log_debug(conn, "cookie input %lu encoded %lu final %lu/%lu",
+ (unsigned long)sbuflen, (unsigned long)len,
+ (unsigned long)cookie_len, strlen(cookiebuf));
log_debug(conn, "cookie encoded: %s", data2);
log_debug(conn, "cookie final: %s", cookiebuf);
@@ -373,14 +354,12 @@ http_client_cookie_transmit (http_steg_t *s, struct evbuffer *source,
rval = evbuffer_add(dest, "\r\n\r\n", 4);
-
+
if (rval) {
log_warn("error adding terminators \n");
goto err;
}
-
-
evbuffer_drain(source, sbuflen);
log_debug("CLIENT TRANSMITTED payload %d\n", (int) sbuflen);
conn->cease_transmission();
@@ -625,6 +604,8 @@ http_server_receive(http_steg_t *s, conn_t *conn, struct evbuffer *dest, struct
char* data;
int type;
+ log_debug("Receive dump:");
+
do {
struct evbuffer_ptr s2 = evbuffer_search(source, "\r\n\r\n", sizeof ("\r\n\r\n") -1 , NULL);
char *p;
@@ -662,6 +643,7 @@ http_server_receive(http_steg_t *s, conn_t *conn, struct evbuffer *dest, struct
else
p = data + sizeof "GET /" -1;
+ log_debug("Cookie: %s", p);
pend = strstr(p, "\r\n");
log_assert(pend);
if (pend - p > MAX_COOKIE_SIZE * 3/2)
@@ -669,13 +651,10 @@ http_server_receive(http_steg_t *s, conn_t *conn, struct evbuffer *dest, struct
(unsigned long)(pend - p), (unsigned long)MAX_COOKIE_SIZE);
memset(outbuf, 0, sizeof(outbuf));
- int cookielen = unwrap_b64_cookie((char*) p, (char*) outbuf, pend - p);
+ size_t cookielen = unwrap_b64_cookies(outbuf, p, pend - p);
- desanitize_b64(outbuf, cookielen);
- outbuf[cookielen] = '\n';
+ base64::decoder D('-', '_', '.');
memset(outbuf2, 0, sizeof(outbuf2));
-
- base64::decoder D;
sofar = D.decode(outbuf, cookielen+1, outbuf2);
if (sofar <= 0)
diff --git a/src/test/unittest_base64.cc b/src/test/unittest_base64.cc
new file mode 100644
index 0000000..76f122b
--- /dev/null
+++ b/src/test/unittest_base64.cc
@@ -0,0 +1,194 @@
+/* Copyright 2012 SRI International
+ * See LICENSE for other credits and copying information
+ */
+
+#include "util.h"
+#include "unittest.h"
+#include "base64.h"
+
+struct testvec
+{
+ const char *dec;
+ const char *enc;
+ size_t declen;
+ size_t enclen;
+};
+
+#define S_(x) #x
+#define S(x) S_(x)
+
+const struct testvec testvecs[] = {
+ // padding tests from RFC 4648
+ { "", "", 0, 0 },
+ { "f", "Zg==", 1, 4 },
+ { "fo", "Zm8=", 2, 4 },
+ { "foo", "Zm9v", 3, 4 },
+ { "foob", "Zm9vYg==", 4, 8 },
+ { "fooba", "Zm9vYmE=", 5, 8 },
+ { "foobar", "Zm9vYmFy", 6, 8 },
+
+ // all single bytes
+#define B(b,e) { S(\x##b), S(e==), 1, 4 }
+ B(00,AA), B(01,AQ), B(02,Ag), B(03,Aw), B(04,BA), B(05,BQ), B(06,Bg),
+ B(07,Bw), B(08,CA), B(09,CQ), B(0a,Cg), B(0b,Cw), B(0c,DA), B(0d,DQ),
+ B(0e,Dg), B(0f,Dw), B(10,EA), B(11,EQ), B(12,Eg), B(13,Ew), B(14,FA),
+ B(15,FQ), B(16,Fg), B(17,Fw), B(18,GA), B(19,GQ), B(1a,Gg), B(1b,Gw),
+ B(1c,HA), B(1d,HQ), B(1e,Hg), B(1f,Hw), B(20,IA), B(21,IQ), B(22,Ig),
+ B(23,Iw), B(24,JA), B(25,JQ), B(26,Jg), B(27,Jw), B(28,KA), B(29,KQ),
+ B(2a,Kg), B(2b,Kw), B(2c,LA), B(2d,LQ), B(2e,Lg), B(2f,Lw), B(30,MA),
+ B(31,MQ), B(32,Mg), B(33,Mw), B(34,NA), B(35,NQ), B(36,Ng), B(37,Nw),
+ B(38,OA), B(39,OQ), B(3a,Og), B(3b,Ow), B(3c,PA), B(3d,PQ), B(3e,Pg),
+ B(3f,Pw), B(40,QA), B(41,QQ), B(42,Qg), B(43,Qw), B(44,RA), B(45,RQ),
+ B(46,Rg), B(47,Rw), B(48,SA), B(49,SQ), B(4a,Sg), B(4b,Sw), B(4c,TA),
+ B(4d,TQ), B(4e,Tg), B(4f,Tw), B(50,UA), B(51,UQ), B(52,Ug), B(53,Uw),
+ B(54,VA), B(55,VQ), B(56,Vg), B(57,Vw), B(58,WA), B(59,WQ), B(5a,Wg),
+ B(5b,Ww), B(5c,XA), B(5d,XQ), B(5e,Xg), B(5f,Xw), B(60,YA), B(61,YQ),
+ B(62,Yg), B(63,Yw), B(64,ZA), B(65,ZQ), B(66,Zg), B(67,Zw), B(68,aA),
+ B(69,aQ), B(6a,ag), B(6b,aw), B(6c,bA), B(6d,bQ), B(6e,bg), B(6f,bw),
+ B(70,cA), B(71,cQ), B(72,cg), B(73,cw), B(74,dA), B(75,dQ), B(76,dg),
+ B(77,dw), B(78,eA), B(79,eQ), B(7a,eg), B(7b,ew), B(7c,fA), B(7d,fQ),
+ B(7e,fg), B(7f,fw), B(80,gA), B(81,gQ), B(82,gg), B(83,gw), B(84,hA),
+ B(85,hQ), B(86,hg), B(87,hw), B(88,iA), B(89,iQ), B(8a,ig), B(8b,iw),
+ B(8c,jA), B(8d,jQ), B(8e,jg), B(8f,jw), B(90,kA), B(91,kQ), B(92,kg),
+ B(93,kw), B(94,lA), B(95,lQ), B(96,lg), B(97,lw), B(98,mA), B(99,mQ),
+ B(9a,mg), B(9b,mw), B(9c,nA), B(9d,nQ), B(9e,ng), B(9f,nw), B(a0,oA),
+ B(a1,oQ), B(a2,og), B(a3,ow), B(a4,pA), B(a5,pQ), B(a6,pg), B(a7,pw),
+ B(a8,qA), B(a9,qQ), B(aa,qg), B(ab,qw), B(ac,rA), B(ad,rQ), B(ae,rg),
+ B(af,rw), B(b0,sA), B(b1,sQ), B(b2,sg), B(b3,sw), B(b4,tA), B(b5,tQ),
+ B(b6,tg), B(b7,tw), B(b8,uA), B(b9,uQ), B(ba,ug), B(bb,uw), B(bc,vA),
+ B(bd,vQ), B(be,vg), B(bf,vw), B(c0,wA), B(c1,wQ), B(c2,wg), B(c3,ww),
+ B(c4,xA), B(c5,xQ), B(c6,xg), B(c7,xw), B(c8,yA), B(c9,yQ), B(ca,yg),
+ B(cb,yw), B(cc,zA), B(cd,zQ), B(ce,zg), B(cf,zw), B(d0,0A), B(d1,0Q),
+ B(d2,0g), B(d3,0w), B(d4,1A), B(d5,1Q), B(d6,1g), B(d7,1w), B(d8,2A),
+ B(d9,2Q), B(da,2g), B(db,2w), B(dc,3A), B(dd,3Q), B(de,3g), B(df,3w),
+ B(e0,4A), B(e1,4Q), B(e2,4g), B(e3,4w), B(e4,5A), B(e5,5Q), B(e6,5g),
+ B(e7,5w), B(e8,6A), B(e9,6Q), B(ea,6g), B(eb,6w), B(ec,7A), B(ed,7Q),
+ B(ee,7g), B(ef,7w), B(f0,8A), B(f1,8Q), B(f2,8g), B(f3,8w), B(f4,9A),
+ B(f5,9Q), B(f6,9g), B(f7,9w), B(f8,+A), B(f9,+Q), B(fa,+g), B(fb,+w),
+ B(fc,/A), B(fd,/Q), B(fe,/g), B(ff,/w),
+#undef B
+
+ // all single base64 digits, padded on the left and the right with an A
+#define D(d1,d2,e) { S(\x##d1\x##d2), "A" S(e) "A=", 2, 4 }
+ D(00,00,A), D(00,10,B), D(00,20,C), D(00,30,D), D(00,40,E),
+ D(00,50,F), D(00,60,G), D(00,70,H), D(00,80,I), D(00,90,J),
+ D(00,a0,K), D(00,b0,L), D(00,c0,M), D(00,d0,N), D(00,e0,O),
+ D(00,f0,P), D(01,00,Q), D(01,10,R), D(01,20,S), D(01,30,T),
+ D(01,40,U), D(01,50,V), D(01,60,W), D(01,70,X), D(01,80,Y),
+ D(01,90,Z), D(01,a0,a), D(01,b0,b), D(01,c0,c), D(01,d0,d),
+ D(01,e0,e), D(01,f0,f), D(02,00,g), D(02,10,h), D(02,20,i),
+ D(02,30,j), D(02,40,k), D(02,50,l), D(02,60,m), D(02,70,n),
+ D(02,80,o), D(02,90,p), D(02,a0,q), D(02,b0,r), D(02,c0,s),
+ D(02,d0,t), D(02,e0,u), D(02,f0,v), D(03,00,w), D(03,10,x),
+ D(03,20,y), D(03,30,z), D(03,40,0), D(03,50,1), D(03,60,2),
+ D(03,70,3), D(03,80,4), D(03,90,5), D(03,a0,6), D(03,b0,7),
+ D(03,c0,8), D(03,d0,9), D(03,e0,+), D(03,f0,/),
+#undef D
+
+ { 0, 0, 0, 0 }
+};
+
+
+static void
+test_base64_standard(void *)
+{
+ base64::encoder E(false);
+ base64::decoder D;
+
+ char buf[16];
+
+ for (const struct testvec *tv = testvecs; tv->dec; tv++) {
+ size_t len = E.encode(tv->dec, tv->declen, buf);
+ E.encode_end(buf + len);
+ tt_str_op(buf, ==, tv->enc);
+
+ len = D.decode(tv->enc, tv->enclen, buf);
+ D.reset();
+ tt_uint_op(len, ==, tv->declen);
+ tt_mem_op(buf, ==, tv->dec, tv->declen);
+ }
+
+ end:;
+}
+
+static void
+test_base64_altpunct(void *)
+{
+ base64::encoder E(false, '-', '_', '.');
+ base64::decoder D('-', '_', '.');
+
+ char buf[16];
+ char cenc[16];
+
+ for (const struct testvec *tv = testvecs; tv->dec; tv++) {
+ memcpy(cenc, tv->enc, tv->enclen);
+ cenc[tv->enclen] = '\0';
+ for (size_t i = 0; i < tv->enclen; i++)
+ switch (cenc[i]) {
+ default: break;
+ case '+': cenc[i] = '-'; break;
+ case '/': cenc[i] = '_'; break;
+ case '=': cenc[i] = '.'; break;
+ }
+
+ size_t len = E.encode(tv->dec, tv->declen, buf);
+ E.encode_end(buf + len);
+ tt_str_op(buf, ==, cenc);
+
+ len = D.decode(cenc, tv->enclen, buf);
+ D.reset();
+ tt_uint_op(len, ==, tv->declen);
+ tt_mem_op(buf, ==, tv->dec, tv->declen);
+ }
+
+ end:;
+}
+
+static void
+test_base64_wrapping(void *)
+{
+ const char in[] =
+ "..........................................."
+ "..........................................."
+ "...........................................";
+ const char out[] =
+ "Li4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4"
+ "uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi"
+ "4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uL"
+ "i4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4u";
+ const char outw[] =
+ "Li4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4u\n"
+ "Li4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4u\n"
+ "Li4uLi4uLi4uLi4uLi4uLi4uLi4u";
+
+ base64::encoder E(false);
+ base64::encoder Ew(true);
+
+ char buf[256];
+ size_t len;
+
+ for (size_t nc = 0; nc <= 129; nc += 3) {
+ memset(buf, 0, sizeof buf);
+
+ len = E.encode(in, nc, buf);
+ len += E.encode_end(buf + len);
+ tt_stn_op(buf, ==, out, len);
+
+ len = Ew.encode(in, nc, buf);
+ len += Ew.encode_end(buf + len);
+ tt_stn_op(buf, ==, outw, len-1);
+ tt_char_op(buf[len-1], ==, '\n');
+ }
+
+ end:;
+}
+
+#define T(name) \
+ { #name, test_base64_##name, 0, 0, 0 }
+
+struct testcase_t base64_tests[] = {
+ T(standard),
+ T(altpunct),
+ T(wrapping),
+ END_OF_TESTCASES
+};
More information about the tor-commits
mailing list