[tor-commits] [tor/master] More generic passphrase hashing code, including scrypt support

nickm at torproject.org nickm at torproject.org
Thu Sep 25 16:06:16 UTC 2014


commit e84e1c97458a7b0f449b6689caa0b3da2853f471
Author: Nick Mathewson <nickm at torproject.org>
Date:   Thu Aug 28 17:38:22 2014 -0400

    More generic passphrase hashing code, including scrypt support
    
    Uses libscrypt when found; otherwise, we don't have scrypt and we
    only support openpgp rfc2440 s2k hashing, or pbkdf2.
    
    Includes documentation and unit tests; coverage around 95%. Remaining
    uncovered code is sanity-checks that shouldn't be reachable fwict.
---
 configure.ac            |   11 ++
 src/common/crypto.h     |    7 -
 src/common/crypto_s2k.c |  405 +++++++++++++++++++++++++++++++++++++++++++++++
 src/common/crypto_s2k.h |   66 ++++++++
 src/common/include.am   |    1 +
 src/or/control.c        |    1 +
 src/or/main.c           |    1 +
 src/test/test_crypto.c  |  177 ++++++++++++++++++++-
 8 files changed, 660 insertions(+), 9 deletions(-)

diff --git a/configure.ac b/configure.ac
index 414c72a..b93683f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -153,6 +153,9 @@ AC_ARG_ENABLE(tool-name-check,
 AC_ARG_ENABLE(seccomp,
      AS_HELP_STRING(--disable-seccomp, do not attempt to use libseccomp))
 
+AC_ARG_ENABLE(libscrypt,
+     AS_HELP_STRING(--disable-libscrypt, do not attempt to use libscrypt))
+
 dnl check for the correct "ar" when cross-compiling
 AN_MAKEVAR([AR], [AC_PROG_AR])
 AN_PROGRAM([ar], [AC_PROG_AR])
@@ -722,6 +725,14 @@ if test "x$enable_seccomp" != "xno"; then
 fi
 
 dnl ============================================================
+dnl Check for libscrypt
+
+if test "x$enable_libscrypt" != "xno"; then
+  AC_CHECK_HEADERS([libscrypt.h])
+  AC_SEARCH_LIBS(libscrypt_scrypt, [scrypt])
+fi
+
+dnl ============================================================
 dnl We need an implementation of curve25519.
 
 dnl set these defaults.
diff --git a/src/common/crypto.h b/src/common/crypto.h
index ba6fe84..39bbdb5 100644
--- a/src/common/crypto.h
+++ b/src/common/crypto.h
@@ -280,13 +280,6 @@ int digest_from_base64(char *digest, const char *d64);
 int digest256_to_base64(char *d64, const char *digest);
 int digest256_from_base64(char *digest, const char *d64);
 
-/** Length of RFC2440-style S2K specifier: the first 8 bytes are a salt, the
- * 9th describes how much iteration to do. */
-#define S2K_RFC2440_SPECIFIER_LEN 9
-void secret_to_key_rfc2440(
-                   char *key_out, size_t key_out_len, const char *secret,
-                   size_t secret_len, const char *s2k_specifier);
-
 /** OpenSSL-based utility functions. */
 void memwipe(void *mem, uint8_t byte, size_t sz);
 
diff --git a/src/common/crypto_s2k.c b/src/common/crypto_s2k.c
index 669ee53..8f08a22 100644
--- a/src/common/crypto_s2k.c
+++ b/src/common/crypto_s2k.c
@@ -7,6 +7,165 @@
 #include "crypto.h"
 #include "util.h"
 #include "compat.h"
+#include "crypto_s2k.h"
+
+#include <openssl/evp.h>
+
+#ifdef HAVE_LIBSCRYPT_H
+#define HAVE_SCRYPT
+#include <libscrypt.h>
+#endif
+
+/* Encoded secrets take the form:
+
+     u8 type;
+     u8 salt_and_parameters[depends on type];
+     u8 key[depends on type];
+
+   As a special case, if the encoded secret is exactly 29 bytes long,
+   type 0 is understood.
+
+   Recognized types are:
+       00 -- RFC2440. salt_and_parameters is 9 bytes. key is 20 bytes.
+                salt_and_parameters is 8 bytes random salt,
+                1 byte iteration info.
+       01 -- PKBDF2_SHA1. salt_and_parameters is 17 bytes. key is 20 bytes.
+                salt_and_parameters is 16 bytes random salt,
+                1 byte iteration info.
+       02 -- SCRYPT_SALSA208_SHA256. salt_and_parameters is 18 bytes. key is
+             32 bytes.
+                salt_and_parameters is 18 bytes random salt, 2 bytes iteration
+                info.
+*/
+
+#define S2K_TYPE_RFC2440 0
+#define S2K_TYPE_PBKDF2  1
+#define S2K_TYPE_SCRYPT  2
+
+#define PBKDF2_SPEC_LEN 17
+#define PBKDF2_KEY_LEN 20
+
+#define SCRYPT_SPEC_LEN 18
+#define SCRYPT_KEY_LEN 32
+
+/** Given an algorithm ID (one of S2K_TYPE_*), return the length of the
+ * specifier part of it, without the prefix type byte. */
+static int
+secret_to_key_spec_len(uint8_t type)
+{
+  switch (type) {
+    case S2K_TYPE_RFC2440:
+      return S2K_RFC2440_SPECIFIER_LEN;
+    case S2K_TYPE_PBKDF2:
+      return PBKDF2_SPEC_LEN;
+    case S2K_TYPE_SCRYPT:
+      return SCRYPT_SPEC_LEN;
+    default:
+      return -1;
+  }
+}
+
+/** Given an algorithm ID (one of S2K_TYPE_*), return the length of the
+ * its preferred output. */
+static int
+secret_to_key_key_len(uint8_t type)
+{
+  switch (type) {
+    case S2K_TYPE_RFC2440:
+      return DIGEST_LEN;
+    case S2K_TYPE_PBKDF2:
+      return DIGEST_LEN;
+    case S2K_TYPE_SCRYPT:
+      return DIGEST256_LEN;
+    default:
+      return -1;
+  }
+}
+
+/** Given a specifier in <b>spec_and_key</b> of length
+ * <b>spec_and_key_len</b>, along with its prefix algorithm ID byte, and along
+ * with a key if <b>key_included</b> is true, check whether the whole
+ * specifier-and-key is of valid length, and return the algorithm type if it
+ * is.  Set *<b>legacy_out</b> to 1 iff this is a legacy password hash or
+ * legacy specifier.  Return an error code on failure.
+ */
+static int
+secret_to_key_get_type(const uint8_t *spec_and_key, size_t spec_and_key_len,
+                       int key_included, int *legacy_out)
+{
+  size_t legacy_len = S2K_RFC2440_SPECIFIER_LEN;
+  uint8_t type;
+  int total_len;
+
+  if (key_included)
+    legacy_len += DIGEST_LEN;
+
+  if (spec_and_key_len == legacy_len) {
+    *legacy_out = 1;
+    return S2K_TYPE_RFC2440;
+  }
+
+  *legacy_out = 0;
+  if (spec_and_key_len == 0)
+    return S2K_BAD_LEN;
+
+  type = spec_and_key[0];
+  total_len = secret_to_key_spec_len(type);
+  if (total_len < 0)
+    return S2K_BAD_ALGORITHM;
+  if (key_included) {
+    int keylen = secret_to_key_key_len(type);
+    if (keylen < 0)
+      return S2K_BAD_ALGORITHM;
+    total_len += keylen;
+  }
+
+  if ((size_t)total_len + 1 == spec_and_key_len)
+    return type;
+  else
+    return S2K_BAD_LEN;
+}
+
+/**
+ * Write a new random s2k specifier of type <b>type</b>, without prefixing
+ * type byte, to <b>spec_out</b>, which must have enough room.  May adjust
+ * parameter choice based on <b>flags</b>.
+ */
+static int
+make_specifier(uint8_t *spec_out, uint8_t type, unsigned flags)
+{
+  int speclen = secret_to_key_spec_len(type);
+  if (speclen < 0)
+      return S2K_BAD_ALGORITHM;
+
+  crypto_rand((char*)spec_out, speclen);
+  switch (type) {
+    case S2K_TYPE_RFC2440:
+      /* Hash 64 k of data. */
+      spec_out[S2K_RFC2440_SPECIFIER_LEN-1] = 96;
+      break;
+    case S2K_TYPE_PBKDF2:
+      /* 131 K iterations */
+      spec_out[PBKDF2_SPEC_LEN-1] = 17;
+      break;
+    case S2K_TYPE_SCRYPT:
+      if (flags & S2K_FLAG_LOW_MEM) {
+        /* N = 1<<12 */
+        spec_out[SCRYPT_SPEC_LEN-2] = 12;
+      } else {
+        /* N = 1<<15 */
+        spec_out[SCRYPT_SPEC_LEN-2] = 15;
+      }
+      /* r = 8; p = 2. */
+      spec_out[SCRYPT_SPEC_LEN-1] = (3u << 4) | (1u << 0);
+      break;
+    default:
+      tor_fragile_assert();
+      return S2K_BAD_ALGORITHM;
+  }
+
+  return speclen;
+}
 
 /** Implement RFC2440-style iterated-salted S2K conversion: convert the
  * <b>secret_len</b>-byte <b>secret</b> into a <b>key_out_len</b> byte
@@ -51,3 +210,249 @@ secret_to_key_rfc2440(char *key_out, size_t key_out_len, const char *secret,
   tor_free(tmp);
   crypto_digest_free(d);
 }
+
+/**
+ * Helper: given a valid specifier without prefix type byte in <b>spec</b>,
+ * whose length must be correct, and given a secret passphrase <b>secret</b>
+ * of length <b>secret_len</b>, compute the key and store it into
+ * <b>key_out</b>, which must have enough room for secret_to_key_key_len(type)
+ * bytes.  Return the number of bytes written on success and an error code
+ * on failure.
+ */
+static int
+secret_to_key_compute_key(uint8_t *key_out, const uint8_t *spec,
+                          const char *secret, size_t secret_len,
+                          int type)
+{
+  int rv;
+
+  switch (type) {
+    case S2K_TYPE_RFC2440:
+      secret_to_key_rfc2440((char*)key_out, DIGEST_LEN, secret, secret_len,
+                            (const char*)spec);
+      return DIGEST_LEN;
+
+    case S2K_TYPE_PBKDF2: {
+      int iters;
+      if (spec[16] > 31)
+        return S2K_BAD_PARAMS;
+      if (secret_len > INT_MAX)
+        return S2K_BAD_LEN;
+      iters = 1 << spec[16];
+      rv = PKCS5_PBKDF2_HMAC_SHA1(secret, (int)secret_len,
+                                 spec, 16,
+                                 1<<spec[16],
+                                 DIGEST_LEN, key_out);
+
+      if (rv < 0)
+        return S2K_FAILED;
+      return DIGEST_LEN;
+    }
+
+    case S2K_TYPE_SCRYPT: {
+#ifdef HAVE_SCRYPT
+      uint64_t N;
+      uint32_t r, p;
+      if (spec[16] > 63)
+        return S2K_BAD_PARAMS;
+      N = ((uint64_t)1) << spec[16];
+      r = 1u << (spec[17] >> 4);
+      p = 1u << (spec[17] & 15);
+      rv = libscrypt_scrypt((const uint8_t*)secret, secret_len,
+                            spec, 16, N, r, p, key_out, 32);
+      if (rv < 0)
+        return S2K_FAILED;
+      return DIGEST256_LEN;
+#else
+      return S2K_NO_SCRYPT_SUPPORT;
+#endif
+    }
+    default:
+      return S2K_BAD_ALGORITHM;
+  }
+}
+
+/**
+ * Given a specifier previously constructed with secret_to_key_make_specifier
+ * in <b>spec</b> of length <b>spec_len</b>, and a secret password in
+ * <b>secret</b> of length <b>secret_len</b>, generate <b>key_out_len</b>
+ * bytes of cryptographic material in <b>key_out</b>.  The native output of
+ * the secret-to-key function will be truncated if key_out_len is short, and
+ * expanded with HKDF if key_out_len is long.  Returns S2K_OKAY on success,
+ * and an error code on failure.
+ */
+int
+secret_to_key_derivekey(uint8_t *key_out, size_t key_out_len,
+                        const uint8_t *spec, size_t spec_len,
+                        const char *secret, size_t secret_len)
+{
+  int legacy_format = 0;
+  int type = secret_to_key_get_type(spec, spec_len, 0, &legacy_format);
+  int keylen, r;
+  uint8_t buf[32];
+
+  if (type < 0)
+    return type;
+#ifndef HAVE_SCRYPT
+  if (type == S2K_TYPE_SCRYPT)
+    return S2K_NO_SCRYPT_SUPPORT;
+ #endif
+
+  if (! legacy_format) {
+    ++spec;
+    --spec_len;
+  }
+
+  keylen = secret_to_key_key_len(type);
+  tor_assert(keylen > 0);
+  tor_assert(keylen <= (int)sizeof(buf));
+
+  r = secret_to_key_compute_key(buf, spec, secret, secret_len, type);
+  if (r < 0)
+    return r;
+
+  tor_assert(r == keylen);
+  if (key_out_len <= sizeof(buf)) {
+    memcpy(key_out, buf, key_out_len);
+    r = S2K_OKAY;
+  } else {
+    r = crypto_expand_key_material_rfc5869_sha256(buf, keylen,
+                                                spec, spec_len,
+                                                (const uint8_t*)"EXPAND", 6,
+                                                key_out, key_out_len);
+    if (r < 0)
+      r = S2K_FAILED;
+    else
+      r = S2K_OKAY;
+  }
+
+  memwipe(buf, 0, sizeof(buf));
+
+  return r;
+}
+
+/**
+ * Construct a new s2k algorithm specifier and salt in <b>buf</b>, according
+ * to the bitwise-or of some S2K_FLAG_* options in <b>flags</b>.  Up to
+ * <b>buf_len</b> bytes of storage may be used in <b>buf</b>.  Return the
+ * number of bytes used on success and an error code on failure.
+ */
+int
+secret_to_key_make_specifier(uint8_t *buf, size_t buf_len, unsigned flags)
+{
+  int rv;
+  int spec_len;
+#ifdef HAVE_SCRYPT
+  uint8_t type = S2K_TYPE_SCRYPT;
+#else
+  uint8_t type = S2K_TYPE_RFC2440;
+#endif
+
+  if (flags & S2K_FLAG_NO_SCRYPT)
+    type = S2K_TYPE_RFC2440;
+  if (flags & S2K_FLAG_USE_PBKDF2)
+    type = S2K_TYPE_PBKDF2;
+
+  spec_len = secret_to_key_spec_len(type);
+
+  if ((int)buf_len < spec_len + 1)
+    return S2K_TRUNCATED;
+
+  buf[0] = type;
+  rv = make_specifier(buf+1, type, flags);
+  if (rv < 0)
+    return rv;
+  else
+    return rv + 1;
+}
+
+/**
+ * Hash a passphrase from <b>secret</b> of length <b>secret_len</b>, according
+ * to the bitwise-or of some S2K_FLAG_* options in <b>flags</b>, and store the
+ * hash along with salt and hashing parameters into <b>buf</b>.  Up to
+ * <b>buf_len</b> bytes of storage may be used in <b>buf</b>.  Set
+ * *<b>len_out</b> to the number of bytes used and return S2K_OKAY on success;
+ * and return an error code on failure.
+ */
+int
+secret_to_key_new(uint8_t *buf,
+                  size_t buf_len,
+                  size_t *len_out,
+                  const char *secret, size_t secret_len,
+                  unsigned flags)
+{
+  int key_len;
+  int spec_len;
+  int type;
+  int rv;
+
+  spec_len = secret_to_key_make_specifier(buf, buf_len, flags);
+
+  if (spec_len < 0)
+    return spec_len;
+
+  type = buf[0];
+  key_len = secret_to_key_key_len(type);
+
+  if ((int)buf_len < key_len + spec_len)
+    return S2K_TRUNCATED;
+
+  rv = secret_to_key_compute_key(buf + spec_len, buf + 1,
+                                 secret, secret_len, type);
+  if (rv < 0)
+    return rv;
+
+  *len_out = spec_len + key_len;
+
+  return S2K_OKAY;
+}
+
+/**
+ * Given a hashed passphrase in <b>spec_and_key</b> of length
+ * <b>spec_and_key_len</b> as generated by secret_to_key_new(), verify whether
+ * it is a hash of the passphrase <b>secret</b> of length <b>secret_len</b>.
+ * Return S2K_OKAY on a match, S2K_BAD_SECRET on a well-formed hash that
+ * doesn't match this secret, and another error code on other errors.
+ */
+int
+secret_to_key_check(const uint8_t *spec_and_key, size_t spec_and_key_len,
+                    const char *secret, size_t secret_len)
+{
+  int is_legacy = 0;
+  int type = secret_to_key_get_type(spec_and_key, spec_and_key_len,
+                                    1, &is_legacy);
+  uint8_t buf[32];
+  int spec_len;
+  int key_len;
+  int rv;
+
+  if (type < 0)
+    return type;
+
+  if (! is_legacy) {
+    spec_and_key++;
+    spec_and_key_len--;
+  }
+
+  spec_len = secret_to_key_spec_len(type);
+  key_len = secret_to_key_key_len(type);
+  tor_assert(spec_len > 0);
+  tor_assert(key_len > 0);
+  tor_assert(key_len <= (int) sizeof(buf));
+  tor_assert((int)spec_and_key_len == spec_len + key_len);
+  rv = secret_to_key_compute_key(buf,
+                                 spec_and_key,
+                                 secret, secret_len, type);
+  if (rv < 0)
+    goto done;
+
+  if (tor_memeq(buf, spec_and_key + spec_len, key_len))
+    rv = S2K_OKAY;
+  else
+    rv = S2K_BAD_SECRET;
+
+ done:
+  memwipe(buf, 0, sizeof(buf));
+  return rv;
+}
+
diff --git a/src/common/crypto_s2k.h b/src/common/crypto_s2k.h
new file mode 100644
index 0000000..abd8014
--- /dev/null
+++ b/src/common/crypto_s2k.h
@@ -0,0 +1,66 @@
+/* Copyright (c) 2001, Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2013, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_CRYPTO_S2K_H_INCLUDED
+#define TOR_CRYPTO_S2K_H_INCLUDED
+
+#include <stdio.h>
+#include "torint.h"
+
+/** Length of RFC2440-style S2K specifier: the first 8 bytes are a salt, the
+ * 9th describes how much iteration to do. */
+#define S2K_RFC2440_SPECIFIER_LEN 9
+void secret_to_key_rfc2440(
+                   char *key_out, size_t key_out_len, const char *secret,
+                   size_t secret_len, const char *s2k_specifier);
+
+/** Flag for secret-to-key function: do not use scrypt. */
+#define S2K_FLAG_NO_SCRYPT  (1u<<0)
+/** Flag for secret-to-key functions: if using a memory-tuned s2k function,
+ * assume that we have limited memory. */
+#define S2K_FLAG_LOW_MEM    (1u<<1)
+/** Flag for secret-to-key functions: force use of pbkdf2.  Without this, we
+ * default to scrypt, then RFC2440. */
+#define S2K_FLAG_USE_PBKDF2 (1u<<2)
+
+/** Maximum possible output length from secret_to_key_new. */
+#define S2K_MAXLEN 64
+
+/** Error code from secret-to-key functions: all is well */
+#define S2K_OKAY 0
+/** Error code from secret-to-key functions: generic failure */
+#define S2K_FAILED -1
+/** Error code from secret-to-key functions: provided secret didn't match */
+#define S2K_BAD_SECRET -2
+/** Error code from secret-to-key functions: didn't recognize the algorithm */
+#define S2K_BAD_ALGORITHM -3
+/** Error code from secret-to-key functions: specifier wasn't valid */
+#define S2K_BAD_PARAMS -4
+/** Error code from secret-to-key functions: compiled without scrypt */
+#define S2K_NO_SCRYPT_SUPPORT -5
+/** Error code from secret-to-key functions: not enough space to write output.
+ */
+#define S2K_TRUNCATED -6
+/** Error code from secret-to-key functions: Wrong length for specifier. */
+#define S2K_BAD_LEN -7
+
+int secret_to_key_new(uint8_t *buf,
+                      size_t buf_len,
+                      size_t *len_out,
+                      const char *secret, size_t secret_len,
+                      unsigned flags);
+
+int secret_to_key_make_specifier(uint8_t *buf, size_t buf_len, unsigned flags);
+
+int secret_to_key_check(const uint8_t *spec_and_key, size_t spec_and_key_len,
+                          const char *secret, size_t secret_len);
+
+int secret_to_key_derivekey(uint8_t *key_out, size_t key_out_len,
+                            const uint8_t *spec, size_t spec_len,
+                            const char *secret, size_t secret_len);
+
+#endif
+
diff --git a/src/common/include.am b/src/common/include.am
index 7a8cc5c..0fb1962 100644
--- a/src/common/include.am
+++ b/src/common/include.am
@@ -111,6 +111,7 @@ COMMONHEADERS = \
   src/common/container.h			\
   src/common/crypto.h				\
   src/common/crypto_curve25519.h		\
+  src/common/crypto_s2k.h			\
   src/common/di_ops.h				\
   src/common/memarea.h				\
   src/common/linux_syscalls.inc			\
diff --git a/src/or/control.c b/src/or/control.c
index 08cdad4..0062e81 100644
--- a/src/or/control.c
+++ b/src/or/control.c
@@ -47,6 +47,7 @@
 #include <sys/resource.h>
 #endif
 
+#include "crypto_s2k.h"
 #include "procmon.h"
 
 /** Yield true iff <b>s</b> is the state of a control_connection_t that has
diff --git a/src/or/main.c b/src/or/main.c
index 4ead8aa..61efc1f 100644
--- a/src/or/main.c
+++ b/src/or/main.c
@@ -28,6 +28,7 @@
 #include "connection_or.h"
 #include "control.h"
 #include "cpuworker.h"
+#include "crypto_s2k.h"
 #include "directory.h"
 #include "dirserv.h"
 #include "dirvote.h"
diff --git a/src/test/test_crypto.c b/src/test/test_crypto.c
index d002475..d36d657 100644
--- a/src/test/test_crypto.c
+++ b/src/test/test_crypto.c
@@ -13,6 +13,7 @@
 #ifdef CURVE25519_ENABLED
 #include "crypto_curve25519.h"
 #endif
+#include "crypto_s2k.h"
 
 extern const char AUTHORITY_SIGNKEY_3[];
 extern const char AUTHORITY_SIGNKEY_A_DIGEST[];
@@ -696,7 +697,7 @@ test_crypto_formats(void)
 
 /** Run unit tests for our secret-to-key passphrase hashing functionality. */
 static void
-test_crypto_s2k(void)
+test_crypto_s2k_rfc2440(void)
 {
   char buf[29];
   char buf2[29];
@@ -727,6 +728,165 @@ test_crypto_s2k(void)
   tor_free(buf3);
 }
 
+static void
+run_s2k_tests(const unsigned flags, const unsigned type,
+              int speclen, const int keylen, int legacy)
+{
+  uint8_t buf[S2K_MAXLEN], buf2[S2K_MAXLEN], buf3[S2K_MAXLEN];
+  int r;
+  size_t sz;
+  const char pw1[] = "You can't come in here unless you say swordfish!";
+  const char pw2[] = "Now, I give you one more guess.";
+
+  r = secret_to_key_new(buf, sizeof(buf), &sz,
+                        pw1, strlen(pw1), flags);
+  tt_int_op(r, ==, S2K_OKAY);
+  tt_int_op(buf[0], ==, type);
+
+  tt_int_op(sz, ==, keylen + speclen);
+
+  if (legacy) {
+    memmove(buf, buf+1, sz-1);
+    --sz;
+    --speclen;
+  }
+
+  tt_int_op(S2K_OKAY, ==,
+            secret_to_key_check(buf, sz, pw1, strlen(pw1)));
+
+  tt_int_op(S2K_BAD_SECRET, ==,
+            secret_to_key_check(buf, sz, pw2, strlen(pw2)));
+
+  /* Move key to buf2, and clear it. */
+  memset(buf3, 0, sizeof(buf3));
+  memcpy(buf2, buf+speclen, keylen);
+  memset(buf+speclen, 0, sz - speclen);
+
+  /* Derivekey should produce the same results. */
+  tt_int_op(S2K_OKAY, ==,
+      secret_to_key_derivekey(buf3, keylen, buf, speclen, pw1, strlen(pw1)));
+
+  tt_mem_op(buf2, ==, buf3, keylen);
+
+  /* Derivekey with a longer output should fill the output. */
+  memset(buf2, 0, sizeof(buf2));
+  tt_int_op(S2K_OKAY, ==,
+   secret_to_key_derivekey(buf2, sizeof(buf2), buf, speclen,
+                           pw1, strlen(pw1)));
+
+  tt_mem_op(buf2, !=, buf3, keylen);
+
+  memset(buf3, 0, sizeof(buf3));
+  tt_int_op(S2K_OKAY, ==,
+            secret_to_key_derivekey(buf3, sizeof(buf3), buf, speclen,
+                                    pw1, strlen(pw1)));
+  tt_mem_op(buf2, ==, buf3, sizeof(buf3));
+  tt_assert(!tor_mem_is_zero((char*)buf2+keylen, sizeof(buf2)-keylen));
+
+ done:
+  ;
+}
+
+static void
+test_crypto_s2k_general(void *arg)
+{
+  const char *which = arg;
+
+  if (!strcmp(which, "scrypt")) {
+    run_s2k_tests(0, 2, 19, 32, 0);
+  } else if (!strcmp(which, "scrypt-low")) {
+    run_s2k_tests(S2K_FLAG_LOW_MEM, 2, 19, 32, 0);
+  } else if (!strcmp(which, "pbkdf2")) {
+    run_s2k_tests(S2K_FLAG_USE_PBKDF2, 1, 18, 20, 0);
+  } else if (!strcmp(which, "rfc2440")) {
+    run_s2k_tests(S2K_FLAG_NO_SCRYPT, 0, 10, 20, 0);
+  } else if (!strcmp(which, "rfc2440-legacy")) {
+    run_s2k_tests(S2K_FLAG_NO_SCRYPT, 0, 10, 20, 1);
+  } else {
+    tt_fail();
+  }
+}
+
+static void
+test_crypto_s2k_errors(void *arg)
+{
+  uint8_t buf[S2K_MAXLEN], buf2[S2K_MAXLEN];
+  size_t sz;
+
+  (void)arg;
+
+  /* Bogus specifiers: simple */
+  tt_int_op(S2K_BAD_LEN, ==,
+            secret_to_key_derivekey(buf, sizeof(buf),
+                                    (const uint8_t*)"", 0, "ABC", 3));
+  tt_int_op(S2K_BAD_ALGORITHM, ==,
+            secret_to_key_derivekey(buf, sizeof(buf),
+                                    (const uint8_t*)"\x10", 1, "ABC", 3));
+  tt_int_op(S2K_BAD_LEN, ==,
+            secret_to_key_derivekey(buf, sizeof(buf),
+                                    (const uint8_t*)"\x01\x02", 2, "ABC", 3));
+
+  tt_int_op(S2K_BAD_LEN, ==,
+            secret_to_key_check((const uint8_t*)"", 0, "ABC", 3));
+  tt_int_op(S2K_BAD_ALGORITHM, ==,
+            secret_to_key_check((const uint8_t*)"\x10", 1, "ABC", 3));
+  tt_int_op(S2K_BAD_LEN, ==,
+            secret_to_key_check((const uint8_t*)"\x01\x02", 2, "ABC", 3));
+
+  /* too long gets "BAD_LEN" too */
+  memset(buf, 0, sizeof(buf));
+  buf[0] = 2;
+  tt_int_op(S2K_BAD_LEN, ==,
+            secret_to_key_derivekey(buf2, sizeof(buf2),
+                                    buf, sizeof(buf), "ABC", 3));
+
+  /* Truncated output */
+#ifdef HAVE_LIBSCRYPT_H
+  tt_int_op(S2K_TRUNCATED, ==, secret_to_key_new(buf, 50, &sz,
+                                                 "ABC", 3, 0));
+  tt_int_op(S2K_TRUNCATED, ==, secret_to_key_new(buf, 50, &sz,
+                                                 "ABC", 3, S2K_FLAG_LOW_MEM));
+#endif
+  tt_int_op(S2K_TRUNCATED, ==, secret_to_key_new(buf, 37, &sz,
+                                              "ABC", 3, S2K_FLAG_USE_PBKDF2));
+  tt_int_op(S2K_TRUNCATED, ==, secret_to_key_new(buf, 29, &sz,
+                                              "ABC", 3, S2K_FLAG_NO_SCRYPT));
+
+#ifdef HAVE_LIBSCRYPT_H
+  tt_int_op(S2K_TRUNCATED, ==, secret_to_key_make_specifier(buf, 18, 0));
+  tt_int_op(S2K_TRUNCATED, ==, secret_to_key_make_specifier(buf, 18,
+                                                 S2K_FLAG_LOW_MEM));
+#endif
+  tt_int_op(S2K_TRUNCATED, ==, secret_to_key_make_specifier(buf, 17,
+                                                 S2K_FLAG_USE_PBKDF2));
+  tt_int_op(S2K_TRUNCATED, ==, secret_to_key_make_specifier(buf, 9,
+                                                 S2K_FLAG_NO_SCRYPT));
+
+  /* Now try using type-specific bogus specifiers. */
+
+  /* It's a bad pbkdf2 buffer if it has an iteration count that would overflow
+   * int32_t. */
+  memset(buf, 0, sizeof(buf));
+  buf[0] = 1; /* pbkdf2 */
+  buf[17] = 100; /* 1<<100 is much bigger than INT32_MAX */
+  tt_int_op(S2K_BAD_PARAMS, ==,
+            secret_to_key_derivekey(buf2, sizeof(buf2),
+                                    buf, 18, "ABC", 3));
+
+#ifdef HAVE_LIBSCRYPT_H
+  /* It's a bad scrypt buffer if N would overflow uint64 */
+  memset(buf, 0, sizeof(buf));
+  buf[0] = 2; /* scrypt */
+  buf[17] = 100; /* 1<<100 is much bigger than UINT64_MAX */
+  tt_int_op(S2K_BAD_PARAMS, ==,
+            secret_to_key_derivekey(buf2, sizeof(buf2),
+                                    buf, 19, "ABC", 3));
+#endif
+
+ done:
+  ;
+}
+
 /** Test AES-CTR encryption and decryption with IV. */
 static void
 test_crypto_aes_iv(void *arg)
@@ -1288,7 +1448,20 @@ struct testcase_t crypto_tests[] = {
   { "pk_fingerprints", test_crypto_pk_fingerprints, TT_FORK, NULL, NULL },
   CRYPTO_LEGACY(digests),
   CRYPTO_LEGACY(dh),
-  CRYPTO_LEGACY(s2k),
+  CRYPTO_LEGACY(s2k_rfc2440),
+#ifdef HAVE_LIBSCRYPT_H
+  { "s2k_scrypt", test_crypto_s2k_general, 0, &pass_data,
+    (void*)"scrypt" },
+  { "s2k_scrypt_low", test_crypto_s2k_general, 0, &pass_data,
+    (void*)"scrypt-low" },
+#endif
+  { "s2k_pbkdf2", test_crypto_s2k_general, 0, &pass_data,
+    (void*)"pbkdf2" },
+  { "s2k_rfc2440_general", test_crypto_s2k_general, 0, &pass_data,
+    (void*)"rfc2440" },
+  { "s2k_rfc2440_legacy", test_crypto_s2k_general, 0, &pass_data,
+    (void*)"rfc2440-legacy" },
+  { "s2k_errors", test_crypto_s2k_errors, 0, NULL, NULL },
   { "aes_iv_AES", test_crypto_aes_iv, TT_FORK, &pass_data, (void*)"aes" },
   { "aes_iv_EVP", test_crypto_aes_iv, TT_FORK, &pass_data, (void*)"evp" },
   CRYPTO_LEGACY(base32_decode),





More information about the tor-commits mailing list