commit 902aa5b3a46da72a3e080d3c1844cc65c8bd169d Author: Zack Weinberg zackw@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 +};
tor-commits@lists.torproject.org