Browse Source

Add rudimentary support for PEM-encoding, since NSS doesn't do that.

Nick Mathewson 5 years ago
parent
commit
9566ed6fd9

+ 1 - 0
src/lib/encoding/.may_include

@@ -1,5 +1,6 @@
 orconfig.h
 lib/cc/*.h
+lib/ctime/*.h
 lib/encoding/*.h
 lib/intmath/*.h
 lib/log/*.h

+ 2 - 0
src/lib/encoding/include.am

@@ -9,6 +9,7 @@ src_lib_libtor_encoding_a_SOURCES =			\
 	src/lib/encoding/confline.c			\
 	src/lib/encoding/cstring.c			\
 	src/lib/encoding/keyval.c			\
+	src/lib/encoding/pem.c				\
 	src/lib/encoding/time_fmt.c
 
 src_lib_libtor_encoding_testing_a_SOURCES = \
@@ -21,4 +22,5 @@ noinst_HEADERS +=					\
 	src/lib/encoding/confline.h			\
 	src/lib/encoding/cstring.h			\
 	src/lib/encoding/keyval.h			\
+	src/lib/encoding/pem.h				\
 	src/lib/encoding/time_fmt.h

+ 106 - 0
src/lib/encoding/pem.c

@@ -0,0 +1,106 @@
+/* Copyright (c) 2001, Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file pem.c
+ *
+ * \brief Implement a trivial version of PEM encoding, for use with NSS.
+ *
+ * We deliberately do not support any encryption here.
+ **/
+
+#include "orconfig.h"
+
+#include "lib/encoding/pem.h"
+
+#include "lib/ctime/di_ops.h"
+#include "lib/encoding/binascii.h"
+#include "lib/log/util_bug.h"
+#include "lib/malloc/malloc.h"
+#include "lib/string/printf.h"
+#include "lib/string/util_string.h"
+
+#include <string.h>
+
+/**
+ * Return the length of a <b>src_len</b>-byte object when tagged with
+ * <b>objtype</b> and PEM-encoded.  Includes terminating NUL.
+ */
+size_t
+pem_encoded_size(size_t src_len, const char *objtype)
+{
+  return
+    strlen("-----BEGIN -----\n") +
+    strlen("-----END -----\n") +
+    strlen(objtype) * 2 +
+    base64_encode_size(src_len, BASE64_ENCODE_MULTILINE)
+    + 1;
+}
+
+/**
+ * PEM-encode the <b>srclen</b>-byte object at <b>src</b> into the
+ * <b>destlen<\b>-byte buffer at <b>dest</b>, tagging it with <b>objtype</b>.
+ * Return 0 on success and -1 on failure.
+ */
+int
+pem_encode(char *dest, size_t destlen, const uint8_t *src, size_t srclen,
+           const char *objtype)
+{
+  if (tor_snprintf(dest, destlen, "-----BEGIN %s-----\n", objtype) < 0)
+    return -1;
+
+  size_t offset = strlen(dest);
+
+  int n = base64_encode(dest + offset, destlen - offset,
+                        (const char *)src, srclen, BASE64_ENCODE_MULTILINE);
+  if (n < 0)
+    return -1;
+  offset += n;
+  if (BUG(offset > destlen))
+    return -1;
+
+  if (tor_snprintf(dest + offset, destlen - offset,
+                   "-----END %s-----\n", objtype) < 0)
+    return -1;
+
+  tor_assert(strlen(dest) + 1 <= pem_encoded_size(srclen, objtype));
+  return 0;
+}
+
+/**
+ * Given a PEM-encoded block of size <b>srclen</b> in <b>src</b>, if it has
+ * object type <b>objtype</b>, decode it into the <b>destlen</b>-byte buffer
+ * at <b>dest</b>.  Return the number of characters decoded on success, or -1
+ * on failure.
+ */
+int
+pem_decode(uint8_t *dest, size_t destlen, const char *src, size_t srclen,
+           const char *objtype)
+{
+  const char *eos = src + srclen;
+
+  src = eat_whitespace_eos(src, eos);
+
+  char *tag = NULL;
+  tor_asprintf(&tag, "-----BEGIN %s-----\n", objtype);
+  if ((size_t)(eos-src) < strlen(tag) || fast_memneq(src, tag, strlen(tag))) {
+    tor_free(tag);
+    return -1;
+  }
+  src += strlen(tag);
+  tor_free(tag);
+
+  // NOTE lack of trailing \n.  We do not enforce its presence.
+  tor_asprintf(&tag, "\n-----END %s-----", objtype);
+  const char *end_of_base64 = tor_memstr(src, eos-src, tag);
+  tor_free(tag);
+  if (end_of_base64 == NULL)
+    return -1;
+
+  /* Should we actually allow extra stuff at the end? */
+
+  return base64_decode((char*)dest, destlen, src, end_of_base64-src);
+}

+ 26 - 0
src/lib/encoding/pem.h

@@ -0,0 +1,26 @@
+/* Copyright (c) 2001, Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file pem.h
+ *
+ * \brief Header for pem.c
+ **/
+
+#ifndef TOR_PEM_H
+#define TOR_PEM_H
+
+#include "orconfig.h"
+#include <stddef.h>
+#include "lib/cc/torint.h"
+
+size_t pem_encoded_size(size_t src_len, const char *objtype);
+int pem_encode(char *dest, size_t destlen, const uint8_t *src, size_t srclen,
+               const char *objtype);
+int pem_decode(uint8_t *dest, size_t destlen, const char *src, size_t srclen,
+               const char *objtype);
+
+#endif

+ 1 - 0
src/test/include.am

@@ -151,6 +151,7 @@ src_test_test_SOURCES += \
 	src/test/test_oom.c \
 	src/test/test_oos.c \
 	src/test/test_options.c \
+	src/test/test_pem.c \
 	src/test/test_periodic_event.c \
 	src/test/test_policy.c \
 	src/test/test_procmon.c \

+ 1 - 0
src/test/test.c

@@ -867,6 +867,7 @@ struct testgroup_t testgroups[] = {
   { "crypto/", crypto_tests },
   { "crypto/ope/", crypto_ope_tests },
   { "crypto/openssl/", crypto_openssl_tests },
+  { "crypto/pem/", pem_tests },
   { "dir/", dir_tests },
   { "dir_handle_get/", dir_handle_get_tests },
   { "dir/md/", microdesc_tests },

+ 1 - 0
src/test/test.h

@@ -234,6 +234,7 @@ extern struct testcase_t nodelist_tests[];
 extern struct testcase_t oom_tests[];
 extern struct testcase_t oos_tests[];
 extern struct testcase_t options_tests[];
+extern struct testcase_t pem_tests[];
 extern struct testcase_t periodic_event_tests[];
 extern struct testcase_t policy_tests[];
 extern struct testcase_t procmon_tests[];

+ 122 - 0
src/test/test_pem.c

@@ -0,0 +1,122 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+
+#include "lib/encoding/pem.h"
+#include "lib/cc/compat_compiler.h"
+#include "lib/malloc/malloc.h"
+
+#include "test/test.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static const char example_pre[] =
+    "Lest you get the wrong impression, we wombats "
+    "are not in the habit of tunneling madly about, without any supplies "
+    "or even a map."; /* -- Ursula Vernon, _Digger_ */
+static const char expected[] =
+    "-----BEGIN WOMBAT QUOTE-----\n"
+    "TGVzdCB5b3UgZ2V0IHRoZSB3cm9uZyBpbXByZXNzaW9uLCB3ZSB3b21iYXRzIGFy\n"
+    "ZSBub3QgaW4gdGhlIGhhYml0IG9mIHR1bm5lbGluZyBtYWRseSBhYm91dCwgd2l0\n"
+    "aG91dCBhbnkgc3VwcGxpZXMgb3IgZXZlbiBhIG1hcC4=\n"
+    "-----END WOMBAT QUOTE-----\n";
+
+static void
+test_crypto_pem_encode(void *arg)
+{
+  (void)arg;
+
+  char buf[4096];
+
+  int n = (int) pem_encoded_size(strlen(example_pre), "WOMBAT QUOTE");
+
+  int n2 = pem_encode(buf, sizeof(buf),
+                      (const unsigned char *)example_pre, strlen(example_pre),
+                      "WOMBAT QUOTE");
+  tt_int_op(strlen(buf)+1, OP_EQ, n);
+  tt_int_op(n2, OP_EQ, 0);
+  tt_str_op(buf, OP_EQ, expected);
+
+  /* Now make sure it succeeds if the buffer is exactly the length we want. */
+  memset(buf, 0, sizeof(buf));
+  n2 = pem_encode(buf, n, (const unsigned char *)example_pre,
+                      strlen(example_pre), "WOMBAT QUOTE");
+  tt_int_op(n2, OP_EQ, 0);
+  tt_str_op(buf, OP_EQ, expected);
+
+  /* Make sure it fails if the buffer is too short. */
+  memset(buf, 0, sizeof(buf));
+  n2 = pem_encode(buf, n - 1, (const unsigned char *)example_pre,
+                  strlen(example_pre), "WOMBAT QUOTE");
+  tt_int_op(n2, OP_EQ, -1);
+
+ done:
+  ;
+}
+
+static void
+test_crypto_pem_decode(void *arg)
+{
+  (void)arg;
+
+  unsigned char buf[4096];
+
+  /* Try a straightforward decoding. */
+  int n = pem_decode(buf, sizeof(buf),
+                     expected, strlen(expected),
+                     "WOMBAT QUOTE");
+  tt_int_op(n, OP_EQ, strlen(example_pre));
+  tt_mem_op(buf, OP_EQ, example_pre, n);
+
+  /* Succeed if the buffer is exactly the right size. */
+  memset(buf, 0xff, sizeof(buf));
+  n = pem_decode(buf, strlen(example_pre),
+                 expected, strlen(expected),
+                 "WOMBAT QUOTE");
+  tt_int_op(n, OP_EQ, strlen(example_pre));
+  tt_mem_op(buf, OP_EQ, example_pre, n);
+  tt_int_op(buf[n], OP_EQ, 0xff);
+
+  /* Verify that it fails if the buffer is too small. */
+  memset(buf, 0xff, sizeof(buf));
+  n = pem_decode(buf, strlen(example_pre) - 1,
+                 expected, strlen(expected),
+                 "WOMBAT QUOTE");
+  tt_int_op(n, OP_EQ, -1);
+
+  /* Verify that it fails with an incorrect tag. */
+  memset(buf, 0xff, sizeof(buf));
+  n = pem_decode(buf, sizeof(buf),
+                 expected, strlen(expected),
+                 "QUOKKA VOTE");
+  tt_int_op(n, OP_EQ, -1);
+
+  /* Try truncated buffers of different sizes. */
+  size_t i;
+  for (i = 0; i <= strlen(expected); ++i) {
+    char *truncated = tor_memdup(expected, i);
+    n = pem_decode(buf, sizeof(buf),
+                   truncated, i,
+                   "WOMBAT QUOTE");
+    tor_free(truncated);
+    if (i < strlen(expected) - 1) {
+      tt_int_op(n, OP_EQ, -1);
+    } else {
+      tt_int_op(n, OP_EQ, strlen(example_pre));
+    }
+  }
+
+ done:
+  ;
+}
+
+struct testcase_t pem_tests[] = {
+  { "encode", test_crypto_pem_encode, 0, NULL, NULL },
+  { "decode", test_crypto_pem_decode, 0, NULL, NULL },
+  END_OF_TESTCASES
+};