diff options
-rw-r--r-- | GNUmakefile | 10 | ||||
-rw-r--r-- | hal.h | 3 | ||||
-rw-r--r-- | ks.c | 23 | ||||
-rw-r--r-- | last_gasp_pin_internal.h | 9 | ||||
-rw-r--r-- | rpc_api.c | 31 | ||||
-rw-r--r-- | rpc_misc.c | 68 | ||||
-rw-r--r-- | rpc_pkey.c | 14 | ||||
-rwxr-xr-x | utils/last_gasp_default_pin | 86 |
8 files changed, 203 insertions, 41 deletions
diff --git a/GNUmakefile b/GNUmakefile index 3dcc9da..b74a63f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -189,10 +189,14 @@ ${OBJ}: ${INC} ${LIB}: ${OBJ} ${AR} rcs $@ $^ -asn1.o rsa.o ecdsa.o: asn1_internal.h -ecdsa.o: ecdsa_curves.h -novena-eim.o hal_io_eim.o: novena-eim.h +asn1.o rsa.o ecdsa.o: asn1_internal.h +ecdsa.o: ecdsa_curves.h +novena-eim.o hal_io_eim.o: novena-eim.h slip.o rpc_client_serial.o rpc_server_serial.o: slip_internal.h +ks.o: last_gasp_pin_internal.h + +last_gasp_pin_internal.h: + ./utils/last_gasp_default_pin >$@ test: all export RPC_CLIENT RPC_SERVER @@ -568,6 +568,9 @@ typedef struct { uint32_t handle; } hal_session_handle_t; typedef enum { HAL_USER_NONE, HAL_USER_NORMAL, HAL_USER_SO, HAL_USER_WHEEL } hal_user_t; +extern const size_t hal_rpc_min_pin_length; +extern const size_t hal_rpc_max_pin_length; + extern hal_error_t hal_rpc_set_pin(const hal_client_handle_t client, const hal_user_t user, const char * const newpin, const size_t newpin_len); @@ -38,6 +38,7 @@ #include "hal.h" #include "hal_internal.h" +#include "last_gasp_pin_internal.h" #define KEK_LENGTH (bitsToBytes(256)) @@ -327,6 +328,28 @@ hal_error_t hal_ks_get_pin(const hal_user_t user, default: return HAL_ERROR_BAD_ARGUMENTS; } + /* + * If we were looking for the WHEEL PIN and it appears to be + * completely unset, return the compiled-in last-gasp PIN. This is + * not a great answer, but we need some kind of bootstrapping + * mechanism. Feel free to suggest something better. + * + * We probably need some more general "have we been initialized?" + * state somewhere, and might want to refuse to do things like + * storing keys until we've been initialized and the appropriate + * PINs have been set. + */ + + if (user == HAL_USER_WHEEL && (*pin)->iterations == 0) { + uint8_t u = 0; + for (int i = 0; i < sizeof((*pin)->pin); i++) + u |= (*pin)->pin[i]; + for (int i = 0; i < sizeof((*pin)->salt); i++) + u |= (*pin)->salt[i]; + if (u == 0) + *pin = &hal_last_gasp_pin; + } + return HAL_OK; } diff --git a/last_gasp_pin_internal.h b/last_gasp_pin_internal.h new file mode 100644 index 0000000..13c3078 --- /dev/null +++ b/last_gasp_pin_internal.h @@ -0,0 +1,9 @@ +/* + * Automatically generated by a script, do not edit. + */ + +static const hal_ks_pin_t hal_last_gasp_pin = { + 100000, + {0xc1, 0xdb, 0xbf, 0x89, 0x5b, 0xd4, 0xa5, 0x64, 0xfb, 0xbc, 0x33, 0xcb, 0xf8, 0x5a, 0xb0, 0xfa, 0xa8, 0x13, 0xd8, 0x9e, 0x28, 0xdf, 0x28, 0x15, 0x21, 0x0d, 0x7e, 0x9d, 0x53, 0xd9, 0xfc, 0x32}, + {0xb3, 0xbf, 0x4d, 0xcd, 0xa2, 0x1a, 0x96, 0x63, 0x2b, 0xc4, 0x0c, 0xdb, 0xa1, 0x5d, 0x34, 0xfa} +}; @@ -38,6 +38,25 @@ const hal_hash_handle_t hal_hash_handle_none = {HAL_HANDLE_NONE}; +/* + * PIN lengths. These are somewhat arbitrary, and the current values + * are really placeholders until we figure out something better. + * Minimum length here is almost certainly too short for production + * use, we allow it because most test programs fail if we insist on a + * PIN long enough to have any real security. + */ + +#ifndef HAL_PIN_MINIMUM_LENGTH +#define HAL_PIN_MINIMUM_LENGTH 4 +#endif + +#ifndef HAL_PIN_MAXIMUM_LENGTH +#define HAL_PIN_MAXIMUM_LENGTH 4096 +#endif + +const size_t hal_rpc_min_pin_length = HAL_PIN_MINIMUM_LENGTH; +const size_t hal_rpc_max_pin_length = HAL_PIN_MAXIMUM_LENGTH; + static inline int check_pkey_type(const hal_key_type_t type) { switch (type) { @@ -103,13 +122,14 @@ hal_error_t hal_rpc_get_random(void *buffer, const size_t length) return hal_rpc_misc_dispatch->get_random(buffer, length); } -#warning Perhaps we should be enforcing a minimum PIN length here - hal_error_t hal_rpc_set_pin(const hal_client_handle_t client, const hal_user_t user, const char * const newpin, const size_t newpin_len) { - if (newpin == NULL || newpin_len == 0 || (user != HAL_USER_NORMAL && user != HAL_USER_SO && user != HAL_USER_WHEEL)) + if (newpin == NULL || + newpin_len < hal_rpc_min_pin_length || + newpin_len > hal_rpc_max_pin_length || + (user != HAL_USER_NORMAL && user != HAL_USER_SO && user != HAL_USER_WHEEL)) return HAL_ERROR_BAD_ARGUMENTS; return hal_rpc_misc_dispatch->set_pin(client, user, newpin, newpin_len); } @@ -118,7 +138,10 @@ hal_error_t hal_rpc_login(const hal_client_handle_t client, const hal_user_t user, const char * const pin, const size_t pin_len) { - if (pin == NULL || pin_len == 0 || (user != HAL_USER_NORMAL && user != HAL_USER_SO && user != HAL_USER_WHEEL)) + if (pin == NULL || + pin_len < hal_rpc_min_pin_length || + pin_len > hal_rpc_max_pin_length || + (user != HAL_USER_NORMAL && user != HAL_USER_SO && user != HAL_USER_WHEEL)) return HAL_ERROR_BAD_ARGUMENTS; return hal_rpc_misc_dispatch->login(client, user, pin, pin_len); } @@ -72,8 +72,6 @@ static hal_error_t get_random(void *buffer, const size_t length) * PIN to be changed a second time without toasting the keystore. */ -#warning PIN code not yet fully implemented - typedef struct { hal_client_handle_t handle; hal_user_t logged_in; @@ -123,37 +121,6 @@ static inline client_slot_t *find_handle(const hal_client_handle_t handle) return NULL; } -static hal_error_t set_pin(const hal_client_handle_t client, - const hal_user_t user, - const char * const newpin, const size_t newpin_len) -{ - assert(newpin != NULL && newpin_len != 0); - -#warning Need access control to decide who is allowed to set this PIN -#warning Need length checks (here or in caller) on supplied PIN - - const hal_ks_pin_t *pp; - hal_error_t err; - - if ((err = hal_ks_get_pin(user, &pp)) != HAL_OK) - return err; - - hal_ks_pin_t p = *pp; - - if (p.iterations == 0) - p.iterations = HAL_PIN_DEFAULT_ITERATIONS; - - if ((err = hal_get_random(NULL, p.salt, sizeof(p.salt))) != HAL_OK || - (err = hal_pbkdf2(NULL, hal_hash_sha256, - (const uint8_t *) newpin, newpin_len, - p.salt, sizeof(p.salt), - p.pin, sizeof(p.pin), p.iterations)) != HAL_OK || - (err = hal_ks_set_pin(user, &p)) != HAL_OK) - return err; - - return HAL_OK; -} - static hal_error_t login(const hal_client_handle_t client, const hal_user_t user, const char * const pin, const size_t pin_len) @@ -168,9 +135,10 @@ static hal_error_t login(const hal_client_handle_t client, return err; uint8_t buf[sizeof(p->pin)]; + const uint32_t iterations = p->iterations == 0 ? HAL_PIN_DEFAULT_ITERATIONS : p->iterations; if ((err = hal_pbkdf2(NULL, hal_hash_sha256, (const uint8_t *) pin, pin_len, - p->salt, sizeof(p->salt), buf, sizeof(buf), p->iterations)) != HAL_OK) + p->salt, sizeof(p->salt), buf, sizeof(buf), iterations)) != HAL_OK) return err; unsigned diff = 0; @@ -224,6 +192,38 @@ static hal_error_t logout_all(void) return HAL_OK; } +static hal_error_t set_pin(const hal_client_handle_t client, + const hal_user_t user, + const char * const newpin, const size_t newpin_len) +{ + assert(newpin != NULL && newpin_len >= hal_rpc_min_pin_length && newpin_len <= hal_rpc_max_pin_length); + + if ((user != HAL_USER_NORMAL || is_logged_in(client, HAL_USER_SO) != HAL_OK) && + is_logged_in(client, HAL_USER_WHEEL) != HAL_OK) + return HAL_ERROR_FORBIDDEN; + + const hal_ks_pin_t *pp; + hal_error_t err; + + if ((err = hal_ks_get_pin(user, &pp)) != HAL_OK) + return err; + + hal_ks_pin_t p = *pp; + + if (p.iterations == 0) + p.iterations = HAL_PIN_DEFAULT_ITERATIONS; + + if ((err = hal_get_random(NULL, p.salt, sizeof(p.salt))) != HAL_OK || + (err = hal_pbkdf2(NULL, hal_hash_sha256, + (const uint8_t *) newpin, newpin_len, + p.salt, sizeof(p.salt), + p.pin, sizeof(p.pin), p.iterations)) != HAL_OK || + (err = hal_ks_set_pin(user, &p)) != HAL_OK) + return err; + + return HAL_OK; +} + const hal_rpc_misc_dispatch_t hal_rpc_local_misc_dispatch = { set_pin, login, @@ -123,6 +123,20 @@ static inline pkey_slot_t *find_handle(const hal_pkey_handle_t handle) return NULL; } +#warning Still need access control on pkey objects based on current login state +/* + * This would be simple, except for PKCS #11 non-token objects (CKA_TOKEN = CK_FALSE). + * Need to check detailed PKCS #11 rules, but, from memory, we may be supposed to allow + * access to non-token objects even when not logged in. Maybe. Rules are complex. + * + * I think the libhal translation of this resolves around what we've + * been calling the PROXIMATE flags (which probably ought to be + * renamed to *_NONTOKEN_*, slightly less confusing name). For token + * objects, we insist on being logged in properly; for non-token + * objects, we do whatever silly thing PKCS #11 wants us to do, + * probably defaulting to requiring login if PKCS #11 gives us a choice. + */ + /* * Construct a PKCS #1 DigestInfo object. This requires some (very * basic) ASN.1 encoding, which we perform inline. diff --git a/utils/last_gasp_default_pin b/utils/last_gasp_default_pin new file mode 100755 index 0000000..e0d9839 --- /dev/null +++ b/utils/last_gasp_default_pin @@ -0,0 +1,86 @@ +#!/usr/bin/env python + +""" +Somewhere, the HSM has to have a last-gasp default PIN, even if it's +only the null string, because there has to be **some** way to +initialize the poor thing. Absent a better plan (feel free to +suggest one!), this last-gasp default is compiled in. + +The normal value of this last-gasp PIN is deliberately chosen to be +annoying, so that people will change it, but since the derevation +requires running PBKDF2 and you might want a different default if +you're compiling this for yourself, we provide the script that +generates the default. +""" + +# Author: Rob Austein +# Copyright (c) 2016, NORDUnet A/S +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# - Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# - Neither the name of the NORDUnet nor the names of its contributors may +# be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter +from os import urandom +from Crypto.Protocol.KDF import PBKDF2 +from Crypto.Hash import SHA256, HMAC + +parser = ArgumentParser(description = __doc__, formatter_class = ArgumentDefaultsHelpFormatter) +parser.add_argument("-p", "--pin", + default = "YouReallyNeedToChangeThisPINRightNowWeAreNotKidding", + help = "PIN plaintext before PBKDF2 processing") +parser.add_argument("-i", "--iterations", + type = int, + default = 100000, + help = "PBKDF2 iteration count") +args = parser.parse_args() + +def HMAC_SHA256(pin, salt): + return HMAC.new(pin, salt, SHA256).digest() + +def hexify(value): + return ", ".join("0x%02x" % ord(v) for v in value) + +salt = urandom(16) + +pin = PBKDF2(password = args.pin, + salt = salt, + dkLen = 32, + count = args.iterations, + prf = HMAC_SHA256) + +print '''\ +/* + * Automatically generated by a script, do not edit. + */ + +static const hal_ks_pin_t hal_last_gasp_pin = {{ + {iterations}, + {{{pin}}}, + {{{salt}}} +}};'''.format(iterations = args.iterations, + pin = hexify(pin), + salt = hexify(salt)) |