[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