Commit e84e1c97 authored by Nick Mathewson's avatar Nick Mathewson 👁
Browse files

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.
parent e72a5b3c
......@@ -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])
......@@ -721,6 +724,14 @@ if test "x$enable_seccomp" != "xno"; then
AC_SEARCH_LIBS(seccomp_init, [seccomp])
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.
......
......@@ -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);
......
......@@ -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;
}
/* 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
......@@ -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 \
......
......@@ -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
......
......@@ -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"
......
......@@ -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