diff options
-rw-r--r-- | Makefile | 20 | ||||
-rw-r--r-- | README.md | 108 | ||||
-rw-r--r-- | core.c | 35 | ||||
-rw-r--r-- | cryptech/libhal.py | 5 | ||||
-rwxr-xr-x | cryptech_muxd | 20 | ||||
-rw-r--r-- | ecdsa.c | 6 | ||||
-rw-r--r-- | hal.h | 17 | ||||
-rw-r--r-- | hal_internal.h | 431 | ||||
-rw-r--r-- | hal_io_fmc.c | 2 | ||||
-rw-r--r-- | ks.c | 909 | ||||
-rw-r--r-- | ks.h | 421 | ||||
-rw-r--r-- | ks_flash.c | 2213 | ||||
-rw-r--r-- | ks_index.c | 367 | ||||
-rw-r--r-- | ks_mmap.c | 180 | ||||
-rw-r--r-- | ks_token.c | 680 | ||||
-rw-r--r-- | ks_volatile.c | 685 | ||||
-rw-r--r-- | last_gasp_pin_internal.h | 6 | ||||
-rw-r--r-- | locks.c | 5 | ||||
-rw-r--r-- | mkmif.c | 52 | ||||
-rw-r--r-- | modexp.c | 1 | ||||
-rw-r--r-- | rpc_client.c | 2 | ||||
-rw-r--r-- | rpc_misc.c | 59 | ||||
-rw-r--r-- | rpc_pkey.c | 195 | ||||
-rw-r--r-- | rpc_server.c | 18 | ||||
-rw-r--r-- | rsa.c | 12 | ||||
-rw-r--r-- | unit-tests.py | 60 | ||||
-rwxr-xr-x | utils/last_gasp_default_pin | 2 |
27 files changed, 2655 insertions, 3856 deletions
@@ -41,20 +41,18 @@ LIB = libhal.a # Error checking on known control options, some of which allow the user entirely too much rope. -USAGE := "usage: ${MAKE} [IO_BUS=eim|i2c|fmc] [RPC_MODE=none|server|client-simple|client-mixed] [KS=mmap|flash] [RPC_TRANSPORT=none|loopback|serial|daemon] [MODEXP_CORE=no|yes] [HASH_CORES=no|yes] [ECDSA_CORES=no|yes]" +USAGE := "usage: ${MAKE} [IO_BUS=eim|i2c|fmc] [RPC_MODE=none|server|client-simple|client-mixed] [RPC_TRANSPORT=none|loopback|serial|daemon] [MODEXP_CORE=no|yes] [HASH_CORES=no|yes] [ECDSA_CORES=no|yes]" IO_BUS ?= none -KS ?= flash RPC_MODE ?= none RPC_TRANSPORT ?= none -MODEXP_CORE ?= no +MODEXP_CORE ?= yes HASH_CORES ?= no ECDSA_CORES ?= yes ifeq (,$(and \ $(filter none eim i2c fmc ,${IO_BUS}),\ $(filter none server client-simple client-mixed ,${RPC_MODE}),\ - $(filter mmap flash ,${KS}),\ $(filter none loopback serial daemon ,${RPC_TRANSPORT}),\ $(filter no yes ,${MODEXP_CORE}),\ $(filter no yes ,${HASH_CORES}),\ @@ -62,7 +60,7 @@ ifeq (,$(and \ $(error ${USAGE}) endif -$(info Building libhal with configuration IO_BUS=${IO_BUS} RPC_MODE=${RPC_MODE} KS=${KS} RPC_TRANSPORT=${RPC_TRANSPORT} MODEXP_CORE=${MODEXP_CORE} HASH_CORES=${HASH_CORES} ECDSA_CORES=${ECDSA_CORES}) +$(info Building libhal with configuration IO_BUS=${IO_BUS} RPC_MODE=${RPC_MODE} RPC_TRANSPORT=${RPC_TRANSPORT} MODEXP_CORE=${MODEXP_CORE} HASH_CORES=${HASH_CORES} ECDSA_CORES=${ECDSA_CORES}) # Whether the RSA code should use the ModExp | ModExpS6 | ModExpA7 core. @@ -138,16 +136,8 @@ endif # In the new world, all keystores are on the server side, and the # volatile keystore is always present, to support things like PKCS #11 # "session" objects. -# -# The mmap keystore hasn't been rewritten for the new API yet. - -KS_OBJ = ks_index.o ks_attribute.o ks_volatile.o -ifeq "${KS}" "mmap" - KS_OBJ += ks_mmap.o -else ifeq "${KS}" "flash" - KS_OBJ += ks_flash.o mkm.o -endif +KS_OBJ = ks.o ks_index.o ks_attribute.o ks_volatile.o ks_token.o mkm.o # RPC_MODE = none | server | client-simple | client-mixed # none: Build without RPC client, use cores directly. @@ -278,7 +268,7 @@ 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_flash.o: last_gasp_pin_internal.h +ks_token.o: last_gasp_pin_internal.h last_gasp_pin_internal.h: ./utils/last_gasp_default_pin >$@ @@ -15,7 +15,7 @@ rather than a higher-level API. Current contents of the library: -* Low-level I/O code (EIM and I2C). +* Low-level I/O code (FMC, EIM, and I2C). * An implementation of AES Key Wrap using the Cryptech AES core. @@ -25,21 +25,44 @@ Current contents of the library: * An implementation of PBKDF2. -* An implementation of RSA using the Cryptech ModExp core. +* An implementation of RSA, optionally using the Cryptech ModExp core. -* An implementation of ECDSA, currently entirely in software. +* An implementation of ECDSA, optionally using the Cryptech ECDSA base + point multiplier cores. + +* An interface to the Master Key Memory interface core on the Cryptech + Alpha platform. + +* A simple keystore implementation with drivers for RAM and flash + storage on the Cryptech Alpha platform. + +* A remote procedure call (RPC) interface. + +* (Just enough) ASN.1 code to support a uniform interface to public + (SubjectPublicKeyInformation (SPKI)) and private (PKCS #8) keys. + +* A simple key backup mechanism, including a Python script to drive it + from the client side. + +* An RPC multiplexer to allow multiple clients (indepedent processes) + to talk to the Cryptech Alpha at once. + +* Client implenetations of the RPC mechanism in both C and Python. * Test code for all of the above. Most of these are fairly well self-contained, although the PBKDF2 -implementation uses the hash-core-based HMAC implementation. +implementation uses the hash-core-based HMAC implementation with +fallback to a software implementation if the cores aren't available. The major exceptions are the RSA and ECDSA implementations, which uses an external bignum implementation (libtfm) to handle a lot of the arithmetic. In the long run, much or all of this may end up being implemented in Verilog, but for the moment all of the RSA math except for modular exponentiation is happening in software, as is all of the -math for ECDSA. +math for ECDSA verification; ECDSA math for key generation and signing +on the P-256 and P-384 curves is done in the ECDSA base point +multiplier cores when those are available. ## RSA ## @@ -47,10 +70,7 @@ The RSA implementation includes a compile-time option to bypass the ModExp core and do everything in software, because the ModExp core is a tad slow at the moment (others are hard at work fixing this). -The RSA implementation includes optional blinding (enabled by default) -and just enough ASN.1 code to read and write private keys; the -expectation is that the latter will be used in combination with the -AES Key Wrap code. +The RSA implementation includes optional blinding (enabled by default). ## ECDSA ## @@ -63,29 +83,27 @@ Do **NOT** enable this in production builds, as ECDSA depends on good random numbers not just for private keys but for individual signatures, and an attacker who knows the random number used for a particular signature can use this to recover the private key. -Arguably, this option should be removed from the code entirely, once -the implementation is stable. +Arguably, this option should be removed from the code entirely. -The ECDSA implementation includes enough ASN.1 to read and write ECDSA -signatures and ECDSA private keys in RFC 5915 format; the expectation -is that the latter will be used in combination with AES Key Wrap. +The ECDSA software implementation attempts to be constant-time, to +reduce the risk of timing channel attacks. The algorithms chosen for +the point arithmetic are a tradeoff between speed and code complexity, +and can probably be improved upon even in software; reimplementing at +least the field arithmetic in hardware would probably also help. +Signing and key generation performance is significantly better when +the ECDSA base point multiplier cores are available. -The ECDSA implementation attempts to be constant-time, to reduce the -risk of timing channel attacks. The algorithms chosen for the point -arithmetic are a tradeoff between speed and code complexity, and can -probably be improved upon even in software; reimplementing at least -the field arithmetic in hardware would probably also help. - -The current point addition and point doubling algorithms come from the -[EFD][]. At least at the moment, we're only interested in ECDSA with -the NIST prime curves, so we use algorithms optimized for a=-3. +The point addition and point doubling algorithms in the current ECDSA +software implementation come from the [EFD][]. At least at the +moment, we're only interested in ECDSA with the NIST prime curves, so +we use algorithms optimized for a=-3. The point multiplication algorithm is a straightforward double-and-add loop, which is not the fastest possible algorithm, but is relatively easy to confirm by inspection as being constant-time within the limits imposed by the NIST curves. Point multiplication could probably be made faster by using a non-adjacent form (NAF) representation for the -scalar, but the author doesn't yet understand that well enough to +scalar, but the author doesn't understand that well enough to implement it as a constant-time algorithm. In theory, changing to a NAF representation could be done without any change to the public API. @@ -94,11 +112,49 @@ point arithmetic is performed in Jacobian projective coordinates, with the coordinates themselves in Montgomery form; final mapping back to affine coordinates also handles the final Montgomery reduction. +## Key backup ## + +The key backup mechanism is a straightforward three-step process, +mediated by a Python script which uses the Python client +implementation of the RPC mechanism. Steps: + +1. Destination HSM (target of key transfer) generates an RSA keypair, + exports the public key (the "key encryption key encryption key" or + "KEKEK"). + +2. Source HSM (origin of the key transfer) wraps keys to be backed up + using AES keywrap with key encryption keys (KEKs) generated by the + TRNG; these key encryption keys are in turn encrypted with RSA + public key (KEKEK) generated by the receipient HSM. + +3. Destination HSM receives wrapped keys, unwraps the KEKs using the + KEKEK then unwraps the wrapped private keys. + +Transfer of the wrapped keys between the two HSMs can be by any +convenient mechanism; for simplicity, `cryptech_backup` script bundles +everything up in a text file using JSON and Base64 encoding. + +## Multiplexer daemon ## + +While the C client library can be built to talk directly to the +Cryptech Alpha board, in most cases it is more convenient to use the +`cryptech_muxd` multiplexer daemon, which is now the default. Client +code talks to `cryptech_muxd` via a `PF_UNIX` socket; `cryptech_muxd` +handles interleaving of messages between multiple clients, and also +manages access to the Alpha's console port. + +The multiplexer requires two external Python libraries, Tornado +(version 4.0 or later) and PySerial (version 3.0 or later). + +In the long run, the RPC mechanism will need to be wrapped in some +kind of secure channel protocol, but we're not there yet. + ## API ## Yeah, we ought to document the API, Real Soon Now, perhaps using -[Doxygen][]. For the moment, see the function prototypes in hal.h and -comments in the code. +[Doxygen][]. For the moment, see the function prototypes in hal.h, +the Python definitions in cryptech.libhal, and and comments in the +code. [EFD]: http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html [Doxygen]: http://www.doxygen.org/ @@ -213,31 +213,39 @@ hal_error_t hal_core_alloc(const char *name, hal_core_t **pcore) if (name == NULL) name = core->info.name; - hal_critical_section_start(); if (core != NULL) { /* if we can reallocate the same core, do it now */ if (!core->busy) { + hal_critical_section_start(); core->busy = 1; hal_critical_section_end(); return HAL_OK; } /* else fall through to search */ } - for (core = hal_core_iterate(NULL); core != NULL; core = core->next) { - if (name_matches(core, name)) { - if (core->busy) { - err = HAL_ERROR_CORE_BUSY; - continue; - } - else { - err = HAL_OK; - *pcore = core; - core->busy = 1; - break; + + while (1) { + hal_critical_section_start(); + for (core = hal_core_iterate(NULL); core != NULL; core = core->next) { + if (name_matches(core, name)) { + if (core->busy) { + err = HAL_ERROR_CORE_BUSY; + continue; + } + else { + err = HAL_OK; + *pcore = core; + core->busy = 1; + break; + } } } + hal_critical_section_end(); + if (err == HAL_ERROR_CORE_BUSY) + hal_task_yield(); + else + break; } - hal_critical_section_end(); return err; } @@ -248,6 +256,7 @@ void hal_core_free(hal_core_t *core) hal_critical_section_start(); core->busy = 0; hal_critical_section_end(); + hal_task_yield(); } } diff --git a/cryptech/libhal.py b/cryptech/libhal.py index 0c6b3f6..39fa826 100644 --- a/cryptech/libhal.py +++ b/cryptech/libhal.py @@ -117,10 +117,7 @@ HALError.define(HAL_ERROR_KEYSTORE_LOST_DATA = "Keystore appears to have HALError.define(HAL_ERROR_BAD_ATTRIBUTE_LENGTH = "Bad attribute length") HALError.define(HAL_ERROR_ATTRIBUTE_NOT_FOUND = "Attribute not found") HALError.define(HAL_ERROR_NO_KEY_INDEX_SLOTS = "No key index slots available") -HALError.define(HAL_ERROR_KSI_INDEX_UUID_MISORDERED = "Key index UUID misordered") -HALError.define(HAL_ERROR_KSI_INDEX_CHUNK_ORPHANED = "Key index chunk orphaned") -HALError.define(HAL_ERROR_KSI_INDEX_CHUNK_MISSING = "Key index chunk missing") -HALError.define(HAL_ERROR_KSI_INDEX_CHUNK_OVERLAPS = "Key index chunk overlaps") +HALError.define(HAL_ERROR_KS_INDEX_UUID_MISORDERED = "Key index UUID misordered") HALError.define(HAL_ERROR_KEYSTORE_WRONG_BLOCK_TYPE = "Wrong block type in keystore") HALError.define(HAL_ERROR_RPC_PROTOCOL_ERROR = "RPC protocol error") HALError.define(HAL_ERROR_NOT_IMPLEMENTED = "Not implemented") diff --git a/cryptech_muxd b/cryptech_muxd index 8e57ef8..d5de227 100755 --- a/cryptech_muxd +++ b/cryptech_muxd @@ -94,8 +94,22 @@ class SerialIOStream(tornado.iostream.BaseIOStream): Implementation of a Tornado IOStream over a PySerial device. """ + # In theory, we want zero (non-blocking mode) for both the read + # and write timeouts here so that PySerial will let Tornado handle + # all the select()/poll()/epoll()/kqueue() fun, delivering maximum + # throughput to all. In practice, this has always worked for the + # author, but another developer reports that on some (not all) + # platforms this fails consistently with Tornado reporting write + # timeout errors, presumably as the result of receiving an IOError + # or OSError exception from PySerial. For reasons we don't really + # understand, setting a PySerial write timeout on the order of + # 50-100 ms "solves" this problem. Again in theory, this will + # result in lower throughput if PySerial spends too much time + # blocking on a single serial device when Tornado could be doing + # something useful elsewhere, but such is life. + def __init__(self, device): - self.serial = serial.Serial(device, 921600, timeout = 0, write_timeout = 0) + self.serial = serial.Serial(device, 921600, timeout = 0, write_timeout = 0.1) self.serial_device = device super(SerialIOStream, self).__init__() @@ -437,8 +451,8 @@ def main(): if __name__ == "__main__": try: tornado.ioloop.IOLoop.current().run_sync(main) - except KeyboardInterrupt: - logger.debug("Normal SIGTERM exit") + except (SystemExit, KeyboardInterrupt): + pass except: logger.exception("Unhandled exception") else: @@ -969,7 +969,7 @@ static int point_is_on_curve(const ec_point_t * const P, * Generate a new ECDSA key. */ -hal_error_t hal_ecdsa_key_gen(const hal_core_t *core, +hal_error_t hal_ecdsa_key_gen(hal_core_t *core, hal_ecdsa_key_t **key_, void *keybuf, const size_t keybuf_len, const hal_curve_name_t curve_) @@ -1608,7 +1608,7 @@ static hal_error_t decode_signature_pkcs11(const ecdsa_curve_t * const curve, * Sign a caller-supplied hash. */ -hal_error_t hal_ecdsa_sign(const hal_core_t *core, +hal_error_t hal_ecdsa_sign(hal_core_t *core, const hal_ecdsa_key_t * const key, const uint8_t * const hash, const size_t hash_len, uint8_t *signature, size_t *signature_len, const size_t signature_max) @@ -1689,7 +1689,7 @@ hal_error_t hal_ecdsa_sign(const hal_core_t *core, * Verify a signature using a caller-supplied hash. */ -hal_error_t hal_ecdsa_verify(const hal_core_t *core, +hal_error_t hal_ecdsa_verify(hal_core_t *core, const hal_ecdsa_key_t * const key, const uint8_t * const hash, const size_t hash_len, const uint8_t * const signature, const size_t signature_len) @@ -157,10 +157,7 @@ DEFINE_HAL_ERROR(HAL_ERROR_BAD_ATTRIBUTE_LENGTH, "Bad attribute length") \ DEFINE_HAL_ERROR(HAL_ERROR_ATTRIBUTE_NOT_FOUND, "Attribute not found") \ DEFINE_HAL_ERROR(HAL_ERROR_NO_KEY_INDEX_SLOTS, "No key index slots available") \ - DEFINE_HAL_ERROR(HAL_ERROR_KSI_INDEX_UUID_MISORDERED, "Key index UUID misordered") \ - DEFINE_HAL_ERROR(HAL_ERROR_KSI_INDEX_CHUNK_ORPHANED, "Key index chunk orphaned") \ - DEFINE_HAL_ERROR(HAL_ERROR_KSI_INDEX_CHUNK_MISSING, "Key index chunk missing") \ - DEFINE_HAL_ERROR(HAL_ERROR_KSI_INDEX_CHUNK_OVERLAPS, "Key index chunk overlaps") \ + DEFINE_HAL_ERROR(HAL_ERROR_KS_INDEX_UUID_MISORDERED, "Key index UUID misordered") \ DEFINE_HAL_ERROR(HAL_ERROR_KEYSTORE_WRONG_BLOCK_TYPE, "Wrong block type in keystore") \ DEFINE_HAL_ERROR(HAL_ERROR_RPC_PROTOCOL_ERROR, "RPC protocol error") \ DEFINE_HAL_ERROR(HAL_ERROR_NOT_IMPLEMENTED, "Not implemented") \ @@ -461,17 +458,17 @@ extern hal_error_t hal_rsa_key_get_public_exponent(const hal_rsa_key_t * const k extern void hal_rsa_key_clear(hal_rsa_key_t *key); -extern hal_error_t hal_rsa_encrypt(const hal_core_t *core, +extern hal_error_t hal_rsa_encrypt(hal_core_t *core, const hal_rsa_key_t * const key, const uint8_t * const input, const size_t input_len, uint8_t * output, const size_t output_len); -extern hal_error_t hal_rsa_decrypt(const hal_core_t *core, +extern hal_error_t hal_rsa_decrypt(hal_core_t *core, const hal_rsa_key_t * const key, const uint8_t * const input, const size_t input_len, uint8_t * output, const size_t output_len); -extern hal_error_t hal_rsa_key_gen(const hal_core_t *core, +extern hal_error_t hal_rsa_key_gen(hal_core_t *core, hal_rsa_key_t **key, void *keybuf, const size_t keybuf_len, const unsigned key_length, @@ -534,7 +531,7 @@ extern hal_error_t hal_ecdsa_key_get_public(const hal_ecdsa_key_t * const key, extern void hal_ecdsa_key_clear(hal_ecdsa_key_t *key); -extern hal_error_t hal_ecdsa_key_gen(const hal_core_t *core, +extern hal_error_t hal_ecdsa_key_gen(hal_core_t *core, hal_ecdsa_key_t **key, void *keybuf, const size_t keybuf_len, const hal_curve_name_t curve); @@ -567,12 +564,12 @@ extern hal_error_t hal_ecdsa_key_from_ecpoint(hal_ecdsa_key_t **key, const uint8_t * const der, const size_t der_len, const hal_curve_name_t curve); -extern hal_error_t hal_ecdsa_sign(const hal_core_t *core, +extern hal_error_t hal_ecdsa_sign(hal_core_t *core, const hal_ecdsa_key_t * const key, const uint8_t * const hash, const size_t hash_len, uint8_t *signature, size_t *signature_len, const size_t signature_max); -extern hal_error_t hal_ecdsa_verify(const hal_core_t *core, +extern hal_error_t hal_ecdsa_verify(hal_core_t *core, const hal_ecdsa_key_t * const key, const uint8_t * const hash, const size_t hash_len, const uint8_t * const signature, const size_t signature_len); diff --git a/hal_internal.h b/hal_internal.h index 13c79e9..2486fd2 100644 --- a/hal_internal.h +++ b/hal_internal.h @@ -97,11 +97,17 @@ extern void hal_critical_section_start(void); extern void hal_critical_section_end(void); extern void hal_ks_lock(void); extern void hal_ks_unlock(void); +extern void hal_task_yield(void); /* - * Logging. + * Thread sleep. Currently used only for bad-PIN delays. */ +extern void hal_sleep(const unsigned seconds); + +/* + * Logging. + */ typedef enum { HAL_LOG_DEBUG, HAL_LOG_INFO, HAL_LOG_WARN, HAL_LOG_ERROR, HAL_LOG_SILENT @@ -391,8 +397,6 @@ extern hal_error_t hal_get_pin(const hal_user_t user, extern hal_error_t hal_set_pin(const hal_user_t user, const hal_ks_pin_t * const pin); -extern void hal_ks_init_read_only_pins_only(void); - /* * Master key memory (MKM) and key-encryption-key (KEK). * @@ -451,9 +455,9 @@ extern hal_error_t hal_pkey_logout(const hal_client_handle_t client); */ typedef struct { - hal_client_handle_t client_handle; - hal_session_handle_t session_handle; - hal_pkey_handle_t pkey_handle; + hal_client_handle_t client; + hal_session_handle_t session; + hal_pkey_handle_t pkey; hal_key_type_t type; hal_curve_name_t curve; hal_key_flags_t flags; @@ -473,392 +477,59 @@ typedef struct { */ } hal_pkey_slot_t; -typedef struct hal_ks_driver hal_ks_driver_t; +/* + * Keystore is an opaque type, we just pass pointers. + */ typedef struct hal_ks hal_ks_t; -struct hal_ks_driver { - - hal_error_t (*init)(const hal_ks_driver_t * const driver, - const int alloc); +extern hal_ks_t * const hal_ks_token; +extern hal_ks_t * const hal_ks_volatile; - hal_error_t (*shutdown)(const hal_ks_driver_t * const driver); +extern hal_error_t hal_ks_init(hal_ks_t *ks, + const int alloc); - hal_error_t (*open)(const hal_ks_driver_t * const driver, - hal_ks_t **ks); - - hal_error_t (*close)(hal_ks_t *ks); - - hal_error_t (*store)(hal_ks_t *ks, - hal_pkey_slot_t *slot, - const uint8_t * const der, const size_t der_len); - - hal_error_t (*fetch)(hal_ks_t *ks, - hal_pkey_slot_t *slot, - uint8_t *der, size_t *der_len, const size_t der_max); - - hal_error_t (*delete)(hal_ks_t *ks, - hal_pkey_slot_t *slot); - - hal_error_t (*match)(hal_ks_t *ks, - const hal_client_handle_t client, - const hal_session_handle_t session, - const hal_key_type_t type, - const hal_curve_name_t curve, - const hal_key_flags_t mask, - const hal_key_flags_t flags, - const hal_pkey_attribute_t *attributes, - const unsigned attributes_len, - hal_uuid_t *result, - unsigned *result_len, - const unsigned result_max, - const hal_uuid_t * const previous_uuid); +extern void hal_ks_init_read_only_pins_only(void); - hal_error_t (*set_attributes)(hal_ks_t *ks, +extern hal_error_t hal_ks_store(hal_ks_t *ks, hal_pkey_slot_t *slot, - const hal_pkey_attribute_t *attributes, - const unsigned attributes_len); + const uint8_t * const der, const size_t der_len); - hal_error_t (*get_attributes)(hal_ks_t *ks, +extern hal_error_t hal_ks_fetch(hal_ks_t *ks, hal_pkey_slot_t *slot, - hal_pkey_attribute_t *attributes, + uint8_t *der, size_t *der_len, const size_t der_max); + +extern hal_error_t hal_ks_delete(hal_ks_t *ks, + hal_pkey_slot_t *slot); + +extern hal_error_t hal_ks_match(hal_ks_t *ks, + const hal_client_handle_t client, + const hal_session_handle_t session, + const hal_key_type_t type, + const hal_curve_name_t curve, + const hal_key_flags_t mask, + const hal_key_flags_t flags, + const hal_pkey_attribute_t *attributes, const unsigned attributes_len, - uint8_t *attributes_buffer, - const size_t attributes_buffer_len); - - hal_error_t (*logout)(hal_ks_t *ks, - const hal_client_handle_t client); - -}; - - -struct hal_ks { - const hal_ks_driver_t *driver; - /* - * Any other common portions of hal_ks_t go here. - */ - - /* - * Driver-specific stuff is handled by a form of subclassing: - * driver module embeds this structure at the head of whatever - * else it needs, and performs casts as needed. - */ -}; - -extern const hal_ks_driver_t - hal_ks_volatile_driver[1], - hal_ks_token_driver[1]; - -static inline hal_error_t hal_ks_init(const hal_ks_driver_t * const driver, - const int alloc) -{ - if (driver == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - if (driver->init == NULL) - return HAL_ERROR_NOT_IMPLEMENTED; - - return driver->init(driver, alloc); -} - -static inline hal_error_t hal_ks_shutdown(const hal_ks_driver_t * const driver) -{ - if (driver == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - if (driver->shutdown == NULL) - return HAL_ERROR_NOT_IMPLEMENTED; - - return driver->shutdown(driver); -} - -static inline hal_error_t hal_ks_open(const hal_ks_driver_t * const driver, - hal_ks_t **ks) -{ - if (driver == NULL || ks == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - if (driver->open == NULL) - return HAL_ERROR_NOT_IMPLEMENTED; - - return driver->open(driver, ks); -} - -static inline hal_error_t hal_ks_close(hal_ks_t *ks) -{ - if (ks == NULL || ks->driver == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - if (ks->driver->close == NULL) - return HAL_ERROR_NOT_IMPLEMENTED; - - return ks->driver->close(ks); -} - -static inline hal_error_t hal_ks_store(hal_ks_t *ks, - hal_pkey_slot_t *slot, - const uint8_t * const der, const size_t der_len) -{ - if (ks == NULL || ks->driver == NULL || slot == NULL || der == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - if (ks->driver->store == NULL) - return HAL_ERROR_NOT_IMPLEMENTED; - - return ks->driver->store(ks, slot, der, der_len); -} - -static inline hal_error_t hal_ks_fetch(hal_ks_t *ks, - hal_pkey_slot_t *slot, - uint8_t *der, size_t *der_len, const size_t der_max) -{ - if (ks == NULL || ks->driver == NULL || slot == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - if (ks->driver->fetch == NULL) - return HAL_ERROR_NOT_IMPLEMENTED; - - return ks->driver->fetch(ks, slot, der, der_len, der_max); -} - -static inline hal_error_t hal_ks_delete(hal_ks_t *ks, - hal_pkey_slot_t *slot) -{ - if (ks == NULL || ks->driver == NULL || slot == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - if (ks->driver->delete == NULL) - return HAL_ERROR_NOT_IMPLEMENTED; - - return ks->driver->delete(ks, slot); -} - -static inline hal_error_t hal_ks_match(hal_ks_t *ks, - const hal_client_handle_t client, - const hal_session_handle_t session, - const hal_key_type_t type, - const hal_curve_name_t curve, - const hal_key_flags_t mask, - const hal_key_flags_t flags, - const hal_pkey_attribute_t *attributes, - const unsigned attributes_len, - hal_uuid_t *result, - unsigned *result_len, - const unsigned result_max, - const hal_uuid_t * const previous_uuid) -{ - if (ks == NULL || ks->driver == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - if (ks->driver->match == NULL) - return HAL_ERROR_NOT_IMPLEMENTED; - - return ks->driver->match(ks, client, session, type, curve, mask, flags, attributes, attributes_len, - result, result_len, result_max, previous_uuid); -} - -static inline hal_error_t hal_ks_set_attributes(hal_ks_t *ks, - hal_pkey_slot_t *slot, - const hal_pkey_attribute_t *attributes, - const unsigned attributes_len) -{ - if (ks == NULL || ks->driver == NULL || slot == NULL || - attributes == NULL || attributes_len == 0) - return HAL_ERROR_BAD_ARGUMENTS; - - if (ks->driver->set_attributes == NULL) - return HAL_ERROR_NOT_IMPLEMENTED; - - return ks->driver->set_attributes(ks, slot, attributes, attributes_len); -} - -static inline hal_error_t hal_ks_get_attributes(hal_ks_t *ks, - hal_pkey_slot_t *slot, - hal_pkey_attribute_t *attributes, - const unsigned attributes_len, - uint8_t *attributes_buffer, - const size_t attributes_buffer_len) -{ - if (ks == NULL || ks->driver == NULL || slot == NULL || - attributes == NULL || attributes_len == 0) - return HAL_ERROR_BAD_ARGUMENTS; - - if (ks->driver->get_attributes == NULL) - return HAL_ERROR_NOT_IMPLEMENTED; - - return ks->driver->get_attributes(ks, slot, attributes, attributes_len, - attributes_buffer, attributes_buffer_len); -} - -static inline hal_error_t hal_ks_logout(hal_ks_t *ks, - const hal_client_handle_t client) -{ - if (ks == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - if (ks->driver->logout == NULL || client.handle == HAL_HANDLE_NONE) - return HAL_OK; - - return ks->driver->logout(ks, client); -} - -/* - * Keystore index. This is intended to be usable by both memory-based - * (in-memory, mmap(), ...) keystores and keystores based on raw flash. - * Some of the features aren't really necessary for memory-based keystores, - * but should be harmless. - * - * General approach is multiple arrays, all but one of which are - * indexed by "block" numbers, where a block number might be a slot in - * yet another static array, the number of a flash sub-sector, or - * whatever is the appropriate unit for holding one keystore record. - * - * The index array contains nothing but flags and block numbers, and - * is deliberately a small data structure so that moving data around - * within it is relatively cheap. - * - * The index array is divided into two portions: the index proper, and - * the free queue. The index proper is ordered according to the names - * (UUIDs) of the corresponding blocks; the free queue is a FIFO, to - * support a simplistic form of wear leveling in flash-based keystores. - * - * Key names are kept in a separate array, indexed by block number. - * Key names here are a composite of the key's UUID and a "chunk" - * number; the latter allows storage of keys whose total size exceeds - * one block (whatever a block is). For the moment we keep the UUID - * and the chunk number in a single array, which may provide (very) - * slightly better performance due to reference locality in SDRAM, but - * this may change if we need to reclaim the space wasted by structure - * size rounding. - * - * The all-zeros UUID, which (by definition) cannot be a valid key - * UUID, is reserved for the (non-key) block used to stash PINs and - * other small data which aren't really part of the keystore proper - * but are kept with it because the keystore is the flash we have. - * - * Note that this API deliberately says nothing about how the keys - * themselves are stored, that's up to the keystore driver. This - * portion of the API is only concerned with allocation and naming. - */ - -typedef struct { - hal_uuid_t name; /* Key name */ - uint8_t chunk; /* Key chunk number */ -} hal_ks_name_t; - -typedef struct { - unsigned size; /* Array length */ - unsigned used; /* How many blocks are in use */ - uint16_t *index; /* Index/freelist array */ - hal_ks_name_t *names; /* Keyname array */ -} hal_ks_index_t; - -/* - * Finish setting up key index. Caller must populate index, free - * list, and name array. - * - * This function checks a few things then sorts the index proper. - * - * If driver cares about wear leveling, driver must supply the free - * list in the desired order (FIFO); figuring out what that order is a - * problem for the keystore driver. - */ -extern hal_error_t hal_ks_index_setup(hal_ks_index_t *ksi); - -/* - * Find a key block, return its block number. - */ -extern hal_error_t hal_ks_index_find(hal_ks_index_t *ksi, - const hal_uuid_t * const name, - const unsigned chunk, - unsigned *blockno, - int *hint); - -/* - * Find all the blocks in a key, return the block numbers. - */ -extern hal_error_t hal_ks_index_find_range(hal_ks_index_t *ksi, - const hal_uuid_t * const name, - const unsigned max_blocks, - unsigned *n_blocks, - unsigned *blocknos, - int *hint, - const int strict); - -/* - * Add a key block, return its block number. - */ -extern hal_error_t hal_ks_index_add(hal_ks_index_t *ksi, - const hal_uuid_t * const name, - const unsigned chunk, - unsigned *blockno, - int *hint); - -/* - * Delete a key block, returns its block number (driver may need it). - */ -extern hal_error_t hal_ks_index_delete(hal_ks_index_t *ksi, - const hal_uuid_t * const name, - const unsigned chunk, - unsigned *blockno, - int *hint); - -/* - * Delete all of blocks in a key, returning the block numbers. - */ - -extern hal_error_t hal_ks_index_delete_range(hal_ks_index_t *ksi, - const hal_uuid_t * const name, - const unsigned max_blocks, - unsigned *n_blocks, - unsigned *blocknos, - int *hint); - -/* - * Replace a key block with a new one, return new block number. - * Name of block does not change. This is an optimization of - * a delete immediately followed by an add for the same name. - */ - -extern hal_error_t hal_ks_index_replace(hal_ks_index_t *ksi, - const hal_uuid_t * const name, - const unsigned chunk, - unsigned *blockno, - int *hint); - -/* - * Check the index for errors. At least for the moment, this just - * reports errors, it doesn't attempt to fix them. - */ - -extern hal_error_t hal_ks_index_fsck(hal_ks_index_t *ksi); - -/* - * Keystore attribute utilities, for use by keystore drivers. - */ - -extern const size_t hal_ks_attribute_header_size; - -extern hal_error_t hal_ks_attribute_scan(const uint8_t * const bytes, - const size_t bytes_len, + hal_uuid_t *result, + unsigned *result_len, + const unsigned result_max, + const hal_uuid_t * const previous_uuid); + +extern hal_error_t hal_ks_set_attributes(hal_ks_t *ks, + hal_pkey_slot_t *slot, + const hal_pkey_attribute_t *attributes, + const unsigned attributes_len); + +extern hal_error_t hal_ks_get_attributes(hal_ks_t *ks, + hal_pkey_slot_t *slot, hal_pkey_attribute_t *attributes, const unsigned attributes_len, - size_t *total_len); - -extern hal_error_t hal_ks_attribute_delete(uint8_t *bytes, - const size_t bytes_len, - hal_pkey_attribute_t *attributes, - unsigned *attributes_len, - size_t *total_len, - const uint32_t type); - -extern hal_error_t hal_ks_attribute_insert(uint8_t *bytes, const size_t bytes_len, - hal_pkey_attribute_t *attributes, - unsigned *attributes_len, - size_t *total_len, - const uint32_t type, - const uint8_t * const value, - const size_t value_len); + uint8_t *attributes_buffer, + const size_t attributes_buffer_len); + +extern hal_error_t hal_ks_logout(hal_ks_t *ks, + const hal_client_handle_t client); /* * RPC lowest-level send and receive routines. These are blocking, and diff --git a/hal_io_fmc.c b/hal_io_fmc.c index 7aa4b19..76d6883 100644 --- a/hal_io_fmc.c +++ b/hal_io_fmc.c @@ -159,6 +159,8 @@ hal_error_t hal_io_wait(const hal_core_t *core, uint8_t status, int *count) if (count && (*count > 0) && (i >= *count)) return HAL_ERROR_IO_TIMEOUT; + hal_task_yield(); + if ((err = hal_io_read(core, ADDR_STATUS, buf, sizeof(buf))) != HAL_OK) return err; @@ -0,0 +1,909 @@ +/* + * ks.c + * ---- + * Keystore, generic parts anyway. This is internal within libhal. + * + * Copyright (c) 2015-2017, 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. + */ + +#include <stddef.h> +#include <string.h> + +#include "hal.h" +#include "hal_internal.h" +#include "ks.h" + +/* + * PIN block gets the all-zeros UUID, which will never be returned by + * the UUID generation code (by definition -- it's not a version 4 UUID). + */ + +const hal_uuid_t hal_ks_pin_uuid = {{0}}; + +/* + * Pick unused or least-recently-used slot in our in-memory cache. + * + * Updating lru values is caller's problem: if caller is using a cache + * slot as a temporary buffer and there's no point in caching the + * result, leave the lru values alone and the right thing will happen. + */ + +hal_ks_block_t *hal_ks_cache_pick_lru(hal_ks_t *ks) +{ + uint32_t best_delta = 0; + int best_index = 0; + + for (int i = 0; i < ks->cache_size; i++) { + + if (ks->cache[i].blockno == ~0) + return &ks->cache[i].block; + + const unsigned delta = ks->cache_lru - ks->cache[i].lru; + if (delta > best_delta) { + best_delta = delta; + best_index = i; + } + + } + + ks->cache[best_index].blockno = ~0; + return &ks->cache[best_index].block; +} + +/* + * Find a block in our in-memory cache; return block or NULL if not present. + */ + +hal_ks_block_t *hal_ks_cache_find_block(const hal_ks_t * const ks, const unsigned blockno) +{ + for (int i = 0; i < ks->cache_size; i++) + if (ks->cache[i].blockno == blockno) + return &ks->cache[i].block; + return NULL; +} + +/* + * Mark a block in our in-memory cache as being in current use. + */ + +void hal_ks_cache_mark_used(hal_ks_t *ks, const hal_ks_block_t * const block, const unsigned blockno) +{ + for (int i = 0; i < ks->cache_size; i++) { + if (&ks->cache[i].block == block) { + ks->cache[i].blockno = blockno; + ks->cache[i].lru = ++ks->cache_lru; + return; + } + } +} + +/* + * Release a block from the in-memory cache. + */ + +void hal_ks_cache_release(hal_ks_t *ks, const hal_ks_block_t * const block) +{ + if (block != NULL) + hal_ks_cache_mark_used(ks, block, ~0); +} + +/* + * Generate CRC-32 for a block. + * + * This function needs to understand the structure of the + * hal_ks_block_header_t, so that it can skip over fields that + * shouldn't be included in the CRC. + */ + +hal_crc32_t hal_ks_block_calculate_crc(const hal_ks_block_t * const block) +{ + hal_crc32_t crc = hal_crc32_init(); + + if (block != NULL) { + + crc = hal_crc32_update(crc, &block->header.block_type, + sizeof(block->header.block_type)); + + crc = hal_crc32_update(crc, + block->bytes + sizeof(hal_ks_block_header_t), + sizeof(*block) - sizeof(hal_ks_block_header_t)); + } + + return hal_crc32_finalize(crc); +} + +/* + * Read a block using the cache. Marking the block as used is left + * for the caller, so we can avoid blowing out the cache when we + * perform a hal_ks_match() operation. + */ + +hal_error_t hal_ks_block_read_cached(hal_ks_t *ks, const unsigned blockno, hal_ks_block_t **block) +{ + if (block == NULL) + return HAL_ERROR_IMPOSSIBLE; + + if ((*block = hal_ks_cache_find_block(ks, blockno)) != NULL) + return HAL_OK; + + if ((*block = hal_ks_cache_pick_lru(ks)) == NULL) + return HAL_ERROR_IMPOSSIBLE; + + return hal_ks_block_read(ks, blockno, *block); +} + +/* + * Update one block, including zombie jamboree. + */ + +hal_error_t hal_ks_block_update(hal_ks_t *ks, + const unsigned b1, + hal_ks_block_t *block, + const hal_uuid_t * const uuid, + int *hint) +{ + if (block == NULL) + return HAL_ERROR_IMPOSSIBLE; + + if (ks->used == ks->size) + return HAL_ERROR_NO_KEY_INDEX_SLOTS; + + hal_ks_cache_release(ks, block); + + hal_error_t err; + unsigned b2; + + if ((err = hal_ks_block_deprecate(ks, b1)) != HAL_OK || + (err = hal_ks_index_replace(ks, uuid, &b2, hint)) != HAL_OK || + (err = hal_ks_block_write(ks, b2, block)) != HAL_OK || + (err = hal_ks_block_copy_owner(ks, b1, b2)) != HAL_OK || + (err = hal_ks_block_zero(ks, b1)) != HAL_OK) + return err; + + hal_ks_cache_mark_used(ks, block, b2); + + /* + * Erase the first block in the free list. In case of restart, this + * puts the block back at the head of the free list. + */ + + return hal_ks_block_erase_maybe(ks, ks->index[ks->used]); +} + +/* + * Initialize keystore. This includes various tricky bits, some of + * which attempt to preserve the free list ordering across reboots, to + * improve our simplistic attempt at wear leveling, others attempt to + * recover from unclean shutdown. + */ + +hal_error_t hal_ks_init(hal_ks_t *ks, const int alloc) +{ + return + ks == NULL || ks->driver == NULL ? HAL_ERROR_BAD_ARGUMENTS : + ks->driver->init == NULL ? HAL_ERROR_NOT_IMPLEMENTED : + ks->driver->init(ks, alloc); +} + +static inline void *gnaw(uint8_t **mem, size_t *len, const size_t size) +{ + if (mem == NULL || *mem == NULL || len == NULL || size > *len) + return NULL; + void *ret = *mem; + *mem += size; + *len -= size; + return ret; +} + +hal_error_t hal_ks_alloc_common(hal_ks_t *ks, + const unsigned ks_blocks, + const unsigned cache_blocks, + void **extra, + const size_t extra_len) +{ + /* + * We allocate a single big chunk of memory to make it atomic. We + * need all three of our blocks, so this way either all succeed or + * all fail; we allow our caller to piggyback its own memory needs + * (if any) on ours for the same reason. + */ + + size_t len = (sizeof(*ks->index) * ks_blocks + + sizeof(*ks->names) * ks_blocks + + sizeof(*ks->cache) * cache_blocks + + extra_len); + + uint8_t *mem = hal_allocate_static_memory(len); + + if (mem == NULL) + return HAL_ERROR_ALLOCATION_FAILURE; + + memset(((uint8_t *) ks) + sizeof(ks->driver), 0, + sizeof(hal_ks_t) - sizeof(ks->driver)); + memset(mem, 0, len); + + ks->index = gnaw(&mem, &len, sizeof(*ks->index) * ks_blocks); + ks->names = gnaw(&mem, &len, sizeof(*ks->names) * ks_blocks); + ks->cache = gnaw(&mem, &len, sizeof(*ks->cache) * cache_blocks); + + ks->size = ks_blocks; + ks->cache_size = cache_blocks; + + if (extra != NULL) + *extra = mem; + + return HAL_OK; +} + +hal_error_t hal_ks_init_common(hal_ks_t *ks) +{ + if (ks->index == NULL || ks->names == NULL || ks->cache == NULL) + return HAL_ERROR_IMPOSSIBLE; + + ks->used = 0; + + for (int i = 0; i < ks->cache_size; i++) + ks->cache[i].blockno = ~0; + + /* + * Scan existing content of keystore to figure out what we've got. + * This gets a bit involved due to the need to recover from things + * like power failures at inconvenient times. + */ + + hal_ks_block_type_t block_types[ks->size]; + hal_ks_block_status_t block_status[ks->size]; + hal_ks_block_t *block = hal_ks_cache_pick_lru(ks); + int first_erased = -1; + hal_error_t err; + uint16_t n = 0; + + if (block == NULL) + return HAL_ERROR_IMPOSSIBLE; + + for (int i = 0; i < ks->size; i++) { + + /* + * Read one block. If the CRC is bad or the block type is + * unknown, it's old data we don't understand, something we were + * writing when we crashed, or bad flash; in any of these cases, + * we want the block to end up near the end of the free list. + */ + + err = hal_ks_block_read(ks, i, block); + + if (err == HAL_ERROR_KEYSTORE_BAD_CRC || err == HAL_ERROR_KEYSTORE_BAD_BLOCK_TYPE) + block_types[i] = HAL_KS_BLOCK_TYPE_UNKNOWN; + + else if (err == HAL_OK) + block_types[i] = hal_ks_block_get_type(block); + + else + return err; + + switch (block_types[i]) { + case HAL_KS_BLOCK_TYPE_KEY: + case HAL_KS_BLOCK_TYPE_PIN: + block_status[i] = hal_ks_block_get_status(block); + break; + default: + block_status[i] = HAL_KS_BLOCK_STATUS_UNKNOWN; + } + + /* + * First erased block we see is head of the free list. + */ + + if (block_types[i] == HAL_KS_BLOCK_TYPE_ERASED && first_erased < 0) + first_erased = i; + + /* + * If it's a valid data block, include it in the index. We remove + * tombstones (if any) below, for now it's easiest to include them + * in the index, so we can look them up by name if we must. + */ + + const hal_uuid_t *uuid = NULL; + + switch (block_types[i]) { + case HAL_KS_BLOCK_TYPE_KEY: uuid = &block->key.name; break; + case HAL_KS_BLOCK_TYPE_PIN: uuid = &hal_ks_pin_uuid; break; + default: /* Keep GCC happy */ break; + } + + if (uuid != NULL) { + ks->names[i] = *uuid; + ks->index[n++] = i; + } + } + + ks->used = n; + + if (ks->used > ks->size) + return HAL_ERROR_IMPOSSIBLE; + + /* + * At this point we've built the (unsorted) index from all the valid + * blocks. Now we need to insert free and unrecognized blocks into + * the free list in our preferred order. It's possible that there's + * a better way to do this than linear scan, but this is just + * integer comparisons in a fairly small data set, so it's probably + * not worth trying to optimize. + */ + + if (n < ks->size) + for (int i = 0; i < ks->size; i++) + if (block_types[i] == HAL_KS_BLOCK_TYPE_ERASED) + ks->index[n++] = i; + + if (n < ks->size) + for (int i = first_erased; i < ks->size; i++) + if (block_types[i] == HAL_KS_BLOCK_TYPE_ZEROED) + ks->index[n++] = i; + + if (n < ks->size) + for (int i = 0; i < first_erased; i++) + if (block_types[i] == HAL_KS_BLOCK_TYPE_ZEROED) + ks->index[n++] = i; + + if (n < ks->size) + for (int i = 0; i < ks->size; i++) + if (block_types[i] == HAL_KS_BLOCK_TYPE_UNKNOWN) + ks->index[n++] = i; + + if (ks->used > ks->size) + return HAL_ERROR_IMPOSSIBLE; + + /* + * Sort the index, then deal with tombstones. Tombstones are blocks + * left behind when something bad (like a power failure) happened + * while we updating. There can be at most one tombstone and one + * live block for a given UUID. If we find no live block, we need + * to restore it from the tombstone, after which we need to zero the + * tombstone in either case. The sequence of operations while + * updating is designed so that, barring a bug or a hardware + * failure, we should never lose data. + */ + + if ((err = hal_ks_index_heapsort(ks)) != HAL_OK) + return err; + + for (unsigned b_tomb = 0; b_tomb < ks->size; b_tomb++) { + + if (block_status[b_tomb] != HAL_KS_BLOCK_STATUS_TOMBSTONE) + continue; + + hal_uuid_t name = ks->names[b_tomb]; + + int where = -1; + + if ((err = hal_ks_index_find(ks, &name, NULL, &where)) != HAL_OK) + return err; + + if (b_tomb != ks->index[where]) { + if (ks->used > where + 1 && b_tomb == ks->index[where + 1]) + where = where + 1; + else if (0 <= where - 1 && b_tomb == ks->index[where - 1]) + where = where - 1; + else + return HAL_ERROR_IMPOSSIBLE; + } + + const int matches_next = where + 1 < ks->used && !hal_uuid_cmp(&name, &ks->names[ks->index[where + 1]]); + const int matches_prev = where - 1 >= 0 && !hal_uuid_cmp(&name, &ks->names[ks->index[where - 1]]); + + if ((matches_prev && matches_next) || + (matches_prev && block_status[ks->index[b_tomb - 1]] != HAL_KS_BLOCK_STATUS_LIVE) || + (matches_next && block_status[ks->index[b_tomb + 1]] != HAL_KS_BLOCK_STATUS_LIVE)) + return HAL_ERROR_IMPOSSIBLE; + + if (matches_prev || matches_next) { + memmove(&ks->index[where], &ks->index[where + 1], (ks->size - where - 1) * sizeof(*ks->index)); + ks->index[ks->size - 1] = b_tomb; + } + + else { + unsigned b_live; + if ((err = hal_ks_block_read(ks, b_tomb, block)) != HAL_OK) + return err; + block->header.block_status = HAL_KS_BLOCK_STATUS_LIVE; + if ((err = hal_ks_index_replace(ks, &name, &b_live, &where)) != HAL_OK || + (err = hal_ks_block_write(ks, b_live, block)) != HAL_OK) + return err; + block_status[b_live] = HAL_KS_BLOCK_STATUS_LIVE; + } + + if ((err = hal_ks_block_zero(ks, b_tomb)) != HAL_OK) + return err; + block_types[ b_tomb] = HAL_KS_BLOCK_TYPE_ZEROED; + block_status[b_tomb] = HAL_KS_BLOCK_STATUS_UNKNOWN; + } + + /* + * Erase first block on free list if it's not already erased. + */ + + if (ks->used < ks->size && + (err = hal_ks_block_erase_maybe(ks, ks->index[ks->used])) != HAL_OK) + return err; + + /* + * And we're finally done. + */ + + return HAL_OK; +} + +/* + * Log a client out of a keystore. + */ + +hal_error_t hal_ks_logout(hal_ks_t *ks, const hal_client_handle_t client) +{ + return + ks == NULL || ks->driver == NULL ? HAL_ERROR_BAD_ARGUMENTS : + ks->driver->logout == NULL ? HAL_ERROR_NOT_IMPLEMENTED : + ks->driver->logout(ks, client); +} + +/* + * Test whether we like a particular key type. + */ + +static inline int acceptable_key_type(const hal_key_type_t type) +{ + switch (type) { + case HAL_KEY_TYPE_RSA_PRIVATE: + case HAL_KEY_TYPE_EC_PRIVATE: + case HAL_KEY_TYPE_RSA_PUBLIC: + case HAL_KEY_TYPE_EC_PUBLIC: + return 1; + default: + return 0; + } +} + +hal_error_t hal_ks_store(hal_ks_t *ks, + hal_pkey_slot_t *slot, + const uint8_t * const der, const size_t der_len) +{ + if (ks == NULL || slot == NULL || der == NULL || der_len == 0 || !acceptable_key_type(slot->type)) + return HAL_ERROR_BAD_ARGUMENTS; + + hal_error_t err = HAL_OK; + hal_ks_block_t *block; + hal_ks_key_block_t *k; + uint8_t kek[KEK_LENGTH]; + size_t kek_len; + unsigned b; + + hal_ks_lock(); + + if ((block = hal_ks_cache_pick_lru(ks)) == NULL) { + err = HAL_ERROR_IMPOSSIBLE; + goto done; + } + + k = &block->key; + + if ((err = hal_ks_index_add(ks, &slot->name, &b, &slot->hint)) != HAL_OK) + goto done; + + hal_ks_cache_mark_used(ks, block, b); + + memset(block, 0xFF, sizeof(*block)); + + block->header.block_type = HAL_KS_BLOCK_TYPE_KEY; + block->header.block_status = HAL_KS_BLOCK_STATUS_LIVE; + + k->name = slot->name; + k->type = slot->type; + k->curve = slot->curve; + k->flags = slot->flags; + k->der_len = SIZEOF_KS_KEY_BLOCK_DER; + k->attributes_len = 0; + + if (ks->used < ks->size) + err = hal_ks_block_erase_maybe(ks, ks->index[ks->used]); + + if (err == HAL_OK) + err = hal_mkm_get_kek(kek, &kek_len, sizeof(kek)); + + if (err == HAL_OK) + err = hal_aes_keywrap(NULL, kek, kek_len, der, der_len, k->der, &k->der_len); + + memset(kek, 0, sizeof(kek)); + + if (err == HAL_OK) + err = hal_ks_block_write(ks, b, block); + + if (err == HAL_OK) + err = hal_ks_block_set_owner(ks, b, slot->client, slot->session); + + if (err == HAL_OK) + goto done; + + memset(block, 0, sizeof(*block)); + hal_ks_cache_release(ks, block); + (void) hal_ks_index_delete(ks, &slot->name, NULL, &slot->hint); + + done: + hal_ks_unlock(); + return err; +} + +hal_error_t hal_ks_fetch(hal_ks_t *ks, + hal_pkey_slot_t *slot, + uint8_t *der, size_t *der_len, const size_t der_max) +{ + if (ks == NULL || slot == NULL) + return HAL_ERROR_BAD_ARGUMENTS; + + hal_error_t err = HAL_OK; + hal_ks_block_t *block; + unsigned b; + + hal_ks_lock(); + + if ((err = hal_ks_index_find(ks, &slot->name, &b, &slot->hint)) != HAL_OK || + (err = hal_ks_block_test_owner(ks, b, slot->client, slot->session)) != HAL_OK || + (err = hal_ks_block_read_cached(ks, b, &block)) != HAL_OK) + goto done; + + if (hal_ks_block_get_type(block) != HAL_KS_BLOCK_TYPE_KEY) { + err = HAL_ERROR_KEYSTORE_WRONG_BLOCK_TYPE; /* HAL_ERROR_KEY_NOT_FOUND */ + goto done; + } + + hal_ks_cache_mark_used(ks, block, b); + + hal_ks_key_block_t *k = &block->key; + + slot->type = k->type; + slot->curve = k->curve; + slot->flags = k->flags; + + if (der == NULL && der_len != NULL) + *der_len = k->der_len; + + if (der != NULL) { + + uint8_t kek[KEK_LENGTH]; + size_t kek_len, der_len_; + hal_error_t err; + + if (der_len == NULL) + der_len = &der_len_; + + *der_len = der_max; + + if ((err = hal_mkm_get_kek(kek, &kek_len, sizeof(kek))) == HAL_OK) + err = hal_aes_keyunwrap(NULL, kek, kek_len, k->der, k->der_len, der, der_len); + + memset(kek, 0, sizeof(kek)); + } + + done: + hal_ks_unlock(); + return err; +} + +hal_error_t hal_ks_delete(hal_ks_t *ks, + hal_pkey_slot_t *slot) +{ + if (ks == NULL || slot == NULL) + return HAL_ERROR_BAD_ARGUMENTS; + + hal_error_t err = HAL_OK; + unsigned b; + + hal_ks_lock(); + + if ((err = hal_ks_index_delete(ks, &slot->name, &b, &slot->hint)) != HAL_OK || + (err = hal_ks_block_test_owner(ks, b, slot->client, slot->session)) != HAL_OK) + goto done; + + hal_ks_cache_release(ks, hal_ks_cache_find_block(ks, b)); + + if ((err = hal_ks_block_zero(ks, b)) != HAL_OK) + goto done; + + err = hal_ks_block_erase_maybe(ks, ks->index[ks->used]); + + done: + hal_ks_unlock(); + return err; +} + +static inline hal_error_t locate_attributes(hal_ks_block_t *block, + uint8_t **bytes, size_t *bytes_len, + unsigned **attrs_len) +{ + if (block == NULL || bytes == NULL || bytes_len == NULL || attrs_len == NULL) + return HAL_ERROR_IMPOSSIBLE; + + + if (hal_ks_block_get_type(block) != HAL_KS_BLOCK_TYPE_KEY) + return HAL_ERROR_KEYSTORE_WRONG_BLOCK_TYPE; + *attrs_len = &block->key.attributes_len; + *bytes = block->key.der + block->key.der_len; + *bytes_len = SIZEOF_KS_KEY_BLOCK_DER - block->key.der_len; + + return HAL_OK; +} + +hal_error_t hal_ks_match(hal_ks_t *ks, + const hal_client_handle_t client, + const hal_session_handle_t session, + const hal_key_type_t type, + const hal_curve_name_t curve, + const hal_key_flags_t mask, + const hal_key_flags_t flags, + const hal_pkey_attribute_t *attributes, + const unsigned attributes_len, + hal_uuid_t *result, + unsigned *result_len, + const unsigned result_max, + const hal_uuid_t * const previous_uuid) +{ + if (ks == NULL || (attributes == NULL && attributes_len > 0) || + result == NULL || result_len == NULL || previous_uuid == NULL) + return HAL_ERROR_BAD_ARGUMENTS; + + hal_error_t err = HAL_OK; + hal_ks_block_t *block; + int i = -1; + + hal_ks_lock(); + + *result_len = 0; + + err = hal_ks_index_find(ks, previous_uuid, NULL, &i); + + if (err == HAL_ERROR_KEY_NOT_FOUND) + i--; + else if (err != HAL_OK) + goto done; + + while (*result_len < result_max && ++i < ks->used) { + + unsigned b = ks->index[i]; + + if ((err = hal_ks_block_read_cached(ks, b, &block)) != HAL_OK) + goto done; + + if ((err = hal_ks_block_test_owner(ks, b, client, session)) == HAL_ERROR_KEY_NOT_FOUND) + continue; + + if (err != HAL_OK) + goto done; + + if ((type != HAL_KEY_TYPE_NONE && type != block->key.type) || + (curve != HAL_CURVE_NONE && curve != block->key.curve) || + ((flags ^ block->key.flags) & mask) != 0) + continue; + + if (attributes_len > 0) { + uint8_t need_attr[attributes_len]; + uint8_t *bytes = NULL; + size_t bytes_len = 0; + unsigned *attrs_len; + int possible = 1; + + memset(need_attr, 1, sizeof(need_attr)); + + if ((err = locate_attributes(block, &bytes, &bytes_len, &attrs_len)) != HAL_OK) + goto done; + + if (*attrs_len > 0) { + hal_pkey_attribute_t attrs[*attrs_len]; + + if ((err = hal_ks_attribute_scan(bytes, bytes_len, attrs, *attrs_len, NULL)) != HAL_OK) + goto done; + + for (int j = 0; possible && j < attributes_len; j++) { + + if (!need_attr[j]) + continue; + + for (hal_pkey_attribute_t *a = attrs; a < attrs + *attrs_len; a++) { + if (a->type != attributes[j].type) + continue; + need_attr[j] = 0; + possible = (a->length == attributes[j].length && + !memcmp(a->value, attributes[j].value, a->length)); + break; + } + } + } + + if (!possible || memchr(need_attr, 1, sizeof(need_attr)) != NULL) + continue; + } + + result[*result_len] = ks->names[b]; + ++*result_len; + } + + err = HAL_OK; + + done: + hal_ks_unlock(); + return err; +} + +hal_error_t hal_ks_set_attributes(hal_ks_t *ks, + hal_pkey_slot_t *slot, + const hal_pkey_attribute_t *attributes, + const unsigned attributes_len) +{ + if (ks == NULL || slot == NULL || attributes == NULL || attributes_len == 0) + return HAL_ERROR_BAD_ARGUMENTS; + + hal_error_t err = HAL_OK; + hal_ks_block_t *block; + unsigned b; + + hal_ks_lock(); + + { + if ((err = hal_ks_index_find(ks, &slot->name, &b, &slot->hint)) != HAL_OK || + (err = hal_ks_block_test_owner(ks, b, slot->client, slot->session)) != HAL_OK || + (err = hal_ks_block_read_cached(ks, b, &block)) != HAL_OK) + goto done; + + hal_ks_cache_mark_used(ks, block, b); + + uint8_t *bytes = NULL; + size_t bytes_len = 0; + unsigned *attrs_len; + + if ((err = locate_attributes(block, &bytes, &bytes_len, &attrs_len)) != HAL_OK) + goto done; + + hal_pkey_attribute_t attrs[*attrs_len + attributes_len]; + size_t total; + + if ((err = hal_ks_attribute_scan(bytes, bytes_len, attrs, *attrs_len, &total)) != HAL_OK) + goto done; + + for (int i = 0; err == HAL_OK && i < attributes_len; i++) + if (attributes[i].length == HAL_PKEY_ATTRIBUTE_NIL) + err = hal_ks_attribute_delete(bytes, bytes_len, attrs, attrs_len, &total, + attributes[i].type); + else + err = hal_ks_attribute_insert(bytes, bytes_len, attrs, attrs_len, &total, + attributes[i].type, + attributes[i].value, + attributes[i].length); + + if (err == HAL_OK) + err = hal_ks_block_update(ks, b, block, &slot->name, &slot->hint); + else + hal_ks_cache_release(ks, block); + } + + done: + hal_ks_unlock(); + return err; +} + +hal_error_t hal_ks_get_attributes(hal_ks_t *ks, + hal_pkey_slot_t *slot, + hal_pkey_attribute_t *attributes, + const unsigned attributes_len, + uint8_t *attributes_buffer, + const size_t attributes_buffer_len) +{ + if (ks == NULL || slot == NULL || attributes == NULL || attributes_len == 0 || + attributes_buffer == NULL) + return HAL_ERROR_BAD_ARGUMENTS; + + for (int i = 0; i < attributes_len; i++) { + attributes[i].length = 0; + attributes[i].value = NULL; + } + + uint8_t *abuf = attributes_buffer; + hal_ks_block_t *block = NULL; + hal_error_t err = HAL_OK; + unsigned found = 0; + unsigned b; + + hal_ks_lock(); + + { + if ((err = hal_ks_index_find(ks, &slot->name, &b, &slot->hint)) != HAL_OK || + (err = hal_ks_block_test_owner(ks, b, slot->client, slot->session)) != HAL_OK || + (err = hal_ks_block_read_cached(ks, b, &block)) != HAL_OK) + goto done; + + hal_ks_cache_mark_used(ks, block, b); + + uint8_t *bytes = NULL; + size_t bytes_len = 0; + unsigned *attrs_len; + + if ((err = locate_attributes(block, &bytes, &bytes_len, &attrs_len)) != HAL_OK) + goto done; + + if (*attrs_len == 0) { + err = HAL_ERROR_ATTRIBUTE_NOT_FOUND; + goto done; + } + + hal_pkey_attribute_t attrs[*attrs_len]; + + if ((err = hal_ks_attribute_scan(bytes, bytes_len, attrs, *attrs_len, NULL)) != HAL_OK) + goto done; + + for (int i = 0; i < attributes_len; i++) { + + if (attributes[i].length > 0) + continue; + + int j = 0; + while (j < *attrs_len && attrs[j].type != attributes[i].type) + j++; + if (j >= *attrs_len) + continue; + found++; + + attributes[i].length = attrs[j].length; + + if (attributes_buffer_len == 0) + continue; + + if (attrs[j].length > attributes_buffer + attributes_buffer_len - abuf) { + err = HAL_ERROR_RESULT_TOO_LONG; + goto done; + } + + memcpy(abuf, attrs[j].value, attrs[j].length); + attributes[i].value = abuf; + abuf += attrs[j].length; + } + + }; + + if (found < attributes_len && attributes_buffer_len > 0) + err = HAL_ERROR_ATTRIBUTE_NOT_FOUND; + else + err = HAL_OK; + + done: + hal_ks_unlock(); + return err; +} + +/* + * Local variables: + * indent-tabs-mode: nil + * End: + */ @@ -0,0 +1,421 @@ +/* + * ks.h + * ---- + * Keystore, generic parts anyway. This is internal within libhal. + * + * Copyright (c) 2015-2017, 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. + */ + +#ifndef _KS_H_ +#define _KS_H_ + +#include "hal.h" +#include "hal_internal.h" + +/* + * Size of a keystore "block". + * + * This must be an integer multiple of the flash subsector size, among + * other reasons because that's the minimum erasable unit. + */ + +#ifndef HAL_KS_BLOCK_SIZE +#define HAL_KS_BLOCK_SIZE (4096) +#endif + +/* + * PIN block gets the all-zeros UUID, which will never be returned by + * the UUID generation code (by definition -- it's not a version 4 UUID). + */ + +const hal_uuid_t hal_ks_pin_uuid; + +/* + * Known block states. + * + * C does not guarantee any particular representation for enums, so + * including enums directly in the block header isn't safe. Instead, + * we use an access method which casts when reading from the header. + * Writing to the header isn't a problem, because C does guarantee + * that enum is compatible with *some* integer type, it just doesn't + * specify which one. + */ + +typedef enum { + HAL_KS_BLOCK_TYPE_ERASED = 0xFF, /* Pristine erased block (candidate for reuse) */ + HAL_KS_BLOCK_TYPE_ZEROED = 0x00, /* Zeroed block (recently used) */ + HAL_KS_BLOCK_TYPE_KEY = 0x55, /* Block contains key material */ + HAL_KS_BLOCK_TYPE_PIN = 0xAA, /* Block contains PINs */ + HAL_KS_BLOCK_TYPE_UNKNOWN = -1, /* Internal code for "I have no clue what this is" */ +} hal_ks_block_type_t; + +/* + * Block status. + */ + +typedef enum { + HAL_KS_BLOCK_STATUS_LIVE = 0x66, /* This is a live block */ + HAL_KS_BLOCK_STATUS_TOMBSTONE = 0x44, /* This is a tombstone left behind during an update */ + HAL_KS_BLOCK_STATUS_UNKNOWN = -1, /* Internal code for "I have no clue what this is" */ +} hal_ks_block_status_t; + +/* + * Common header for all keystore block types. + * A few of these fields are deliberately omitted from the CRC. + */ + +typedef struct { + uint8_t block_type; + uint8_t block_status; + hal_crc32_t crc; +} hal_ks_block_header_t; + +/* + * Key block. Tail end of "der" field (after der_len) used for attributes. + */ + +typedef struct { + hal_ks_block_header_t header; + hal_uuid_t name; + hal_key_type_t type; + hal_curve_name_t curve; + hal_key_flags_t flags; + size_t der_len; + unsigned attributes_len; + uint8_t der[]; /* Must be last field -- C99 "flexible array member" */ +} hal_ks_key_block_t; + +#define SIZEOF_KS_KEY_BLOCK_DER \ + (HAL_KS_BLOCK_SIZE - offsetof(hal_ks_key_block_t, der)) + +/* + * PIN block. Also includes space for backing up the KEK when + * HAL_MKM_FLASH_BACKUP_KLUDGE is enabled. + */ + +typedef struct { + hal_ks_block_header_t header; + hal_ks_pin_t wheel_pin; + hal_ks_pin_t so_pin; + hal_ks_pin_t user_pin; +#if HAL_MKM_FLASH_BACKUP_KLUDGE + uint32_t kek_set; + uint8_t kek[KEK_LENGTH]; +#endif +} hal_ks_pin_block_t; + +#define FLASH_KEK_SET 0x33333333 + +/* + * One keystore block. + */ + +typedef union { + uint8_t bytes[HAL_KS_BLOCK_SIZE]; + hal_ks_block_header_t header; + hal_ks_key_block_t key; + hal_ks_pin_block_t pin; +} hal_ks_block_t; + +/* + * In-memory cache. + */ + +typedef struct { + unsigned blockno; + unsigned lru; + hal_ks_block_t block; +} hal_ks_cache_block_t; + +/* + * Keystore object. hal_internal.h typedefs this to hal_ks_t. + * + * We expect this to be a static variable, but we expect the arrays in + * it to be allocated at runtime using hal_allocate_static_memory() + * because they can get kind of large. + * + * Driver-specific stuff is handled by a form of subclassing: the + * driver embeds the hal_ks_t structure at the head of whatever else + * it needs, and performs (controlled, type-safe) casts as needed. + * + * Core of this is the keystore index. This is intended to be usable + * by both memory-based and flash-based keystores. Some of the + * features aren't necessary for memory-based keystores, but should be + * harmless, and let us keep the drivers simple. + * + * General approach is multiple arrays, all but one of which are + * indexed by "block" numbers, where a block number might be a slot in + * yet another static array, the number of a flash sub-sector, or + * whatever is the appropriate unit for holding one keystore record. + * + * The index array only contains block numbers. This is a small data + * structure so that moving data within it is relatively cheap. + * + * The index array is divided into two portions: the index proper, and + * the free queue. The index proper is ordered according to the names + * (UUIDs) of the corresponding blocks; the free queue is a FIFO, to + * support a simplistic form of wear leveling in flash-based keystores. + * + * Key names are kept in a separate array, indexed by block number. + * + * The all-zeros UUID, which (by definition) cannot be a valid key + * UUID, is reserved for the (non-key) block used to stash PINs and + * other small data which aren't really part of the keystore proper + * but are kept with it because the keystore is the flash we have. + * + * Note that this API deliberately says nothing about how the keys + * themselves are stored, that's up to the keystore driver. + */ + +typedef struct hal_ks_driver hal_ks_driver_t; + +struct hal_ks { + const hal_ks_driver_t *driver;/* Must be first */ + unsigned size; /* Blocks in keystore */ + unsigned used; /* How many blocks are in use */ + uint16_t *index; /* Index/freelist array */ + hal_uuid_t *names; /* Keyname array */ + unsigned cache_lru; /* Cache LRU counter */ + unsigned cache_size; /* Size (how many blocks) in cache */ + hal_ks_cache_block_t *cache; /* Cache */ +}; + +/* + * Keystore driver. + */ + +struct hal_ks_driver { + hal_error_t (*init) (hal_ks_t *ks, const int alloc); + hal_error_t (*read) (hal_ks_t *ks, const unsigned blockno, hal_ks_block_t *block); + hal_error_t (*write) (hal_ks_t *ks, const unsigned blockno, hal_ks_block_t *block); + hal_error_t (*deprecate) (hal_ks_t *ks, const unsigned blockno); + hal_error_t (*zero) (hal_ks_t *ks, const unsigned blockno); + hal_error_t (*erase) (hal_ks_t *ks, const unsigned blockno); + hal_error_t (*erase_maybe) (hal_ks_t *ks, const unsigned blockno); + hal_error_t (*set_owner) (hal_ks_t *ks, const unsigned blockno, + const hal_client_handle_t client, const hal_session_handle_t session); + hal_error_t (*test_owner) (hal_ks_t *ks, const unsigned blockno, + const hal_client_handle_t client, const hal_session_handle_t session); + hal_error_t (*copy_owner) (hal_ks_t *ks, const unsigned source, const unsigned target); + hal_error_t (*logout) (hal_ks_t *ks, const hal_client_handle_t client); +}; + +/* + * Wrappers around keystore driver methods. + * + * hal_ks_init() and hal_ks_logout() are missing here because we + * expose them to the rest of libhal. + */ + +static inline hal_error_t hal_ks_block_read(hal_ks_t *ks, const unsigned blockno, hal_ks_block_t *block) +{ + return + ks == NULL || ks->driver == NULL ? HAL_ERROR_BAD_ARGUMENTS : + ks->driver->read == NULL ? HAL_ERROR_NOT_IMPLEMENTED : + ks->driver->read(ks, blockno, block); +} + +static inline hal_error_t hal_ks_block_write(hal_ks_t *ks, const unsigned blockno, hal_ks_block_t *block) +{ + return + ks == NULL || ks->driver == NULL ? HAL_ERROR_BAD_ARGUMENTS : + ks->driver->write == NULL ? HAL_ERROR_NOT_IMPLEMENTED : + ks->driver->write(ks, blockno, block); +} + +static inline hal_error_t hal_ks_block_deprecate(hal_ks_t *ks, const unsigned blockno) +{ + return + ks == NULL || ks->driver == NULL ? HAL_ERROR_BAD_ARGUMENTS : + ks->driver->deprecate == NULL ? HAL_ERROR_NOT_IMPLEMENTED : + ks->driver->deprecate(ks, blockno); +} + +static inline hal_error_t hal_ks_block_zero(hal_ks_t *ks, const unsigned blockno) +{ + return + ks == NULL || ks->driver == NULL ? HAL_ERROR_BAD_ARGUMENTS : + ks->driver->zero == NULL ? HAL_ERROR_NOT_IMPLEMENTED : + ks->driver->zero(ks, blockno); +} + +static inline hal_error_t hal_ks_block_erase(hal_ks_t *ks, const unsigned blockno) +{ + return + ks == NULL || ks->driver == NULL ? HAL_ERROR_BAD_ARGUMENTS : + ks->driver->erase == NULL ? HAL_ERROR_NOT_IMPLEMENTED : + ks->driver->erase(ks, blockno); +} + +static inline hal_error_t hal_ks_block_erase_maybe(hal_ks_t *ks, const unsigned blockno) +{ + return + ks == NULL || ks->driver == NULL ? HAL_ERROR_BAD_ARGUMENTS : + ks->driver->erase_maybe == NULL ? HAL_ERROR_NOT_IMPLEMENTED : + ks->driver->erase_maybe(ks, blockno); +} + +static inline hal_error_t hal_ks_block_set_owner(hal_ks_t *ks, + const unsigned blockno, + const hal_client_handle_t client, + const hal_session_handle_t session) +{ + return + ks == NULL || ks->driver == NULL ? HAL_ERROR_BAD_ARGUMENTS : + ks->driver->set_owner == NULL ? HAL_ERROR_NOT_IMPLEMENTED : + ks->driver->set_owner(ks, blockno, client, session); +} + +static inline hal_error_t hal_ks_block_test_owner(hal_ks_t *ks, + const unsigned blockno, + const hal_client_handle_t client, + const hal_session_handle_t session) +{ + return + ks == NULL || ks->driver == NULL ? HAL_ERROR_BAD_ARGUMENTS : + ks->driver->test_owner == NULL ? HAL_ERROR_NOT_IMPLEMENTED : + ks->driver->test_owner(ks, blockno, client, session); +} + +static inline hal_error_t hal_ks_block_copy_owner(hal_ks_t *ks, + const unsigned source, + const unsigned target) +{ + return + ks == NULL || ks->driver == NULL ? HAL_ERROR_BAD_ARGUMENTS : + ks->driver->copy_owner == NULL ? HAL_ERROR_NOT_IMPLEMENTED : + ks->driver->copy_owner(ks, source, target); +} + +/* + * Type safe casts. + */ + +static inline hal_ks_block_type_t hal_ks_block_get_type(const hal_ks_block_t * const block) +{ + return block == NULL ? HAL_KS_BLOCK_TYPE_UNKNOWN : + (hal_ks_block_type_t) block->header.block_type; +} + +static inline hal_ks_block_status_t hal_ks_block_get_status(const hal_ks_block_t * const block) +{ + return block == NULL ? HAL_KS_BLOCK_STATUS_UNKNOWN : + (hal_ks_block_status_t) block->header.block_status; +} + +/* + * Keystore utilities. Some or all of these may end up static within ks.c. + */ + +extern hal_error_t hal_ks_alloc_common(hal_ks_t *ks, + const unsigned ks_blocks, + const unsigned cache_blocks, + void **extra, + const size_t extra_len); + +extern hal_error_t hal_ks_init_common(hal_ks_t *ks); + +extern hal_crc32_t hal_ks_block_calculate_crc(const hal_ks_block_t * const block); + +extern hal_error_t hal_ks_index_heapsort(hal_ks_t *ks); + +extern hal_error_t hal_ks_index_find(hal_ks_t *ks, + const hal_uuid_t * const name, + unsigned *blockno, + int *hint); + +extern hal_error_t hal_ks_index_add(hal_ks_t *ks, + const hal_uuid_t * const name, + unsigned *blockno, + int *hint); + +extern hal_error_t hal_ks_index_delete(hal_ks_t *ks, + const hal_uuid_t * const name, + unsigned *blockno, + int *hint); + +extern hal_error_t hal_ks_index_replace(hal_ks_t *ks, + const hal_uuid_t * const name, + unsigned *blockno, + int *hint); + +extern hal_error_t hal_ks_index_fsck(hal_ks_t *ks); + +extern const size_t hal_ks_attribute_header_size; + +extern hal_error_t hal_ks_attribute_scan(const uint8_t * const bytes, + const size_t bytes_len, + hal_pkey_attribute_t *attributes, + const unsigned attributes_len, + size_t *total_len); + +extern hal_error_t hal_ks_attribute_delete(uint8_t *bytes, + const size_t bytes_len, + hal_pkey_attribute_t *attributes, + unsigned *attributes_len, + size_t *total_len, + const uint32_t type); + +extern hal_error_t hal_ks_attribute_insert(uint8_t *bytes, const size_t bytes_len, + hal_pkey_attribute_t *attributes, + unsigned *attributes_len, + size_t *total_len, + const uint32_t type, + const uint8_t * const value, + const size_t value_len); + +extern hal_ks_block_t *hal_ks_cache_pick_lru(hal_ks_t *ks); + +extern hal_ks_block_t *hal_ks_cache_find_block(const hal_ks_t * const ks, + const unsigned blockno); + +extern void hal_ks_cache_mark_used(hal_ks_t *ks, + const hal_ks_block_t * const block, + const unsigned blockno); + +extern void hal_ks_cache_release(hal_ks_t *ks, + const hal_ks_block_t * const block); + +extern hal_error_t hal_ks_block_read_cached(hal_ks_t *ks, + const unsigned blockno, + hal_ks_block_t **block); + +extern hal_error_t hal_ks_block_update(hal_ks_t *ks, + const unsigned b1, + hal_ks_block_t *block, + const hal_uuid_t * const uuid, + int *hint); + +#endif /* _KS_H_ */ + +/* + * Local variables: + * indent-tabs-mode: nil + * End: + */ diff --git a/ks_flash.c b/ks_flash.c deleted file mode 100644 index 8aadc37..0000000 --- a/ks_flash.c +++ /dev/null @@ -1,2213 +0,0 @@ -/* - * ks_flash.c - * ---------- - * Keystore implementation in flash memory. - * - * Authors: Rob Austein, Fredrik Thulin - * Copyright (c) 2015-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. - */ - -/* - * This keystore driver operates over bare flash, versus over a flash file - * system or flash translation layer. The block size is large enough to - * hold an AES-keywrapped 4096-bit RSA key. Any remaining space in the key - * block may be used to store attributes (opaque TLV blobs). If the - * attributes overflow the key block, additional blocks may be added, but - * no attribute may exceed the block size. - */ - -#include <stddef.h> -#include <string.h> -#include <assert.h> - -#include "hal.h" -#include "hal_internal.h" - -#include "last_gasp_pin_internal.h" - -#define HAL_OK CMIS_HAL_OK -#include "stm-keystore.h" -#undef HAL_OK - -/* - * Known block states. - * - * C does not guarantee any particular representation for enums, so - * including enums directly in the block header isn't safe. Instead, - * we use an access method which casts when reading from the header. - * Writing to the header isn't a problem, because C does guarantee - * that enum is compatible with *some* integer type, it just doesn't - * specify which one. - */ - -typedef enum { - BLOCK_TYPE_ERASED = 0xFF, /* Pristine erased block (candidate for reuse) */ - BLOCK_TYPE_ZEROED = 0x00, /* Zeroed block (recently used) */ - BLOCK_TYPE_KEY = 0x55, /* Block contains key material */ - BLOCK_TYPE_ATTR = 0x66, /* Block contains key attributes (overflow from key block) */ - BLOCK_TYPE_PIN = 0xAA, /* Block contains PINs */ - BLOCK_TYPE_UNKNOWN = -1, /* Internal code for "I have no clue what this is" */ -} flash_block_type_t; - -/* - * Block status. - */ - -typedef enum { - BLOCK_STATUS_LIVE = 0x66, /* This is a live flash block */ - BLOCK_STATUS_TOMBSTONE = 0x44, /* This is a tombstone left behind during an update */ - BLOCK_STATUS_UNKNOWN = -1, /* Internal code for "I have no clue what this is" */ -} flash_block_status_t; - -/* - * Common header for all flash block types. - * A few of these fields are deliberately omitted from the CRC. - */ - -typedef struct { - uint8_t block_type; - uint8_t block_status; - uint8_t total_chunks; - uint8_t this_chunk; - hal_crc32_t crc; -} flash_block_header_t; - -/* - * Key block. Tail end of "der" field (after der_len) used for attributes. - */ - -typedef struct { - flash_block_header_t header; - hal_uuid_t name; - hal_key_type_t type; - hal_curve_name_t curve; - hal_key_flags_t flags; - size_t der_len; - unsigned attributes_len; - uint8_t der[]; /* Must be last field -- C99 "flexible array member" */ -} flash_key_block_t; - -#define SIZEOF_FLASH_KEY_BLOCK_DER \ - (KEYSTORE_SUBSECTOR_SIZE - offsetof(flash_key_block_t, der)) - -/* - * Key attribute overflow block (attributes which don't fit in der field of key block). - */ - -typedef struct { - flash_block_header_t header; - hal_uuid_t name; - unsigned attributes_len; - uint8_t attributes[]; /* Must be last field -- C99 "flexible array member" */ -} flash_attributes_block_t; - -#define SIZEOF_FLASH_ATTRIBUTE_BLOCK_ATTRIBUTES \ - (KEYSTORE_SUBSECTOR_SIZE - offsetof(flash_attributes_block_t, attributes)) - -/* - * PIN block. Also includes space for backing up the KEK when - * HAL_MKM_FLASH_BACKUP_KLUDGE is enabled. - */ - -typedef struct { - flash_block_header_t header; - hal_ks_pin_t wheel_pin; - hal_ks_pin_t so_pin; - hal_ks_pin_t user_pin; -#if HAL_MKM_FLASH_BACKUP_KLUDGE - uint32_t kek_set; - uint8_t kek[KEK_LENGTH]; -#endif -} flash_pin_block_t; - -#define FLASH_KEK_SET 0x33333333 - -/* - * One flash block. - */ - -typedef union { - uint8_t bytes[KEYSTORE_SUBSECTOR_SIZE]; - flash_block_header_t header; - flash_key_block_t key; - flash_attributes_block_t attr; - flash_pin_block_t pin; -} flash_block_t; - -/* - * In-memory cache. - */ - -typedef struct { - unsigned blockno; - uint32_t lru; - flash_block_t block; -} cache_block_t; - -/* - * In-memory database. - * - * The top-level structure is a static variable; the arrays are allocated at runtime - * using hal_allocate_static_memory() because they can get kind of large. - */ - -#ifndef KS_FLASH_CACHE_SIZE -#define KS_FLASH_CACHE_SIZE 4 -#endif - -#define NUM_FLASH_BLOCKS KEYSTORE_NUM_SUBSECTORS - -typedef struct { - hal_ks_t ks; /* Must be first (C "subclassing") */ - hal_ks_index_t ksi; - hal_ks_pin_t wheel_pin; - hal_ks_pin_t so_pin; - hal_ks_pin_t user_pin; - uint32_t cache_lru; - cache_block_t *cache; -} db_t; - -/* - * PIN block gets the all-zeros UUID, which will never be returned by - * the UUID generation code (by definition -- it's not a version 4 UUID). - */ - -const static hal_uuid_t pin_uuid = {{0}}; - -/* - * The in-memory database structure itself is small, but the arrays it - * points to are large enough that they come from SDRAM allocated at - * startup. - */ - -static db_t db; - -/* - * Type safe casts. - */ - -static inline flash_block_type_t block_get_type(const flash_block_t * const block) -{ - assert(block != NULL); - return (flash_block_type_t) block->header.block_type; -} - -static inline flash_block_status_t block_get_status(const flash_block_t * const block) -{ - assert(block != NULL); - return (flash_block_status_t) block->header.block_status; -} - -/* - * Pick unused or least-recently-used slot in our in-memory cache. - * - * Updating lru values is caller's problem: if caller is using a cache - * slot as a temporary buffer and there's no point in caching the - * result, leave the lru values alone and the right thing will happen. - */ - -static inline flash_block_t *cache_pick_lru(void) -{ - uint32_t best_delta = 0; - int best_index = 0; - - for (int i = 0; i < KS_FLASH_CACHE_SIZE; i++) { - - if (db.cache[i].blockno == ~0) - return &db.cache[i].block; - - const uint32_t delta = db.cache_lru - db.cache[i].lru; - if (delta > best_delta) { - best_delta = delta; - best_index = i; - } - - } - - db.cache[best_index].blockno = ~0; - return &db.cache[best_index].block; -} - -/* - * Find a block in our in-memory cache; return block or NULL if not present. - */ - -static inline flash_block_t *cache_find_block(const unsigned blockno) -{ - for (int i = 0; i < KS_FLASH_CACHE_SIZE; i++) - if (db.cache[i].blockno == blockno) - return &db.cache[i].block; - return NULL; -} - -/* - * Mark a block in our in-memory cache as being in current use. - */ - -static inline void cache_mark_used(const flash_block_t * const block, const unsigned blockno) -{ - for (int i = 0; i < KS_FLASH_CACHE_SIZE; i++) { - if (&db.cache[i].block == block) { - db.cache[i].blockno = blockno; - db.cache[i].lru = ++db.cache_lru; - return; - } - } -} - -/* - * Release a block from the in-memory cache. - */ - -static inline void cache_release(const flash_block_t * const block) -{ - if (block != NULL) - cache_mark_used(block, ~0); -} - -/* - * Generate CRC-32 for a block. - * - * This function needs to understand the structure of - * flash_block_header_t, so that it can skip over fields that - * shouldn't be included in the CRC. - */ - -static hal_crc32_t calculate_block_crc(const flash_block_t * const block) -{ - assert(block != NULL); - - hal_crc32_t crc = hal_crc32_init(); - - crc = hal_crc32_update(crc, &block->header.block_type, - sizeof(block->header.block_type)); - - crc = hal_crc32_update(crc, &block->header.total_chunks, - sizeof(block->header.total_chunks)); - - crc = hal_crc32_update(crc, &block->header.this_chunk, - sizeof(block->header.this_chunk)); - - crc = hal_crc32_update(crc, block->bytes + sizeof(flash_block_header_t), - sizeof(*block) - sizeof(flash_block_header_t)); - - return hal_crc32_finalize(crc); -} - -/* - * Calculate offset of the block in the flash address space. - */ - -static inline uint32_t block_offset(const unsigned blockno) -{ - return blockno * KEYSTORE_SUBSECTOR_SIZE; -} - -/* - * Read a flash block. - * - * Flash read on the Alpha is slow enough that it pays to check the - * first page before reading the rest of the block. - */ - -static hal_error_t block_read(const unsigned blockno, flash_block_t *block) -{ - if (block == NULL || blockno >= NUM_FLASH_BLOCKS || sizeof(*block) != KEYSTORE_SUBSECTOR_SIZE) - return HAL_ERROR_IMPOSSIBLE; - - /* Sigh, magic numeric return codes */ - if (keystore_read_data(block_offset(blockno), - block->bytes, - KEYSTORE_PAGE_SIZE) != 1) - return HAL_ERROR_KEYSTORE_ACCESS; - - switch (block_get_type(block)) { - case BLOCK_TYPE_ERASED: - case BLOCK_TYPE_ZEROED: - return HAL_OK; - case BLOCK_TYPE_KEY: - case BLOCK_TYPE_PIN: - case BLOCK_TYPE_ATTR: - break; - default: - return HAL_ERROR_KEYSTORE_BAD_BLOCK_TYPE; - } - - switch (block_get_status(block)) { - case BLOCK_STATUS_LIVE: - case BLOCK_STATUS_TOMBSTONE: - break; - default: - return HAL_ERROR_KEYSTORE_BAD_BLOCK_TYPE; - } - - /* Sigh, magic numeric return codes */ - if (keystore_read_data(block_offset(blockno) + KEYSTORE_PAGE_SIZE, - block->bytes + KEYSTORE_PAGE_SIZE, - sizeof(*block) - KEYSTORE_PAGE_SIZE) != 1) - return HAL_ERROR_KEYSTORE_ACCESS; - - if (calculate_block_crc(block) != block->header.crc) - return HAL_ERROR_KEYSTORE_BAD_CRC; - - return HAL_OK; -} - -/* - * Read a block using the cache. Marking the block as used is left - * for the caller, so we can avoid blowing out the cache when we - * perform a ks_match() operation. - */ - -static hal_error_t block_read_cached(const unsigned blockno, flash_block_t **block) -{ - if (block == NULL) - return HAL_ERROR_IMPOSSIBLE; - - if ((*block = cache_find_block(blockno)) != NULL) - return HAL_OK; - - if ((*block = cache_pick_lru()) == NULL) - return HAL_ERROR_IMPOSSIBLE; - - return block_read(blockno, *block); -} - -/* - * Convert a live block into a tombstone. Caller is responsible for - * making sure that the block being converted is valid; since we don't - * need to update the CRC for this, we just modify the first page. - */ - -static hal_error_t block_deprecate(const unsigned blockno) -{ - if (blockno >= NUM_FLASH_BLOCKS) - return HAL_ERROR_IMPOSSIBLE; - - uint8_t page[KEYSTORE_PAGE_SIZE]; - flash_block_header_t *header = (void *) page; - uint32_t offset = block_offset(blockno); - - /* Sigh, magic numeric return codes */ - if (keystore_read_data(offset, page, sizeof(page)) != 1) - return HAL_ERROR_KEYSTORE_ACCESS; - - header->block_status = BLOCK_STATUS_TOMBSTONE; - - /* Sigh, magic numeric return codes */ - if (keystore_write_data(offset, page, sizeof(page)) != 1) - return HAL_ERROR_KEYSTORE_ACCESS; - - return HAL_OK; -} - -/* - * Zero (not erase) a flash block. Just need to zero the first page. - */ - -static hal_error_t block_zero(const unsigned blockno) -{ - if (blockno >= NUM_FLASH_BLOCKS) - return HAL_ERROR_IMPOSSIBLE; - - uint8_t page[KEYSTORE_PAGE_SIZE] = {0}; - - /* Sigh, magic numeric return codes */ - if (keystore_write_data(block_offset(blockno), page, sizeof(page)) != 1) - return HAL_ERROR_KEYSTORE_ACCESS; - - return HAL_OK; -} - -/* - * Erase a flash block. Also see block_erase_maybe(), below. - */ - -static hal_error_t block_erase(const unsigned blockno) -{ - if (blockno >= NUM_FLASH_BLOCKS) - return HAL_ERROR_IMPOSSIBLE; - - /* Sigh, magic numeric return codes */ - if (keystore_erase_subsector(blockno) != 1) - return HAL_ERROR_KEYSTORE_ACCESS; - - return HAL_OK; -} - -/* - * Erase a flash block if it hasn't already been erased. - * May not be necessary, trying to avoid unnecessary wear. - * - * Unclear whether there's any sane reason why this needs to be - * constant time, given how slow erasure is. But side channel attacks - * can be tricky things, and it's theoretically possible that we could - * leak information about, eg, key length, so we do constant time. - */ - -static hal_error_t block_erase_maybe(const unsigned blockno) -{ - if (blockno >= NUM_FLASH_BLOCKS) - return HAL_ERROR_IMPOSSIBLE; - - uint8_t mask = 0xFF; - - for (uint32_t a = block_offset(blockno); a < block_offset(blockno + 1); a += KEYSTORE_PAGE_SIZE) { - uint8_t page[KEYSTORE_PAGE_SIZE]; - if (keystore_read_data(a, page, sizeof(page)) != 1) - return HAL_ERROR_KEYSTORE_ACCESS; - for (int i = 0; i < KEYSTORE_PAGE_SIZE; i++) - mask &= page[i]; - } - - return mask == 0xFF ? HAL_OK : block_erase(blockno); -} - -/* - * Write a flash block, calculating CRC when appropriate. - */ - -static hal_error_t block_write(const unsigned blockno, flash_block_t *block) -{ - if (block == NULL || blockno >= NUM_FLASH_BLOCKS || sizeof(*block) != KEYSTORE_SUBSECTOR_SIZE) - return HAL_ERROR_IMPOSSIBLE; - - hal_error_t err = block_erase_maybe(blockno); - - if (err != HAL_OK) - return err; - - switch (block_get_type(block)) { - case BLOCK_TYPE_KEY: - case BLOCK_TYPE_PIN: - case BLOCK_TYPE_ATTR: - block->header.crc = calculate_block_crc(block); - break; - default: - break; - } - - /* Sigh, magic numeric return codes */ - if (keystore_write_data(block_offset(blockno), block->bytes, sizeof(*block)) != 1) - return HAL_ERROR_KEYSTORE_ACCESS; - - return HAL_OK; -} - -/* - * Update one flash block, including zombie jamboree. - */ - -static hal_error_t block_update(const unsigned b1, flash_block_t *block, - const hal_uuid_t * const uuid, const unsigned chunk, int *hint) -{ - if (block == NULL) - return HAL_ERROR_IMPOSSIBLE; - - if (db.ksi.used == db.ksi.size) - return HAL_ERROR_NO_KEY_INDEX_SLOTS; - - cache_release(block); - - hal_error_t err; - unsigned b2; - - if ((err = block_deprecate(b1)) != HAL_OK || - (err = hal_ks_index_replace(&db.ksi, uuid, chunk, &b2, hint)) != HAL_OK || - (err = block_write(b2, block)) != HAL_OK || - (err = block_zero(b1)) != HAL_OK) - return err; - - cache_mark_used(block, b2); - - /* - * Erase the first block in the free list. In case of restart, this - * puts the block back at the head of the free list. - */ - - return block_erase_maybe(db.ksi.index[db.ksi.used]); -} - -/* - * Forward reference. - */ - -static hal_error_t fetch_pin_block(unsigned *b, flash_block_t **block); - -/* - * Initialize keystore. This includes various tricky bits, some of - * which attempt to preserve the free list ordering across reboots, to - * improve our simplistic attempt at wear leveling, others attempt to - * recover from unclean shutdown. - */ - -static inline void *gnaw(uint8_t **mem, size_t *len, const size_t size) -{ - if (mem == NULL || *mem == NULL || len == NULL || size > *len) - return NULL; - void *ret = *mem; - *mem += size; - *len -= size; - return ret; -} - -static hal_error_t ks_init(const hal_ks_driver_t * const driver, const int alloc) -{ - hal_error_t err = HAL_OK; - - hal_ks_lock(); - - /* - * Initialize the in-memory database. - */ - - if (alloc) { - - size_t len = (sizeof(*db.ksi.index) * NUM_FLASH_BLOCKS + - sizeof(*db.ksi.names) * NUM_FLASH_BLOCKS + - sizeof(*db.cache) * KS_FLASH_CACHE_SIZE); - - /* - * This is done as a single large allocation, rather than 3 smaller - * allocations, to make it atomic - we need all 3, so either all - * succeed or all fail. - */ - - uint8_t *mem = hal_allocate_static_memory(len); - - if (mem == NULL) { - err = HAL_ERROR_ALLOCATION_FAILURE; - goto done; - } - - memset(&db, 0, sizeof(db)); - memset(mem, 0, len); - - db.ksi.index = gnaw(&mem, &len, sizeof(*db.ksi.index) * NUM_FLASH_BLOCKS); - db.ksi.names = gnaw(&mem, &len, sizeof(*db.ksi.names) * NUM_FLASH_BLOCKS); - db.cache = gnaw(&mem, &len, sizeof(*db.cache) * KS_FLASH_CACHE_SIZE); - db.ksi.size = NUM_FLASH_BLOCKS; - } - - else { - memset(&db.wheel_pin, 0, sizeof(db.wheel_pin)); - memset(&db.so_pin, 0, sizeof(db.so_pin)); - memset(&db.user_pin, 0, sizeof(db.user_pin)); - } - - db.ksi.used = 0; - - if (db.ksi.index == NULL || db.ksi.names == NULL || db.cache == NULL) { - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - - for (int i = 0; i < KS_FLASH_CACHE_SIZE; i++) - db.cache[i].blockno = ~0; - - /* - * Scan existing content of flash to figure out what we've got. - * This gets a bit involved due to the need to recover from things - * like power failures at inconvenient times. - */ - - flash_block_type_t block_types[NUM_FLASH_BLOCKS]; - flash_block_status_t block_status[NUM_FLASH_BLOCKS]; - flash_block_t *block = cache_pick_lru(); - int first_erased = -1; - uint16_t n = 0; - - if (block == NULL) { - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - - for (int i = 0; i < NUM_FLASH_BLOCKS; i++) { - - /* - * Read one block. If the CRC is bad or the block type is - * unknown, it's old data we don't understand, something we were - * writing when we crashed, or bad flash; in any of these cases, - * we want the block to end up near the end of the free list. - */ - - err = block_read(i, block); - - if (err == HAL_ERROR_KEYSTORE_BAD_CRC || err == HAL_ERROR_KEYSTORE_BAD_BLOCK_TYPE) - block_types[i] = BLOCK_TYPE_UNKNOWN; - - else if (err == HAL_OK) - block_types[i] = block_get_type(block); - - else - goto done; - - switch (block_types[i]) { - case BLOCK_TYPE_KEY: - case BLOCK_TYPE_PIN: - case BLOCK_TYPE_ATTR: - block_status[i] = block_get_status(block); - break; - default: - block_status[i] = BLOCK_STATUS_UNKNOWN; - } - - /* - * First erased block we see is head of the free list. - */ - - if (block_types[i] == BLOCK_TYPE_ERASED && first_erased < 0) - first_erased = i; - - /* - * If it's a valid data block, include it in the index. We remove - * tombstones (if any) below, for now it's easiest to include them - * in the index, so we can look them up by name if we must. - */ - - const hal_uuid_t *uuid = NULL; - - switch (block_types[i]) { - case BLOCK_TYPE_KEY: uuid = &block->key.name; break; - case BLOCK_TYPE_ATTR: uuid = &block->attr.name; break; - case BLOCK_TYPE_PIN: uuid = &pin_uuid; break; - default: /* Keep GCC happy */ break; - } - - if (uuid != NULL) { - db.ksi.names[i].name = *uuid; - db.ksi.names[i].chunk = block->header.this_chunk; - db.ksi.index[n++] = i; - } - } - - db.ksi.used = n; - - assert(db.ksi.used <= db.ksi.size); - - /* - * At this point we've built the (unsorted) index from all the valid - * blocks. Now we need to insert free and unrecognized blocks into - * the free list in our preferred order. It's possible that there's - * a better way to do this than linear scan, but this is just - * integer comparisons in a fairly small data set, so it's probably - * not worth trying to optimize. - */ - - if (n < db.ksi.size) - for (int i = 0; i < NUM_FLASH_BLOCKS; i++) - if (block_types[i] == BLOCK_TYPE_ERASED) - db.ksi.index[n++] = i; - - if (n < db.ksi.size) - for (int i = first_erased; i < NUM_FLASH_BLOCKS; i++) - if (block_types[i] == BLOCK_TYPE_ZEROED) - db.ksi.index[n++] = i; - - if (n < db.ksi.size) - for (int i = 0; i < first_erased; i++) - if (block_types[i] == BLOCK_TYPE_ZEROED) - db.ksi.index[n++] = i; - - if (n < db.ksi.size) - for (int i = 0; i < NUM_FLASH_BLOCKS; i++) - if (block_types[i] == BLOCK_TYPE_UNKNOWN) - db.ksi.index[n++] = i; - - assert(n == db.ksi.size); - - /* - * Initialize the index. - */ - - if ((err = hal_ks_index_setup(&db.ksi)) != HAL_OK) - goto done; - - /* - * We might want to call hal_ks_index_fsck() here, if we can figure - * out some safe set of recovery actions we can take. - */ - - /* - * Deal with tombstones. These are blocks left behind when - * something bad (like a power failure) happened while we updating. - * The sequence of operations while updating is designed so that, - * barring a bug or a hardware failure, we should never lose data. - * - * For any tombstone we find, we start by looking for all the blocks - * with a matching UUID, then see what valid sequences we can - * construct from what we found. This basically works in reverse of - * the update sequence in ks_set_attributes(). - * - * If we can construct a valid sequence of live blocks, the complete - * update was written out, and we just need to finish zeroing the - * tombstones. - * - * Otherwise, if we can construct a complete sequence of tombstone - * blocks, the update failed before it was completely written, so we - * have to zero the incomplete sequence of live blocks then restore - * the tombstones. - * - * Otherwise, if the live and tombstone blocks taken together form a - * valid sequence, the update failed while deprecating the old live - * blocks, and none of the new data was written, so we need to restore - * the tombstones and leave the live blocks alone. - * - * If none of the above applies, we don't understand what happened, - * which is a symptom of either a bug or a hardware failure more - * serious than simple loss of power or reboot at an inconvenient - * time, so we error out to avoid accidental loss of data. - */ - - for (int i = 0; i < NUM_FLASH_BLOCKS; i++) { - - if (block_status[i] != BLOCK_STATUS_TOMBSTONE) - continue; - - hal_uuid_t name = db.ksi.names[i].name; - unsigned n_blocks; - int where = -1; - - if ((err = hal_ks_index_find_range(&db.ksi, &name, 0, &n_blocks, NULL, &where, 0)) != HAL_OK) - goto done; - - /* - * hal_ks_index_find_range does a binary search, not a linear search, - * so it may not return the first instance of a block with the given - * name and chunk=0. Search backwards to make sure we have all chunks. - */ - - while (where > 0 && !hal_uuid_cmp(&name, &db.ksi.names[db.ksi.index[where - 1]].name)) { - where--; - n_blocks++; - } - - /* - * Rather than calling hal_ks_index_find_range with an array pointer - * to get the list of matching blocks (because of the binary search - * issue), we're going to fondle the index directly. This is really - * not something to do in regular code, but this is error-recovery - * code. - */ - - int live_ok = 1, tomb_ok = 1, join_ok = 1; - unsigned n_live = 0, n_tomb = 0; - unsigned i_live = 0, i_tomb = 0; - - for (int j = 0; j < n_blocks; j++) { - unsigned b = db.ksi.index[where + j]; - switch (block_status[b]) { - case BLOCK_STATUS_LIVE: n_live++; break; - case BLOCK_STATUS_TOMBSTONE: n_tomb++; break; - default: err = HAL_ERROR_IMPOSSIBLE; goto done; - } - } - - uint16_t live_blocks[n_live], tomb_blocks[n_tomb]; - - for (int j = 0; j < n_blocks; j++) { - unsigned b = db.ksi.index[where + j]; - - if ((err = block_read(b, block)) != HAL_OK) - goto done; - - join_ok &= block->header.this_chunk == j && block->header.total_chunks == n_blocks; - - switch (block_status[b]) { - case BLOCK_STATUS_LIVE: - live_blocks[i_live] = b; - live_ok &= block->header.this_chunk == i_live++ && block->header.total_chunks == n_live; - break; - case BLOCK_STATUS_TOMBSTONE: - tomb_blocks[i_tomb] = b; - tomb_ok &= block->header.this_chunk == i_tomb++ && block->header.total_chunks == n_tomb; - break; - default: - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - } - - if (!live_ok && !tomb_ok && !join_ok) { - err = HAL_ERROR_KEYSTORE_LOST_DATA; - goto done; - } - - /* - * If live_ok or tomb_ok, we have to zero out some blocks, and adjust - * the index. Again, don't fondle the index directly, outside of error - * recovery. - */ - - if (live_ok) { - for (int j = 0; j < n_tomb; j++) { - const unsigned b = tomb_blocks[j]; - if ((err = block_zero(b)) != HAL_OK) - goto done; - block_types[b] = BLOCK_TYPE_ZEROED; - block_status[b] = BLOCK_STATUS_UNKNOWN; - } - } - - else if (tomb_ok) { - for (int j = 0; j < n_live; j++) { - const unsigned b = live_blocks[j]; - if ((err = block_zero(b)) != HAL_OK) - goto done; - block_types[b] = BLOCK_TYPE_ZEROED; - block_status[b] = BLOCK_STATUS_UNKNOWN; - } - } - - if (live_ok) { - memcpy(&db.ksi.index[where], live_blocks, n_live * sizeof(*db.ksi.index)); - memmove(&db.ksi.index[where + n_live], &db.ksi.index[where + n_blocks], - (db.ksi.size - where - n_blocks) * sizeof(*db.ksi.index)); - memcpy(&db.ksi.index[db.ksi.size - n_tomb], tomb_blocks, n_tomb * sizeof(*db.ksi.index)); - db.ksi.used -= n_tomb; - n_blocks = n_live; - } - - else if (tomb_ok) { - memcpy(&db.ksi.index[where], tomb_blocks, n_tomb * sizeof(*db.ksi.index)); - memmove(&db.ksi.index[where + n_tomb], &db.ksi.index[where + n_blocks], - (db.ksi.size - where - n_blocks) * sizeof(*db.ksi.index)); - memcpy(&db.ksi.index[db.ksi.size - n_live], live_blocks, n_live * sizeof(*db.ksi.index)); - db.ksi.used -= n_live; - n_blocks = n_tomb; - } - - /* - * Restore tombstone blocks (tomb_ok or join_ok). - */ - - for (int j = 0; j < n_blocks; j++) { - int hint = where + j; - unsigned b1 = db.ksi.index[hint], b2; - if (block_status[b1] != BLOCK_STATUS_TOMBSTONE) - continue; - if ((err = block_read(b1, block)) != HAL_OK) - goto done; - block->header.block_status = BLOCK_STATUS_LIVE; - if ((err = hal_ks_index_replace(&db.ksi, &name, j, &b2, &hint)) != HAL_OK || - (err = block_write(b2, block)) != HAL_OK) - goto done; - block_types[b1] = BLOCK_TYPE_ZEROED; - block_status[b1] = BLOCK_STATUS_UNKNOWN; - block_status[b2] = BLOCK_STATUS_LIVE; - } - } - - /* - * Fetch or create the PIN block. - */ - - err = fetch_pin_block(NULL, &block); - - if (err == HAL_OK) { - db.wheel_pin = block->pin.wheel_pin; - db.so_pin = block->pin.so_pin; - db.user_pin = block->pin.user_pin; - } - - else if (err != HAL_ERROR_KEY_NOT_FOUND) - goto done; - - else { - /* - * We found no PIN block, so create one, with the user and so PINs - * cleared and the wheel PIN set to the last-gasp value. The - * last-gasp WHEEL PIN is a terrible answer, but we need some kind - * of bootstrapping mechanism when all else fails. If you have a - * better suggestion, we'd love to hear it. - */ - - unsigned b; - - memset(block, 0xFF, sizeof(*block)); - - block->header.block_type = BLOCK_TYPE_PIN; - block->header.block_status = BLOCK_STATUS_LIVE; - block->header.total_chunks = 1; - block->header.this_chunk = 0; - - block->pin.wheel_pin = db.wheel_pin = hal_last_gasp_pin; - block->pin.so_pin = db.so_pin; - block->pin.user_pin = db.user_pin; - - if ((err = hal_ks_index_add(&db.ksi, &pin_uuid, 0, &b, NULL)) != HAL_OK) - goto done; - - cache_mark_used(block, b); - - err = block_write(b, block); - - cache_release(block); - - if (err != HAL_OK) - goto done; - } - - /* - * Erase first block on free list if it's not already erased. - */ - - if (db.ksi.used < db.ksi.size && - (err = block_erase_maybe(db.ksi.index[db.ksi.used])) != HAL_OK) - goto done; - - /* - * And we're finally done. - */ - - db.ks.driver = driver; - - err = HAL_OK; - - done: - hal_ks_unlock(); - return err; -} - -static hal_error_t ks_shutdown(const hal_ks_driver_t * const driver) -{ - if (db.ks.driver != driver) - return HAL_ERROR_KEYSTORE_ACCESS; - return HAL_OK; -} - -static hal_error_t ks_open(const hal_ks_driver_t * const driver, - hal_ks_t **ks) -{ - if (driver != hal_ks_token_driver || ks == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - *ks = &db.ks; - return HAL_OK; -} - -static hal_error_t ks_close(hal_ks_t *ks) -{ - if (ks != NULL && ks != &db.ks) - return HAL_ERROR_BAD_ARGUMENTS; - - return HAL_OK; -} - -static inline int acceptable_key_type(const hal_key_type_t type) -{ - switch (type) { - case HAL_KEY_TYPE_RSA_PRIVATE: - case HAL_KEY_TYPE_EC_PRIVATE: - case HAL_KEY_TYPE_RSA_PUBLIC: - case HAL_KEY_TYPE_EC_PUBLIC: - return 1; - default: - return 0; - } -} - -static hal_error_t ks_store(hal_ks_t *ks, - hal_pkey_slot_t *slot, - const uint8_t * const der, const size_t der_len) -{ - if (ks != &db.ks || slot == NULL || der == NULL || der_len == 0 || !acceptable_key_type(slot->type)) - return HAL_ERROR_BAD_ARGUMENTS; - - hal_error_t err = HAL_OK; - flash_block_t *block; - flash_key_block_t *k; - uint8_t kek[KEK_LENGTH]; - size_t kek_len; - unsigned b; - - hal_ks_lock(); - - if ((block = cache_pick_lru()) == NULL) { - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - - k = &block->key; - - if ((err = hal_ks_index_add(&db.ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK) - goto done; - - cache_mark_used(block, b); - - memset(block, 0xFF, sizeof(*block)); - - block->header.block_type = BLOCK_TYPE_KEY; - block->header.block_status = BLOCK_STATUS_LIVE; - block->header.total_chunks = 1; - block->header.this_chunk = 0; - - k->name = slot->name; - k->type = slot->type; - k->curve = slot->curve; - k->flags = slot->flags; - k->der_len = SIZEOF_FLASH_KEY_BLOCK_DER; - k->attributes_len = 0; - - if (db.ksi.used < db.ksi.size) - err = block_erase_maybe(db.ksi.index[db.ksi.used]); - - if (err == HAL_OK) - err = hal_mkm_get_kek(kek, &kek_len, sizeof(kek)); - - if (err == HAL_OK) - err = hal_aes_keywrap(NULL, kek, kek_len, der, der_len, k->der, &k->der_len); - - memset(kek, 0, sizeof(kek)); - - if (err == HAL_OK) - err = block_write(b, block); - - if (err == HAL_OK) - goto done; - - memset(block, 0, sizeof(*block)); - cache_release(block); - (void) hal_ks_index_delete(&db.ksi, &slot->name, 0, NULL, &slot->hint); - - done: - hal_ks_unlock(); - return err; -} - -static hal_error_t ks_fetch(hal_ks_t *ks, - hal_pkey_slot_t *slot, - uint8_t *der, size_t *der_len, const size_t der_max) -{ - if (ks != &db.ks || slot == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - hal_error_t err = HAL_OK; - flash_block_t *block; - unsigned b; - - hal_ks_lock(); - - if ((err = hal_ks_index_find(&db.ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK || - (err = block_read_cached(b, &block)) != HAL_OK) - goto done; - - if (block_get_type(block) != BLOCK_TYPE_KEY) { - err = HAL_ERROR_KEYSTORE_WRONG_BLOCK_TYPE; /* HAL_ERROR_KEY_NOT_FOUND */ - goto done; - } - - cache_mark_used(block, b); - - flash_key_block_t *k = &block->key; - - slot->type = k->type; - slot->curve = k->curve; - slot->flags = k->flags; - - if (der == NULL && der_len != NULL) - *der_len = k->der_len; - - if (der != NULL) { - - uint8_t kek[KEK_LENGTH]; - size_t kek_len, der_len_; - hal_error_t err; - - if (der_len == NULL) - der_len = &der_len_; - - *der_len = der_max; - - if ((err = hal_mkm_get_kek(kek, &kek_len, sizeof(kek))) == HAL_OK) - err = hal_aes_keyunwrap(NULL, kek, kek_len, k->der, k->der_len, der, der_len); - - memset(kek, 0, sizeof(kek)); - } - - done: - hal_ks_unlock(); - return err; -} - -static hal_error_t ks_delete(hal_ks_t *ks, - hal_pkey_slot_t *slot) -{ - if (ks != &db.ks || slot == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - hal_error_t err = HAL_OK; - unsigned n; - - hal_ks_lock(); - - { - /* - * Get the count of blocks to delete. - */ - - if ((err = hal_ks_index_delete_range(&db.ksi, &slot->name, 0, &n, NULL, &slot->hint)) != HAL_OK) - goto done; - - /* - * Then delete them. - */ - - unsigned b[n]; - - if ((err = hal_ks_index_delete_range(&db.ksi, &slot->name, n, NULL, b, &slot->hint)) != HAL_OK) - goto done; - - for (int i = 0; i < n; i++) - cache_release(cache_find_block(b[i])); - - /* - * Zero the blocks, to mark them as recently used. - */ - - for (int i = 0; i < n; i++) - if ((err = block_zero(b[i])) != HAL_OK) - goto done; - - /* - * Erase the first block in the free list. In case of restart, this - * puts the block back at the head of the free list. - */ - - err = block_erase_maybe(db.ksi.index[db.ksi.used]); - } - - done: - hal_ks_unlock(); - return err; -} - -static inline hal_error_t locate_attributes(flash_block_t *block, const unsigned chunk, - uint8_t **bytes, size_t *bytes_len, - unsigned **attrs_len) -{ - if (block == NULL || bytes == NULL || bytes_len == NULL || attrs_len == NULL) - return HAL_ERROR_IMPOSSIBLE; - - if (chunk == 0) { - if (block_get_type(block) != BLOCK_TYPE_KEY) - return HAL_ERROR_KEYSTORE_WRONG_BLOCK_TYPE; /* HAL_ERROR_KEY_NOT_FOUND */ - *attrs_len = &block->key.attributes_len; - *bytes = block->key.der + block->key.der_len; - *bytes_len = SIZEOF_FLASH_KEY_BLOCK_DER - block->key.der_len; - } - - else { - if (block_get_type(block) != BLOCK_TYPE_ATTR) - return HAL_ERROR_KEYSTORE_WRONG_BLOCK_TYPE; /* HAL_ERROR_KEY_NOT_FOUND */ - *attrs_len = &block->attr.attributes_len; - *bytes = block->attr.attributes; - *bytes_len = SIZEOF_FLASH_ATTRIBUTE_BLOCK_ATTRIBUTES; - } - - return HAL_OK; -} - -static hal_error_t ks_match(hal_ks_t *ks, - const hal_client_handle_t client, - const hal_session_handle_t session, - const hal_key_type_t type, - const hal_curve_name_t curve, - const hal_key_flags_t mask, - const hal_key_flags_t flags, - const hal_pkey_attribute_t *attributes, - const unsigned attributes_len, - hal_uuid_t *result, - unsigned *result_len, - const unsigned result_max, - const hal_uuid_t * const previous_uuid) -{ - if (ks == NULL || (attributes == NULL && attributes_len > 0) || - result == NULL || result_len == NULL || previous_uuid == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - uint8_t need_attr[attributes_len > 0 ? attributes_len : 1]; - hal_error_t err = HAL_OK; - flash_block_t *block; - int possible = 0; - int i = -1; - - hal_ks_lock(); - - *result_len = 0; - - err = hal_ks_index_find(&db.ksi, previous_uuid, 0, NULL, &i); - - if (err == HAL_ERROR_KEY_NOT_FOUND) - i--; - else if (err != HAL_OK) - goto done; - - while (*result_len < result_max && ++i < db.ksi.used) { - - unsigned b = db.ksi.index[i]; - - if (db.ksi.names[b].chunk == 0) - possible = 1; - - if (!possible) - continue; - - if ((err = block_read_cached(b, &block)) != HAL_OK) - goto done; - - if (db.ksi.names[b].chunk == 0) { - memset(need_attr, 1, sizeof(need_attr)); - possible = ((type == HAL_KEY_TYPE_NONE || type == block->key.type) && - (curve == HAL_CURVE_NONE || curve == block->key.curve) && - ((flags ^ block->key.flags) & mask) == 0); - } - - if (!possible) - continue; - - if (attributes_len > 0) { - uint8_t *bytes = NULL; - size_t bytes_len = 0; - unsigned *attrs_len; - - if ((err = locate_attributes(block, db.ksi.names[b].chunk, - &bytes, &bytes_len, &attrs_len)) != HAL_OK) - goto done; - - if (*attrs_len > 0) { - hal_pkey_attribute_t attrs[*attrs_len]; - - if ((err = hal_ks_attribute_scan(bytes, bytes_len, attrs, *attrs_len, NULL)) != HAL_OK) - goto done; - - for (int j = 0; possible && j < attributes_len; j++) { - - if (!need_attr[j]) - continue; - - for (hal_pkey_attribute_t *a = attrs; a < attrs + *attrs_len; a++) { - if (a->type != attributes[j].type) - continue; - need_attr[j] = 0; - possible = (a->length == attributes[j].length && - !memcmp(a->value, attributes[j].value, a->length)); - break; - } - } - } - } - - if (!possible) - continue; - - if (attributes_len > 0 && memchr(need_attr, 1, sizeof(need_attr)) != NULL) - continue; - - result[*result_len] = db.ksi.names[b].name; - ++*result_len; - possible = 0; - } - - err = HAL_OK; - - done: - hal_ks_unlock(); - return err; -} - -/* - * This controls whether we include a separate code path for the - * common case where we can handle attribute setting via a single - * block update. It's a lot simpler when it works, but it's yet - * another code path, and enabling it leaves the slower full-blown - * algorithm less tested. - */ - -#ifndef KS_SET_ATTRIBUTES_SINGLE_BLOCK_UPDATE_FAST_PATH -#define KS_SET_ATTRIBUTES_SINGLE_BLOCK_UPDATE_FAST_PATH 0 -#endif - -/* - * ks_set_attributes() is much too long. Probably needs to be broken - * up into a collection of inline functions, even if most of them end - * up being called exactly once. - */ - -static hal_error_t ks_set_attributes(hal_ks_t *ks, - hal_pkey_slot_t *slot, - const hal_pkey_attribute_t *attributes, - const unsigned attributes_len) -{ - if (ks != &db.ks || slot == NULL || attributes == NULL || attributes_len == 0) - return HAL_ERROR_BAD_ARGUMENTS; - - /* - * Perform initial scan of the object to figure out the total - * attribute count and a few other parameters. - * - * If enabled, try to do everything as as a single-block update. If - * single block update fails, we MUST clear the modified block from - * the cache before doing anything else. - */ - - unsigned updated_attributes_len = attributes_len; - hal_error_t err = HAL_OK; - flash_block_t *block; - unsigned chunk = 0; - unsigned b; - - hal_ks_lock(); - - { - - do { - int hint = slot->hint + chunk; - - if ((err = hal_ks_index_find(&db.ksi, &slot->name, chunk, &b, &hint)) != HAL_OK || - (err = block_read_cached(b, &block)) != HAL_OK) - goto done; - - if (block->header.this_chunk != chunk) { - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - - cache_mark_used(block, b); - - if (chunk == 0) - slot->hint = hint; - - uint8_t *bytes = NULL; - size_t bytes_len = 0; - unsigned *attrs_len; - - if ((err = locate_attributes(block, chunk, &bytes, &bytes_len, &attrs_len)) != HAL_OK) - goto done; - - updated_attributes_len += *attrs_len; - -#if KS_SET_ATTRIBUTES_SINGLE_BLOCK_UPDATE_FAST_PATH - - hal_pkey_attribute_t attrs[*attrs_len + attributes_len]; - size_t total; - - if ((err = hal_ks_attribute_scan(bytes, bytes_len, attrs, *attrs_len, &total)) != HAL_OK) - goto done; - - for (int i = 0; err == HAL_OK && i < attributes_len; i++) - if (attributes[i].length == HAL_PKEY_ATTRIBUTE_NIL) - err = hal_ks_attribute_delete(bytes, bytes_len, attrs, attrs_len, &total, - attributes[i].type); - else - err = hal_ks_attribute_insert(bytes, bytes_len, attrs, attrs_len, &total, - attributes[i].type, - attributes[i].value, - attributes[i].length); - - if (err != HAL_OK) - cache_release(block); - - if (err == HAL_ERROR_RESULT_TOO_LONG) - continue; - - if (err == HAL_OK) - err = block_update(b, block, &slot->name, chunk, &hint); - - goto done; - -#endif /* KS_SET_ATTRIBUTES_SINGLE_BLOCK_UPDATE_FAST_PATH */ - - } while (++chunk < block->header.total_chunks); - - /* - * If we get here, we're on the slow path, which requires rewriting - * all the chunks in this object but which can also add or remove - * chunks from this object. We need to keep track of all the old - * chunks so we can zero them at the end, and because we can't zero - * them until we've written out the new chunks, we need enough free - * blocks to hold all the new chunks. - * - * Calculating all of this is extremely tedious, but flash writes - * are so much more expensive than anything else we do here that - * it's almost certainly worth it. - * - * We don't need the attribute values to compute the sizes, just the - * attribute sizes, so we scan all the existing blocks, build up a - * structure with the current attribute types and sizes, modify that - * according to our arguments, and compute the needed size. Once we - * have that, we can start rewriting existing blocks. We put all - * the new stuff at the end, which simplifies this slightly. - * - * In theory, this process never requires us to have more than two - * blocks in memory at the same time (source and destination when - * copying across chunk boundaries), but having enough cache buffers - * to keep the whole set in memory will almost certainly make this - * run faster. - */ - - hal_pkey_attribute_t updated_attributes[updated_attributes_len]; - const unsigned total_chunks_old = block->header.total_chunks; - size_t bytes_available = 0; - - updated_attributes_len = 0; - - /* - * Phase 0.1: Walk the old chunks to populate updated_attributes[]. - * This also initializes bytes_available, since we can only get that - * by reading old chunk zero. - */ - - for (chunk = 0; chunk < total_chunks_old; chunk++) { - int hint = slot->hint + chunk; - - if ((err = hal_ks_index_find(&db.ksi, &slot->name, chunk, &b, &hint)) != HAL_OK || - (err = block_read_cached(b, &block)) != HAL_OK) - goto done; - - if (block->header.this_chunk != chunk) { - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - - cache_mark_used(block, b); - - uint8_t *bytes = NULL; - size_t bytes_len = 0; - unsigned *attrs_len; - - if ((err = locate_attributes(block, chunk, &bytes, &bytes_len, &attrs_len)) != HAL_OK) - goto done; - - hal_pkey_attribute_t attrs[*attrs_len]; - size_t total; - - if ((err = hal_ks_attribute_scan(bytes, bytes_len, attrs, *attrs_len, &total)) != HAL_OK) - goto done; - - if (chunk == 0) - bytes_available = bytes_len; - - for (int i = 0; i < *attrs_len; i++) { - - if (updated_attributes_len >= sizeof(updated_attributes)/sizeof(*updated_attributes)) { - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - - updated_attributes[updated_attributes_len].type = attrs[i].type; - updated_attributes[updated_attributes_len].length = attrs[i].length; - updated_attributes[updated_attributes_len].value = NULL; - updated_attributes_len++; - } - } - - /* - * Phase 0.2: Merge new attributes into updated_attributes[]. - * For each new attribute type, mark any existing attributes of that - * type for deletion. Append new attributes to updated_attributes[]. - */ - - for (int i = 0; i < attributes_len; i++) { - - for (int j = 0; j < updated_attributes_len; j++) - if (updated_attributes[j].type == attributes[i].type) - updated_attributes[j].length = HAL_PKEY_ATTRIBUTE_NIL; - - if (updated_attributes_len >= sizeof(updated_attributes)/sizeof(*updated_attributes)) { - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - - updated_attributes[updated_attributes_len].type = attributes[i].type; - updated_attributes[updated_attributes_len].length = attributes[i].length; - updated_attributes[updated_attributes_len].value = attributes[i].value; - updated_attributes_len++; - } - - /* - * Phase 0.3: Prune trailing deletion actions: we don't need them to - * maintain synchronization with existing attributes, and doing so - * simplifies logic for updating the final new chunk. - */ - - while (updated_attributes_len > 0 && - updated_attributes[updated_attributes_len - 1].length == HAL_PKEY_ATTRIBUTE_NIL) - --updated_attributes_len; - - /* - * Phase 0.4: Figure out how many chunks all this will occupy. - */ - - chunk = 0; - - for (int i = 0; i < updated_attributes_len; i++) { - - if (updated_attributes[i].length == HAL_PKEY_ATTRIBUTE_NIL) - continue; - - const size_t needed = hal_ks_attribute_header_size + updated_attributes[i].length; - - if (needed > bytes_available) { - bytes_available = SIZEOF_FLASH_ATTRIBUTE_BLOCK_ATTRIBUTES; - chunk++; - } - - if (needed > bytes_available) { - err = HAL_ERROR_RESULT_TOO_LONG; - goto done; - } - - bytes_available -= needed; - } - - const unsigned total_chunks_new = chunk + 1; - - /* - * If there aren't enough free blocks, give up now, before changing anything. - */ - - if (db.ksi.used + total_chunks_new > db.ksi.size) { - err = HAL_ERROR_NO_KEY_INDEX_SLOTS; - goto done; - } - - /* - * Phase 1: Deprecate all the old chunks, remember where they were. - */ - - unsigned old_blocks[total_chunks_old]; - - for (chunk = 0; chunk < total_chunks_old; chunk++) { - int hint = slot->hint + chunk; - if ((err = hal_ks_index_find(&db.ksi, &slot->name, chunk, &b, &hint)) != HAL_OK || - (err = block_deprecate(b)) != HAL_OK) - goto done; - old_blocks[chunk] = b; - } - - /* - * Phase 2: Write new chunks, copying attributes from old chunks or - * from attributes[], as needed. - */ - - { - hal_pkey_attribute_t old_attrs[updated_attributes_len], new_attrs[updated_attributes_len]; - unsigned *old_attrs_len = NULL, *new_attrs_len = NULL; - flash_block_t *old_block = NULL, *new_block = NULL; - uint8_t *old_bytes = NULL, *new_bytes = NULL; - size_t old_bytes_len = 0, new_bytes_len = 0; - unsigned old_chunk = 0, new_chunk = 0; - size_t old_total = 0, new_total = 0; - - int updated_attributes_i = 0, old_attrs_i = 0; - - uint32_t new_attr_type; - size_t new_attr_length; - const uint8_t *new_attr_value; - - while (updated_attributes_i < updated_attributes_len) { - - if (old_chunk >= total_chunks_old || new_chunk >= total_chunks_new) { - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - - /* - * If we've gotten as far as new data that comes from - * attributes[], we have it in hand and can just copy it. - */ - - if (updated_attributes_len - updated_attributes_i <= attributes_len) { - new_attr_type = updated_attributes[updated_attributes_i].type; - new_attr_length = updated_attributes[updated_attributes_i].length; - new_attr_value = updated_attributes[updated_attributes_i].value; - } - - /* - * Otherwise, we have to read it from an old block, which may in - * turn require reading in the next old block. - */ - - else { - - if (old_block == NULL) { - - if ((err = block_read_cached(old_blocks[old_chunk], &old_block)) != HAL_OK) - goto done; - - if (old_block->header.this_chunk != old_chunk) { - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - - if ((err = locate_attributes(old_block, old_chunk, - &old_bytes, &old_bytes_len, &old_attrs_len)) != HAL_OK || - (err = hal_ks_attribute_scan(old_bytes, old_bytes_len, - old_attrs, *old_attrs_len, &old_total)) != HAL_OK) - goto done; - - old_attrs_i = 0; - } - - if (old_attrs_i >= *old_attrs_len) { - old_chunk++; - old_block = NULL; - continue; - } - - new_attr_type = old_attrs[old_attrs_i].type; - new_attr_length = old_attrs[old_attrs_i].length; - new_attr_value = old_attrs[old_attrs_i].value; - - if (new_attr_type != updated_attributes[updated_attributes_i].type) { - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - - old_attrs_i++; - } - - /* - * Unless this is a deletion, we should have something to write. - */ - - if (new_attr_length != HAL_PKEY_ATTRIBUTE_NIL && new_attr_value == NULL) { - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - - /* - * Initialize the new block if necessary. If it's the new chunk - * zero, we need to copy all the non-attribute data from the old - * chunk zero; otherwise, it's a new empty attribute block. - */ - - if (new_block == NULL) { - - new_block = cache_pick_lru(); - memset(new_block, 0xFF, sizeof(*new_block)); - - if (new_chunk == 0) { - flash_block_t *tmp_block; - if ((err = block_read_cached(old_blocks[0], &tmp_block)) != HAL_OK) - goto done; - if (tmp_block->header.this_chunk != 0) { - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - new_block->header.block_type = BLOCK_TYPE_KEY; - new_block->key.name = slot->name; - new_block->key.type = tmp_block->key.type; - new_block->key.curve = tmp_block->key.curve; - new_block->key.flags = tmp_block->key.flags; - new_block->key.der_len = tmp_block->key.der_len; - new_block->key.attributes_len = 0; - memcpy(new_block->key.der, tmp_block->key.der, tmp_block->key.der_len); - } - else { - new_block->header.block_type = BLOCK_TYPE_ATTR; - new_block->attr.name = slot->name; - new_block->attr.attributes_len = 0; - } - - new_block->header.block_status = BLOCK_STATUS_LIVE; - new_block->header.total_chunks = total_chunks_new; - new_block->header.this_chunk = new_chunk; - - if ((err = locate_attributes(new_block, new_chunk, - &new_bytes, &new_bytes_len, &new_attrs_len)) != HAL_OK) - goto done; - - new_total = 0; - } - - /* - * After all that setup, we finally get to write the frelling attribute. - */ - - if (new_attr_length != HAL_PKEY_ATTRIBUTE_NIL) - err = hal_ks_attribute_insert(new_bytes, new_bytes_len, new_attrs, new_attrs_len, &new_total, - new_attr_type, new_attr_value, new_attr_length); - - /* - * Figure out what to do next: immediately loop for next - * attribute, write current block, or bail out. - */ - - switch (err) { - case HAL_OK: - if (++updated_attributes_i < updated_attributes_len) - continue; - break; - case HAL_ERROR_RESULT_TOO_LONG: - if (new_chunk > 0 && new_attrs_len == 0) - goto done; - break; - default: - goto done; - } - - /* - * If we get here, either the current new block is full or we - * finished the last block, so we need to write it out. - */ - - int hint = slot->hint + new_chunk; - - if (new_chunk < total_chunks_old) - err = hal_ks_index_replace(&db.ksi, &slot->name, new_chunk, &b, &hint); - else - err = hal_ks_index_add( &db.ksi, &slot->name, new_chunk, &b, &hint); - - if (err != HAL_OK || (err = block_write(b, new_block)) != HAL_OK) - goto done; - - cache_mark_used(new_block, b); - - new_block = NULL; - new_chunk++; - } - - /* - * If number of blocks shrank, we need to clear trailing entries from the index. - */ - - for (old_chunk = total_chunks_new; old_chunk < total_chunks_old; old_chunk++) { - int hint = slot->hint + old_chunk; - - err = hal_ks_index_delete(&db.ksi, &slot->name, old_chunk, NULL, &hint); - - if (err != HAL_OK) - goto done; - } - - } - - /* - * Phase 3: Zero the old chunks we deprecated in phase 1. - */ - - for (chunk = 0; chunk < total_chunks_old; chunk++) - if ((err = block_zero(old_blocks[chunk])) != HAL_OK) - goto done; - - err = HAL_OK; - - } - - done: - hal_ks_unlock(); - return err; - -#warning What happens if something goes wrong partway through this awful mess? - // We're left in a state with all the old blocks deprecated and - // (maybe) some of the new blocks current, need to clean that up. - // Would be nice if we could just reuse the keystore initialization - // code, but don't quite see how (yet?). -} - -static hal_error_t ks_get_attributes(hal_ks_t *ks, - hal_pkey_slot_t *slot, - hal_pkey_attribute_t *attributes, - const unsigned attributes_len, - uint8_t *attributes_buffer, - const size_t attributes_buffer_len) -{ - if (ks != &db.ks || slot == NULL || attributes == NULL || attributes_len == 0 || - attributes_buffer == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - for (int i = 0; i < attributes_len; i++) { - attributes[i].length = 0; - attributes[i].value = NULL; - } - - uint8_t *abuf = attributes_buffer; - flash_block_t *block = NULL; - unsigned chunk = 0; - unsigned found = 0; - hal_error_t err = HAL_OK; - unsigned b; - - hal_ks_lock(); - - do { - int hint = slot->hint + chunk; - - if ((err = hal_ks_index_find(&db.ksi, &slot->name, chunk, &b, &hint)) != HAL_OK || - (err = block_read_cached(b, &block)) != HAL_OK) - goto done; - - if (block->header.this_chunk != chunk) { - err = HAL_ERROR_IMPOSSIBLE; - goto done; - } - - if (chunk == 0) - slot->hint = hint; - - cache_mark_used(block, b); - - uint8_t *bytes = NULL; - size_t bytes_len = 0; - unsigned *attrs_len; - - if ((err = locate_attributes(block, chunk, &bytes, &bytes_len, &attrs_len)) != HAL_OK) - goto done; - - if (*attrs_len == 0) - continue; - - hal_pkey_attribute_t attrs[*attrs_len]; - - if ((err = hal_ks_attribute_scan(bytes, bytes_len, attrs, *attrs_len, NULL)) != HAL_OK) - goto done; - - for (int i = 0; i < attributes_len; i++) { - - if (attributes[i].length > 0) - continue; - - int j = 0; - while (j < *attrs_len && attrs[j].type != attributes[i].type) - j++; - if (j >= *attrs_len) - continue; - found++; - - attributes[i].length = attrs[j].length; - - if (attributes_buffer_len == 0) - continue; - - if (attrs[j].length > attributes_buffer + attributes_buffer_len - abuf) { - err = HAL_ERROR_RESULT_TOO_LONG; - goto done; - } - - memcpy(abuf, attrs[j].value, attrs[j].length); - attributes[i].value = abuf; - abuf += attrs[j].length; - } - - } while (found < attributes_len && ++chunk < block->header.total_chunks); - - if (found < attributes_len && attributes_buffer_len > 0) - err = HAL_ERROR_ATTRIBUTE_NOT_FOUND; - else - err = HAL_OK; - - done: - hal_ks_unlock(); - return err; -} - -const hal_ks_driver_t hal_ks_token_driver[1] = {{ - .init = ks_init, - .shutdown = ks_shutdown, - .open = ks_open, - .close = ks_close, - .store = ks_store, - .fetch = ks_fetch, - .delete = ks_delete, - .match = ks_match, - .set_attributes = ks_set_attributes, - .get_attributes = ks_get_attributes -}}; - -/* - * The remaining functions aren't really part of the keystore API per se, - * but they all involve non-key data which we keep in the keystore - * because it's the flash we've got. - */ - -/* - * Special bonus init routine used only by the bootloader, so that it - * can read PINs set by the main firmware. Yes, this is a kludge. We - * could of course call the real ks_init() routine instead, but it's - * slow, and we don't want to allow anything that would modify the - * flash here, so having a special entry point for this kludge is - * simplest, overall. Sigh. - */ - -void hal_ks_init_read_only_pins_only(void) -{ - unsigned b, best_seen = ~0; - flash_block_t block[1]; - - hal_ks_lock(); - - for (b = 0; b < NUM_FLASH_BLOCKS; b++) { - if (block_read(b, block) != HAL_OK || block_get_type(block) != BLOCK_TYPE_PIN) - continue; - best_seen = b; - if (block_get_status(block) == BLOCK_STATUS_LIVE) - break; - } - - if (b != best_seen && best_seen != ~0 && block_read(best_seen, block) != HAL_OK) - best_seen = ~0; - - if (best_seen == ~0) { - memset(block, 0xFF, sizeof(*block)); - block->pin.wheel_pin = hal_last_gasp_pin; - } - - db.wheel_pin = block->pin.wheel_pin; - db.so_pin = block->pin.so_pin; - db.user_pin = block->pin.user_pin; - - hal_ks_unlock(); -} - -/* - * Fetch PIN. This is always cached, so just returned cached value. - */ - -hal_error_t hal_get_pin(const hal_user_t user, - const hal_ks_pin_t **pin) -{ - if (pin == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - hal_error_t err = HAL_OK; - - hal_ks_lock(); - - switch (user) { - case HAL_USER_WHEEL: *pin = &db.wheel_pin; break; - case HAL_USER_SO: *pin = &db.so_pin; break; - case HAL_USER_NORMAL: *pin = &db.user_pin; break; - default: err = HAL_ERROR_BAD_ARGUMENTS; - } - - hal_ks_unlock(); - - return err; -} - -/* - * Fetch PIN block. hint = 0 because we know that the all-zeros UUID - * should always sort to first slot in the index. - */ - -static hal_error_t fetch_pin_block(unsigned *b, flash_block_t **block) -{ - if (block == NULL) - return HAL_ERROR_IMPOSSIBLE; - - hal_error_t err; - int hint = 0; - unsigned b_; - - if (b == NULL) - b = &b_; - - if ((err = hal_ks_index_find(&db.ksi, &pin_uuid, 0, b, &hint)) != HAL_OK || - (err = block_read_cached(*b, block)) != HAL_OK) - return err; - - cache_mark_used(*block, *b); - - if (block_get_type(*block) != BLOCK_TYPE_PIN) - return HAL_ERROR_IMPOSSIBLE; - - return HAL_OK; -} - -/* - * Update the PIN block. This block should always be present, but we - * have to do the zombie jamboree to make sure we write the new PIN - * block before destroying the old one. hint = 0 because we know that - * the all-zeros UUID should always sort to first slot in the index. - */ - -static hal_error_t update_pin_block(const unsigned b, - flash_block_t *block, - const flash_pin_block_t * const new_data) -{ - if (block == NULL || new_data == NULL || block_get_type(block) != BLOCK_TYPE_PIN) - return HAL_ERROR_IMPOSSIBLE; - - int hint = 0; - - block->pin = *new_data; - - return block_update(b, block, &pin_uuid, 0, &hint); -} - -/* - * Change a PIN. - */ - -hal_error_t hal_set_pin(const hal_user_t user, - const hal_ks_pin_t * const pin) -{ - if (pin == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - flash_block_t *block; - hal_error_t err; - unsigned b; - - hal_ks_lock(); - - if ((err = fetch_pin_block(&b, &block)) != HAL_OK) - goto done; - - flash_pin_block_t new_data = block->pin; - hal_ks_pin_t *dp, *bp; - - switch (user) { - case HAL_USER_WHEEL: bp = &new_data.wheel_pin; dp = &db.wheel_pin; break; - case HAL_USER_SO: bp = &new_data.so_pin; dp = &db.so_pin; break; - case HAL_USER_NORMAL: bp = &new_data.user_pin; dp = &db.user_pin; break; - default: err = HAL_ERROR_BAD_ARGUMENTS; goto done; - } - - const hal_ks_pin_t old_pin = *dp; - *dp = *bp = *pin; - - if ((err = update_pin_block(b, block, &new_data)) != HAL_OK) - *dp = old_pin; - - done: - hal_ks_unlock(); - return err; -} - -#if HAL_MKM_FLASH_BACKUP_KLUDGE - -/* - * Horrible insecure kludge in lieu of a battery for the MKM. - * - * API here is a little strange: all calls pass a length parameter, - * but any length other than the compiled in constant just returns an - * immediate error, there's no notion of buffer max length vs buffer - * used length, querying for the size of buffer really needed, or - * anything like that. - * - * We might want to rewrite this some day, if we don't replace it with - * a battery first. For now we just preserve the API as we found it - * while re-implementing it on top of the new keystore. - */ - -hal_error_t hal_mkm_flash_read_no_lock(uint8_t *buf, const size_t len) -{ - if (buf != NULL && len != KEK_LENGTH) - return HAL_ERROR_MASTERKEY_BAD_LENGTH; - - flash_block_t *block; - hal_error_t err; - unsigned b; - - if ((err = fetch_pin_block(&b, &block)) != HAL_OK) - return err; - - if (block->pin.kek_set != FLASH_KEK_SET) - return HAL_ERROR_MASTERKEY_NOT_SET; - - if (buf != NULL) - memcpy(buf, block->pin.kek, len); - - return HAL_OK; -} - -hal_error_t hal_mkm_flash_read(uint8_t *buf, const size_t len) -{ - hal_ks_lock(); - const hal_error_t err = hal_mkm_flash_read_no_lock(buf, len); - hal_ks_unlock(); - return err; -} - -hal_error_t hal_mkm_flash_write(const uint8_t * const buf, const size_t len) -{ - if (buf == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - if (len != KEK_LENGTH) - return HAL_ERROR_MASTERKEY_BAD_LENGTH; - - flash_block_t *block; - hal_error_t err; - unsigned b; - - hal_ks_lock(); - - if ((err = fetch_pin_block(&b, &block)) != HAL_OK) - goto done; - - flash_pin_block_t new_data = block->pin; - - new_data.kek_set = FLASH_KEK_SET; - memcpy(new_data.kek, buf, len); - - err = update_pin_block(b, block, &new_data); - - done: - hal_ks_unlock(); - return err; -} - -hal_error_t hal_mkm_flash_erase(const size_t len) -{ - if (len != KEK_LENGTH) - return HAL_ERROR_MASTERKEY_BAD_LENGTH; - - flash_block_t *block; - hal_error_t err; - unsigned b; - - hal_ks_lock(); - - if ((err = fetch_pin_block(&b, &block)) != HAL_OK) - goto done; - - flash_pin_block_t new_data = block->pin; - - new_data.kek_set = FLASH_KEK_SET; - memset(new_data.kek, 0, len); - - err = update_pin_block(b, block, &new_data); - - done: - hal_ks_unlock(); - return err; -} - -#endif /* HAL_MKM_FLASH_BACKUP_KLUDGE */ - - -/* - * Local variables: - * indent-tabs-mode: nil - * End: - */ @@ -3,7 +3,7 @@ * ---------- * Keystore index API. This is internal within libhal. * - * Copyright (c) 2016, NORDUnet A/S All rights reserved. + * Copyright (c) 2016-2017, 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 @@ -32,54 +32,37 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include <stddef.h> #include <string.h> -#include <assert.h> #include "hal.h" #include "hal_internal.h" - -/* - * Compare two hal_ks_name_t objects. - */ - -static inline int ks_name_cmp(const hal_ks_name_t * const name1, const hal_ks_name_t * const name2) -{ - assert(name1 != NULL && name2 != NULL); - - int cmp = hal_uuid_cmp(&name1->name, &name2->name); - - if (cmp == 0) - cmp = ((int) name1->chunk) - ((int) name2->chunk); - - return cmp; -} +#include "ks.h" /* * Find a block in the index, return true (found) or false (not found). * "where" indicates the name's position, or the position of the first free block. * * NB: This does NOT return a block number, it returns an index into - * ksi->index[]. + * ks->index[]. */ -static int ks_find(const hal_ks_index_t * const ksi, - const hal_uuid_t * const uuid, - const uint8_t chunk, +static int ks_find(const hal_ks_t * const ks, + const hal_uuid_t * const uuid, const int * const hint, - int *where) + int *where) { - assert(ksi != NULL && ksi->index != NULL && ksi->names != NULL && uuid != NULL && where != NULL); + if (ks == NULL || ks->index == NULL || ks->names == NULL || uuid == NULL || where == NULL) + return 0; - const hal_ks_name_t name = { *uuid, chunk }; - - if (hint != NULL && *hint >= 0 && *hint < ksi->used && - ks_name_cmp(&name, &ksi->names[ksi->index[*hint]]) == 0) { + if (hint != NULL && *hint >= 0 && *hint < ks->used && + hal_uuid_cmp(uuid, &ks->names[ks->index[*hint]]) == 0) { *where = *hint; return 1; } int lo = -1; - int hi = ksi->used; + int hi = ks->used; for (;;) { int m = (lo + hi) / 2; @@ -87,7 +70,7 @@ static int ks_find(const hal_ks_index_t * const ksi, *where = hi; return 0; } - const int cmp = ks_name_cmp(&name, &ksi->names[ksi->index[m]]); + const int cmp = hal_uuid_cmp(uuid, &ks->names[ks->index[m]]); if (cmp < 0) hi = m; else if (cmp > 0) @@ -107,125 +90,97 @@ static int ks_find(const hal_ks_index_t * const ksi, * heapsort is easy and works well with data already in place. */ -static inline void ks_heapsift(hal_ks_index_t *ksi, int parent, const int end) +static inline hal_error_t ks_heapsift(hal_ks_t *ks, int parent, const int end) { - assert(ksi != NULL && ksi->index != NULL && ksi->names != NULL && - parent >= 0 && end >= parent); + if (ks == NULL || ks->index == NULL || ks->names == NULL || parent < 0 || end < parent) + return HAL_ERROR_IMPOSSIBLE; + for (;;) { const int left_child = parent * 2 + 1; const int right_child = parent * 2 + 2; int biggest = parent; - if (left_child <= end && ks_name_cmp(&ksi->names[ksi->index[biggest]], - &ksi->names[ksi->index[left_child]]) < 0) + if (left_child <= end && hal_uuid_cmp(&ks->names[ks->index[biggest]], + &ks->names[ks->index[left_child]]) < 0) biggest = left_child; - if (right_child <= end && ks_name_cmp(&ksi->names[ksi->index[biggest]], - &ksi->names[ksi->index[right_child]]) < 0) + if (right_child <= end && hal_uuid_cmp(&ks->names[ks->index[biggest]], + &ks->names[ks->index[right_child]]) < 0) biggest = right_child; if (biggest == parent) - return; - const uint16_t tmp = ksi->index[biggest]; - ksi->index[biggest] = ksi->index[parent]; - ksi->index[parent] = tmp; + return HAL_OK; + const uint16_t tmp = ks->index[biggest]; + ks->index[biggest] = ks->index[parent]; + ks->index[parent] = tmp; parent = biggest; } } -static inline void ks_heapsort(hal_ks_index_t *ksi) +hal_error_t hal_ks_index_heapsort(hal_ks_t *ks) { - assert(ksi != NULL && ksi->index != NULL && ksi->names != NULL); - if (ksi->used < 2) - return; - for (int i = (ksi->used - 2) / 2; i >= 0; i--) - ks_heapsift(ksi, i, ksi->used - 1); - for (int i = ksi->used - 1; i > 0; i--) { - const uint16_t tmp = ksi->index[i]; - ksi->index[i] = ksi->index[0]; - ksi->index[0] = tmp; - ks_heapsift(ksi, 0, i - 1); - } -} - -/* - * Perform a consistency check on the index. - */ - -#define fsck(_ksi) \ - do { hal_error_t _err = hal_ks_index_fsck(_ksi); if (_err != HAL_OK) return _err; } while (0) - + if (ks == NULL || ks->index == NULL || ks->names == NULL) + return HAL_ERROR_IMPOSSIBLE; -hal_error_t hal_ks_index_fsck(hal_ks_index_t *ksi) -{ - if (ksi == NULL || ksi->index == NULL || ksi->names == NULL || - ksi->size == 0 || ksi->used > ksi->size) - return HAL_ERROR_BAD_ARGUMENTS; + if (ks->used < 2) + return HAL_OK; - for (int i = 0; i < ksi->used; i++) { + hal_error_t err; - const int cmp = i == 0 ? -1 : hal_uuid_cmp(&ksi->names[ksi->index[i - 1]].name, - &ksi->names[ksi->index[i ]].name); + for (int i = (ks->used - 2) / 2; i >= 0; i--) + if ((err = ks_heapsift(ks, i, ks->used - 1)) != HAL_OK) + return err; - const uint8_t prev_chunk = i == 0 ? 0 : ksi->names[ksi->index[i - 1]].chunk; - const uint8_t cur_chunk = ksi->names[ksi->index[i ]].chunk; - - if (cmp > 0) - return HAL_ERROR_KSI_INDEX_UUID_MISORDERED; - - if (cur_chunk > 0 && cmp != 0) - return HAL_ERROR_KSI_INDEX_CHUNK_ORPHANED; - - if (cur_chunk > 0 && prev_chunk + 1 < cur_chunk) - return HAL_ERROR_KSI_INDEX_CHUNK_MISSING; - - if (cur_chunk > 0 && prev_chunk + 1 > cur_chunk) - return HAL_ERROR_KSI_INDEX_CHUNK_OVERLAPS; + for (int i = ks->used - 1; i > 0; i--) { + const uint16_t tmp = ks->index[i]; + ks->index[i] = ks->index[0]; + ks->index[0] = tmp; + if ((err = ks_heapsift(ks, 0, i - 1)) != HAL_OK) + return err; } return HAL_OK; } /* - * Set up the index. Only setup task we have at the moment is sorting the index. + * Perform a consistency check on the index. */ -hal_error_t hal_ks_index_setup(hal_ks_index_t *ksi) +#define fsck(_ks) \ + do { hal_error_t _err = hal_ks_index_fsck(_ks); if (_err != HAL_OK) return _err; } while (0) + + +hal_error_t hal_ks_index_fsck(hal_ks_t *ks) { - if (ksi == NULL || ksi->index == NULL || ksi->names == NULL || - ksi->size == 0 || ksi->used > ksi->size) + if (ks == NULL || ks->index == NULL || ks->names == NULL || + ks->size == 0 || ks->used > ks->size) return HAL_ERROR_BAD_ARGUMENTS; - ks_heapsort(ksi); - - /* - * One might think we should fsck here, but errors in the index - * at this point probably relate to errors in the supplied data, - * which only the driver knows how to clean up. - */ + for (int i = 1; i < ks->used; i++) + if (hal_uuid_cmp(&ks->names[ks->index[i - 1]], &ks->names[ks->index[i]]) >= 0) + return HAL_ERROR_KS_INDEX_UUID_MISORDERED; return HAL_OK; } /* - * Find a single block by name and chunk number. + * Find a single block by name. */ -hal_error_t hal_ks_index_find(hal_ks_index_t *ksi, - const hal_uuid_t * const name, - const unsigned chunk, - unsigned *blockno, +hal_error_t hal_ks_index_find(hal_ks_t *ks, + const hal_uuid_t * const name, + unsigned *blockno, int *hint) { - if (ksi == NULL || ksi->index == NULL || ksi->names == NULL || - ksi->size == 0 || ksi->used > ksi->size || name == NULL) + if (ks == NULL || ks->index == NULL || ks->names == NULL || + ks->size == 0 || ks->used > ks->size || name == NULL) return HAL_ERROR_BAD_ARGUMENTS; int where; - fsck(ksi); + fsck(ks); - int ok = ks_find(ksi, name, chunk, hint, &where); + int ok = ks_find(ks, name, hint, &where); if (blockno != NULL) - *blockno = ksi->index[where]; + *blockno = ks->index[where]; if (hint != NULL) *hint = where; @@ -234,73 +189,26 @@ hal_error_t hal_ks_index_find(hal_ks_index_t *ksi, } /* - * Find all blocks with the given name. - * If 'strict' is set, expect it to be a well-ordered set of chunks. - */ - -hal_error_t hal_ks_index_find_range(hal_ks_index_t *ksi, - const hal_uuid_t * const name, - const unsigned max_blocks, - unsigned *n_blocks, - unsigned *blocknos, - int *hint, - const int strict) -{ - if (ksi == NULL || ksi->index == NULL || ksi->names == NULL || - ksi->size == 0 || ksi->used > ksi->size || name == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - int where; - - fsck(ksi); - - if (!ks_find(ksi, name, 0, hint, &where)) - return HAL_ERROR_KEY_NOT_FOUND; - - int n = 0; - - for (int i = where; i < ksi->used && !hal_uuid_cmp(name, &ksi->names[ksi->index[i]].name); i++) { - if (strict && n != ksi->names[ksi->index[i]].chunk) - return HAL_ERROR_IMPOSSIBLE; - if (blocknos != NULL && n < max_blocks) - blocknos[n] = ksi->index[i]; - n++; - } - - if (n_blocks != NULL) - *n_blocks = n; - - if (hint != NULL) - *hint = where; - - if (blocknos != NULL && n > max_blocks) - return HAL_ERROR_RESULT_TOO_LONG; - - return HAL_OK; -} - -/* * Add a single block to the index. */ -hal_error_t hal_ks_index_add(hal_ks_index_t *ksi, - const hal_uuid_t * const name, - const unsigned chunk, - unsigned *blockno, +hal_error_t hal_ks_index_add(hal_ks_t *ks, + const hal_uuid_t * const name, + unsigned *blockno, int *hint) { - if (ksi == NULL || ksi->index == NULL || ksi->names == NULL || - ksi->size == 0 || ksi->used > ksi->size || name == NULL) + if (ks == NULL || ks->index == NULL || ks->names == NULL || + ks->size == 0 || ks->used > ks->size || name == NULL) return HAL_ERROR_BAD_ARGUMENTS; - if (ksi->used == ksi->size) + if (ks->used == ks->size) return HAL_ERROR_NO_KEY_INDEX_SLOTS; int where; - fsck(ksi); + fsck(ks); - if (ks_find(ksi, name, chunk, hint, &where)) + if (ks_find(ks, name, hint, &where)) return HAL_ERROR_KEY_NAME_IN_USE; /* @@ -308,12 +216,11 @@ hal_error_t hal_ks_index_add(hal_ks_index_t *ksi, * index up by one slot so we can insert the new block number. */ - const size_t len = (ksi->used - where) * sizeof(*ksi->index); - const uint16_t b = ksi->index[ksi->used++]; - memmove(&ksi->index[where + 1], &ksi->index[where], len); - ksi->index[where] = b; - ksi->names[b].name = *name; - ksi->names[b].chunk = chunk; + const size_t len = (ks->used - where) * sizeof(*ks->index); + const uint16_t b = ks->index[ks->used++]; + memmove(&ks->index[where + 1], &ks->index[where], len); + ks->index[where] = b; + ks->names[b] = *name; if (blockno != NULL) *blockno = b; @@ -321,7 +228,7 @@ hal_error_t hal_ks_index_add(hal_ks_index_t *ksi, if (hint != NULL) *hint = where; - fsck(ksi); + fsck(ks); return HAL_OK; } @@ -330,33 +237,32 @@ hal_error_t hal_ks_index_add(hal_ks_index_t *ksi, * Delete a single block from the index. */ -hal_error_t hal_ks_index_delete(hal_ks_index_t *ksi, - const hal_uuid_t * const name, - const unsigned chunk, - unsigned *blockno, +hal_error_t hal_ks_index_delete(hal_ks_t *ks, + const hal_uuid_t * const name, + unsigned *blockno, int *hint) { - if (ksi == NULL || ksi->index == NULL || ksi->names == NULL || - ksi->size == 0 || ksi->used > ksi->size || name == NULL) + if (ks == NULL || ks->index == NULL || ks->names == NULL || + ks->size == 0 || ks->used > ks->size || name == NULL) return HAL_ERROR_BAD_ARGUMENTS; int where; - fsck(ksi); + fsck(ks); - if (ksi->used == 0 || !ks_find(ksi, name, chunk, hint, &where)) + if (ks->used == 0 || !ks_find(ks, name, hint, &where)) return HAL_ERROR_KEY_NOT_FOUND; /* * Free the block and stuff it at the end of the free list. */ - const size_t len = (ksi->size - where - 1) * sizeof(*ksi->index); - const uint16_t b = ksi->index[where]; - memmove(&ksi->index[where], &ksi->index[where + 1], len); - ksi->index[ksi->size - 1] = b; - ksi->used--; - memset(&ksi->names[b], 0, sizeof(ksi->names[b])); + const size_t len = (ks->size - where - 1) * sizeof(*ks->index); + const uint16_t b = ks->index[where]; + memmove(&ks->index[where], &ks->index[where + 1], len); + ks->index[ks->size - 1] = b; + ks->used--; + memset(&ks->names[b], 0, sizeof(ks->names[b])); if (blockno != NULL) *blockno = b; @@ -364,94 +270,34 @@ hal_error_t hal_ks_index_delete(hal_ks_index_t *ksi, if (hint != NULL) *hint = where; - fsck(ksi); - - return HAL_OK; -} - -/* - * Delete all blocks with the given name. If blocknos is NULL, return a - * count of the matching blocks without deleting anything. - */ - -hal_error_t hal_ks_index_delete_range(hal_ks_index_t *ksi, - const hal_uuid_t * const name, - const unsigned max_blocks, - unsigned *n_blocks, - unsigned *blocknos, - int *hint) -{ - if (ksi == NULL || ksi->index == NULL || ksi->names == NULL || - ksi->size == 0 || ksi->used > ksi->size || name == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - int where; - - fsck(ksi); - - if (ksi->used == 0 || !ks_find(ksi, name, 0, hint, &where)) - return HAL_ERROR_KEY_NOT_FOUND; - - int n = 0; - - for (int i = where; i < ksi->used && !hal_uuid_cmp(name, &ksi->names[ksi->index[i]].name); i++) { - if (n != ksi->names[ksi->index[i]].chunk) - return HAL_ERROR_IMPOSSIBLE; - if (blocknos != NULL && n < max_blocks) - blocknos[n] = ksi->index[i]; - n++; - } - - if (n_blocks != NULL) - *n_blocks = n; - - /* - * Free the blocks and stuff them at the end of the free list. - */ - - if (blocknos != NULL) { - if (n > max_blocks) - return HAL_ERROR_RESULT_TOO_LONG; - const size_t len = (ksi->size - where - n) * sizeof(*ksi->index); - memmove(&ksi->index[where], &ksi->index[where + n], len); - ksi->used -= n; - for (int i = 0; i < n; i++) { - ksi->index[ksi->size - n + i] = blocknos[i]; - memset(&ksi->names[blocknos[i]], 0, sizeof(ksi->names[blocknos[i]])); - } - where = -1; - } - - if (hint != NULL) - *hint = where; - - fsck(ksi); + fsck(ks); return HAL_OK; } /* - * Replace a single block in the index. + * Replace a single block with a new one, return new block number. + * Name of block does not change. This is an optimization of a delete + * immediately followed by an add for the same name. */ -hal_error_t hal_ks_index_replace(hal_ks_index_t *ksi, +hal_error_t hal_ks_index_replace(hal_ks_t *ks, const hal_uuid_t * const name, - const unsigned chunk, unsigned *blockno, int *hint) { - if (ksi == NULL || ksi->index == NULL || ksi->names == NULL || - ksi->size == 0 || ksi->used > ksi->size || name == NULL) + if (ks == NULL || ks->index == NULL || ks->names == NULL || + ks->size == 0 || ks->used > ks->size || name == NULL) return HAL_ERROR_BAD_ARGUMENTS; - if (ksi->used == ksi->size) + if (ks->used == ks->size) return HAL_ERROR_NO_KEY_INDEX_SLOTS; int where; - fsck(ksi); + fsck(ks); - if (ksi->used == 0 || !ks_find(ksi, name, chunk, hint, &where)) + if (ks->used == 0 || !ks_find(ks, name, hint, &where)) return HAL_ERROR_KEY_NOT_FOUND; /* @@ -459,15 +305,14 @@ hal_error_t hal_ks_index_replace(hal_ks_index_t *ksi, * block at end of free list and replace old block with new block. */ - const size_t len = (ksi->size - ksi->used - 1) * sizeof(*ksi->index); - const uint16_t b1 = ksi->index[where]; - const uint16_t b2 = ksi->index[ksi->used]; - memmove(&ksi->index[ksi->used], &ksi->index[ksi->used + 1], len); - ksi->index[ksi->size - 1] = b1; - ksi->index[where] = b2; - ksi->names[b2].name = *name; - ksi->names[b2].chunk = chunk; - memset(&ksi->names[b1], 0, sizeof(ksi->names[b1])); + const size_t len = (ks->size - ks->used - 1) * sizeof(*ks->index); + const uint16_t b1 = ks->index[where]; + const uint16_t b2 = ks->index[ks->used]; + memmove(&ks->index[ks->used], &ks->index[ks->used + 1], len); + ks->index[ks->size - 1] = b1; + ks->index[where] = b2; + ks->names[b2] = *name; + memset(&ks->names[b1], 0, sizeof(ks->names[b1])); if (blockno != NULL) *blockno = b2; @@ -475,7 +320,7 @@ hal_error_t hal_ks_index_replace(hal_ks_index_t *ksi, if (hint != NULL) *hint = where; - fsck(ksi); + fsck(ks); return HAL_OK; } diff --git a/ks_mmap.c b/ks_mmap.c deleted file mode 100644 index 066e93e..0000000 --- a/ks_mmap.c +++ /dev/null @@ -1,180 +0,0 @@ -/* - * ks_mmap.c - * --------- - * Keystore implementation over POSIX mmap(). - * - * Authors: Rob Austein - * Copyright (c) 2015, 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. - */ - -#include <unistd.h> -#include <fcntl.h> -#include <sys/mman.h> -#include <string.h> -#include <sys/errno.h> -#include <unistd.h> - -#include "hal.h" -#include "hal_internal.h" - -#ifndef HAL_KS_MMAP_FILE -#define HAL_KS_MMAP_FILE ".cryptech_hal_keystore" -#endif - -#ifndef MAP_FILE -#define MAP_FILE 0 -#endif - -/* - * Storing the KEK in with the keys it's protecting is a bad idea, but we have no better - * place to put it (real protection requires dedicated hardware, which we don't have here). - */ - -#define KEKBUF_LEN (bitsToBytes(256)) - -static hal_ks_keydb_t *db; -static uint8_t *kekbuf; - -const hal_ks_keydb_t *hal_ks_get_keydb(void) -{ - if (db != NULL) - return db; - - const char * const env = getenv("CRYPTECH_KEYSTORE"); - const char * const home = getenv("HOME"); - const char * const base = HAL_KS_MMAP_FILE; - const long pagemask = sysconf(_SC_PAGESIZE) - 1; - const size_t len = (sizeof(hal_ks_keydb_t) + KEKBUF_LEN + pagemask) & ~pagemask; - - char fn_[strlen(base) + (home == NULL ? 0 : strlen(home)) + 2]; - const char *fn = fn_; - int fd; - - if (pagemask < 0) - return NULL; - - if (env != NULL) - fn = env; - else if (home == NULL) - fn = base; - else - strcat(strcat(strcpy(fn_, home), "/"), base); - - if ((fd = open(fn, O_RDWR | O_CREAT | O_EXCL, 0600)) >= 0) { - uint8_t zeros[len]; - memset(zeros, 0, sizeof(zeros)); - (void) write(fd, zeros, sizeof(zeros)); - } - else if (errno == EEXIST) { - fd = open(fn, O_RDWR | O_CREAT, 0600); - } - - if (fd >= 0 && (db = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0)) != NULL) - kekbuf = (uint8_t *) (db + 1); - - (void) close(fd); - - return db; -} - -hal_error_t hal_ks_set_keydb(const hal_ks_key_t * const key, - const int loc, - const int updating) -{ - if (key == NULL || loc < 0 || loc >= sizeof(db->keys)/sizeof(*db->keys) || (!key->in_use != !updating)) - return HAL_ERROR_BAD_ARGUMENTS; - - db->keys[loc] = *key; - db->keys[loc].in_use = 1; - return HAL_OK; -} - -hal_error_t hal_ks_del_keydb(const int loc) -{ - if (loc < 0 || loc >= sizeof(db->keys)/sizeof(*db->keys)) - return HAL_ERROR_BAD_ARGUMENTS; - - db->keys[loc].in_use = 0; - memset(&db->keys[loc], 0, sizeof(db->keys[loc])); - return HAL_OK; -} - -hal_error_t hal_set_pin(const hal_user_t user, - const hal_ks_pin_t * const pin) -{ - if (pin == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - hal_ks_pin_t *p = NULL; - - switch (user) { - case HAL_USER_WHEEL: p = &db->wheel_pin; break; - case HAL_USER_SO: p = &db->so_pin; break; - case HAL_USER_NORMAL: p = &db->user_pin; break; - default: return HAL_ERROR_BAD_ARGUMENTS; - } - - *p = *pin; - return HAL_OK; -} - -hal_error_t hal_mkm_get_kek(uint8_t *kek, - size_t *kek_len, - const size_t kek_max) -{ - if (kek == NULL || kek_len == NULL || kek_max < bitsToBytes(128)) - return HAL_ERROR_BAD_ARGUMENTS; - - if (kekbuf == NULL) - return HAL_ERROR_IMPOSSIBLE; - - hal_error_t err; - - const size_t len = ((kek_max < bitsToBytes(192)) ? bitsToBytes(128) : - (kek_max < bitsToBytes(256)) ? bitsToBytes(192) : - bitsToBytes(256)); - - uint8_t t = 0; - - for (int i = 0; i < KEKBUF_LEN; i++) - t |= kekbuf[i]; - - if (t == 0 && (err = hal_rpc_get_random(kekbuf, sizeof(KEKBUF_LEN))) != HAL_OK) - return err; - - memcpy(kek, kekbuf, len); - *kek_len = len; - return HAL_OK; -} - -/* - * Local variables: - * indent-tabs-mode: nil - * End: - */ diff --git a/ks_token.c b/ks_token.c new file mode 100644 index 0000000..e29a90d --- /dev/null +++ b/ks_token.c @@ -0,0 +1,680 @@ +/* + * ks_token.c + * ---------- + * Keystore implementation in flash memory. + * + * Authors: Rob Austein, Fredrik Thulin + * Copyright (c) 2015-2017, 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. + */ + +/* + * This keystore driver operates over bare flash, versus over a flash file + * system or flash translation layer. The block size is large enough to + * hold an AES-keywrapped 4096-bit RSA key. Any remaining space in the key + * block may be used to store attributes (opaque TLV blobs). If the + * attributes overflow the key block, additional blocks may be added, but + * no attribute may exceed the block size. + */ + +#include <stddef.h> +#include <string.h> +#include <assert.h> + +#include "hal.h" +#include "hal_internal.h" +#include "ks.h" + +#include "last_gasp_pin_internal.h" + +#define HAL_OK CMIS_HAL_OK +#include "stm-keystore.h" +#undef HAL_OK + +#ifndef KS_TOKEN_CACHE_SIZE +#define KS_TOKEN_CACHE_SIZE 4 +#endif + +#define NUM_FLASH_BLOCKS KEYSTORE_NUM_SUBSECTORS + +#if HAL_KS_BLOCK_SIZE % KEYSTORE_SUBSECTOR_SIZE != 0 +#error Keystore block size is not a multiple of flash subsector size +#endif + +/* + * Keystore database. + */ + +typedef struct { + hal_ks_t ks; /* Must be first (C "subclassing") */ + hal_ks_pin_t wheel_pin; + hal_ks_pin_t so_pin; + hal_ks_pin_t user_pin; +} ks_token_db_t; + +/* + * This is a bit silly, but it's safe enough, and it lets us avoid a + * nasty mess of forward references. + */ + +#define db ((ks_token_db_t * const) hal_ks_token) + +/* + * Calculate offset of the block in the flash address space. + */ + +static inline uint32_t ks_token_offset(const unsigned blockno) +{ + return blockno * KEYSTORE_SUBSECTOR_SIZE; +} + +/* + * Read a flash block. + * + * Flash read on the Alpha is slow enough that it pays to check the + * first page before reading the rest of the block. + */ + +static hal_error_t ks_token_read(hal_ks_t *ks, const unsigned blockno, hal_ks_block_t *block) +{ + if (ks != hal_ks_token || block == NULL || blockno >= NUM_FLASH_BLOCKS || sizeof(*block) != KEYSTORE_SUBSECTOR_SIZE) + return HAL_ERROR_IMPOSSIBLE; + + /* Sigh, magic numeric return codes */ + if (keystore_read_data(ks_token_offset(blockno), + block->bytes, + KEYSTORE_PAGE_SIZE) != 1) + return HAL_ERROR_KEYSTORE_ACCESS; + + switch (hal_ks_block_get_type(block)) { + case HAL_KS_BLOCK_TYPE_ERASED: + case HAL_KS_BLOCK_TYPE_ZEROED: + return HAL_OK; + case HAL_KS_BLOCK_TYPE_KEY: + case HAL_KS_BLOCK_TYPE_PIN: + break; + default: + return HAL_ERROR_KEYSTORE_BAD_BLOCK_TYPE; + } + + switch (hal_ks_block_get_status(block)) { + case HAL_KS_BLOCK_STATUS_LIVE: + case HAL_KS_BLOCK_STATUS_TOMBSTONE: + break; + default: + return HAL_ERROR_KEYSTORE_BAD_BLOCK_TYPE; + } + + /* Sigh, magic numeric return codes */ + if (keystore_read_data(ks_token_offset(blockno) + KEYSTORE_PAGE_SIZE, + block->bytes + KEYSTORE_PAGE_SIZE, + sizeof(*block) - KEYSTORE_PAGE_SIZE) != 1) + return HAL_ERROR_KEYSTORE_ACCESS; + + if (hal_ks_block_calculate_crc(block) != block->header.crc) + return HAL_ERROR_KEYSTORE_BAD_CRC; + + return HAL_OK; +} + +/* + * Convert a live block into a tombstone. Caller is responsible for + * making sure that the block being converted is valid; since we don't + * need to update the CRC for this, we just modify the first page. + */ + +static hal_error_t ks_token_deprecate(hal_ks_t *ks, const unsigned blockno) +{ + if (ks != hal_ks_token || blockno >= NUM_FLASH_BLOCKS) + return HAL_ERROR_IMPOSSIBLE; + + uint8_t page[KEYSTORE_PAGE_SIZE]; + hal_ks_block_header_t *header = (void *) page; + uint32_t offset = ks_token_offset(blockno); + + /* Sigh, magic numeric return codes */ + if (keystore_read_data(offset, page, sizeof(page)) != 1) + return HAL_ERROR_KEYSTORE_ACCESS; + + header->block_status = HAL_KS_BLOCK_STATUS_TOMBSTONE; + + /* Sigh, magic numeric return codes */ + if (keystore_write_data(offset, page, sizeof(page)) != 1) + return HAL_ERROR_KEYSTORE_ACCESS; + + return HAL_OK; +} + +/* + * Zero (not erase) a flash block. Just need to zero the first page. + */ + +static hal_error_t ks_token_zero(hal_ks_t *ks, const unsigned blockno) +{ + if (ks != hal_ks_token || blockno >= NUM_FLASH_BLOCKS) + return HAL_ERROR_IMPOSSIBLE; + + uint8_t page[KEYSTORE_PAGE_SIZE] = {0}; + + /* Sigh, magic numeric return codes */ + if (keystore_write_data(ks_token_offset(blockno), page, sizeof(page)) != 1) + return HAL_ERROR_KEYSTORE_ACCESS; + + return HAL_OK; +} + +/* + * Erase a flash block. Also see ks_token_erase_maybe(), below. + */ + +static hal_error_t ks_token_erase(hal_ks_t *ks, const unsigned blockno) +{ + if (ks != hal_ks_token || blockno >= NUM_FLASH_BLOCKS) + return HAL_ERROR_IMPOSSIBLE; + + /* Sigh, magic numeric return codes */ + if (keystore_erase_subsector(blockno) != 1) + return HAL_ERROR_KEYSTORE_ACCESS; + + return HAL_OK; +} + +/* + * Erase a flash block if it hasn't already been erased. + * May not be necessary, trying to avoid unnecessary wear. + * + * Unclear whether there's any sane reason why this needs to be + * constant time, given how slow erasure is. But side channel attacks + * can be tricky things, and it's theoretically possible that we could + * leak information about, eg, key length, so we do constant time. + */ + +static hal_error_t ks_token_erase_maybe(hal_ks_t *ks, const unsigned blockno) +{ + if (ks != hal_ks_token || blockno >= NUM_FLASH_BLOCKS) + return HAL_ERROR_IMPOSSIBLE; + + uint8_t mask = 0xFF; + + for (uint32_t a = ks_token_offset(blockno); a < ks_token_offset(blockno + 1); a += KEYSTORE_PAGE_SIZE) { + uint8_t page[KEYSTORE_PAGE_SIZE]; + if (keystore_read_data(a, page, sizeof(page)) != 1) + return HAL_ERROR_KEYSTORE_ACCESS; + for (int i = 0; i < KEYSTORE_PAGE_SIZE; i++) + mask &= page[i]; + } + + return mask == 0xFF ? HAL_OK : ks_token_erase(ks, blockno); +} + +/* + * Write a flash block, calculating CRC when appropriate. + */ + +static hal_error_t ks_token_write(hal_ks_t *ks, const unsigned blockno, hal_ks_block_t *block) +{ + if (ks != hal_ks_token || block == NULL || blockno >= NUM_FLASH_BLOCKS || sizeof(*block) != KEYSTORE_SUBSECTOR_SIZE) + return HAL_ERROR_IMPOSSIBLE; + + hal_error_t err = ks_token_erase_maybe(ks, blockno); + + if (err != HAL_OK) + return err; + + switch (hal_ks_block_get_type(block)) { + case HAL_KS_BLOCK_TYPE_KEY: + case HAL_KS_BLOCK_TYPE_PIN: + block->header.crc = hal_ks_block_calculate_crc(block); + break; + default: + break; + } + + /* Sigh, magic numeric return codes */ + if (keystore_write_data(ks_token_offset(blockno), block->bytes, sizeof(*block)) != 1) + return HAL_ERROR_KEYSTORE_ACCESS; + + return HAL_OK; +} + +/* + * The token keystore doesn't implement per-session objects, so these are no-ops. + */ + +static hal_error_t ks_token_set_owner(hal_ks_t *ks, + const unsigned blockno, + const hal_client_handle_t client, + const hal_session_handle_t session) +{ + return HAL_OK; +} + +static hal_error_t ks_token_test_owner(hal_ks_t *ks, + const unsigned blockno, + const hal_client_handle_t client, + const hal_session_handle_t session) +{ + return HAL_OK; +} + +static hal_error_t ks_token_copy_owner(hal_ks_t *ks, + const unsigned source, + const unsigned target) +{ + return HAL_OK; +} + +static hal_error_t ks_token_logout(hal_ks_t *ks, + hal_client_handle_t client) +{ + return HAL_OK; +} + +/* + * Forward reference. + */ + +static hal_error_t fetch_pin_block(unsigned *b, hal_ks_block_t **block); + +/* + * Initialize keystore. + */ + +static hal_error_t ks_token_init(hal_ks_t *ks, const int alloc) +{ + if (ks != hal_ks_token) + return HAL_ERROR_IMPOSSIBLE; + + hal_ks_block_t *block = NULL; + hal_error_t err = HAL_OK; + + hal_ks_lock(); + + if (alloc && (err = hal_ks_alloc_common(ks, NUM_FLASH_BLOCKS, KS_TOKEN_CACHE_SIZE, NULL, 0)) != HAL_OK) + goto done; + + if ((err = hal_ks_init_common(ks)) != HAL_OK) + goto done; + + /* + * Fetch or create the PIN block. + */ + + memset(&db->wheel_pin, 0, sizeof(db->wheel_pin)); + memset(&db->so_pin, 0, sizeof(db->so_pin)); + memset(&db->user_pin, 0, sizeof(db->user_pin)); + + err = fetch_pin_block(NULL, &block); + + if (err == HAL_OK) { + db->wheel_pin = block->pin.wheel_pin; + db->so_pin = block->pin.so_pin; + db->user_pin = block->pin.user_pin; + } + + else if (err != HAL_ERROR_KEY_NOT_FOUND) + goto done; + + else { + /* + * We found no PIN block, so create one, with the user and so PINs + * cleared and the wheel PIN set to the last-gasp value. The + * last-gasp WHEEL PIN is a terrible answer, but we need some kind + * of bootstrapping mechanism when all else fails. If you have a + * better suggestion, we'd love to hear it. + */ + + unsigned b; + + if ((block = hal_ks_cache_pick_lru(ks)) == NULL) { + err = HAL_ERROR_IMPOSSIBLE; + goto done; + } + + memset(block, 0xFF, sizeof(*block)); + + block->header.block_type = HAL_KS_BLOCK_TYPE_PIN; + block->header.block_status = HAL_KS_BLOCK_STATUS_LIVE; + + block->pin.wheel_pin = db->wheel_pin = hal_last_gasp_pin; + block->pin.so_pin = db->so_pin; + block->pin.user_pin = db->user_pin; + + if ((err = hal_ks_index_add(ks, &hal_ks_pin_uuid, &b, NULL)) != HAL_OK) + goto done; + + hal_ks_cache_mark_used(ks, block, b); + + err = ks_token_write(ks, b, block); + + hal_ks_cache_release(ks, block); + + if (err != HAL_OK) + goto done; + } + + err = HAL_OK; + + done: + hal_ks_unlock(); + return err; +} + +/* + * Dispatch vector and keystore definition, now that we've defined all + * the driver functions. + */ + +static const hal_ks_driver_t ks_token_driver = { + .init = ks_token_init, + .read = ks_token_read, + .write = ks_token_write, + .deprecate = ks_token_deprecate, + .zero = ks_token_zero, + .erase = ks_token_erase, + .erase_maybe = ks_token_erase_maybe, + .set_owner = ks_token_set_owner, + .test_owner = ks_token_test_owner, + .copy_owner = ks_token_copy_owner, + .logout = ks_token_logout +}; + +static ks_token_db_t _db = { .ks.driver = &ks_token_driver }; + +hal_ks_t * const hal_ks_token = &_db.ks; + +/* + * The remaining functions aren't really part of the keystore API per se, + * but they all involve non-key data which we keep in the keystore + * because it's the flash we've got. + */ + +/* + * Special bonus init routine used only by the bootloader, so that it + * can read PINs set by the main firmware. Yes, this is a kludge. We + * could of course call the real ks_init() routine instead, but it's + * slow, and we don't want to allow anything that would modify the + * flash here, so having a special entry point for this kludge is + * simplest, overall. Sigh. + */ + +void hal_ks_init_read_only_pins_only(void) +{ + unsigned b, best_seen = ~0; + hal_ks_block_t block[1]; + + hal_ks_lock(); + + for (b = 0; b < NUM_FLASH_BLOCKS; b++) { + if (hal_ks_block_read(hal_ks_token, b, block) != HAL_OK || + hal_ks_block_get_type(block) != HAL_KS_BLOCK_TYPE_PIN) + continue; + best_seen = b; + if (hal_ks_block_get_status(block) == HAL_KS_BLOCK_STATUS_LIVE) + break; + } + + if (b != best_seen && best_seen != ~0 && + hal_ks_block_read(hal_ks_token, best_seen, block) != HAL_OK) + best_seen = ~0; + + if (best_seen == ~0) { + memset(block, 0xFF, sizeof(*block)); + block->pin.wheel_pin = hal_last_gasp_pin; + } + + db->wheel_pin = block->pin.wheel_pin; + db->so_pin = block->pin.so_pin; + db->user_pin = block->pin.user_pin; + + hal_ks_unlock(); +} + +/* + * Fetch PIN. This is always cached, so just returned cached value. + */ + +hal_error_t hal_get_pin(const hal_user_t user, + const hal_ks_pin_t **pin) +{ + if (pin == NULL) + return HAL_ERROR_BAD_ARGUMENTS; + + hal_error_t err = HAL_OK; + + hal_ks_lock(); + + switch (user) { + case HAL_USER_WHEEL: *pin = &db->wheel_pin; break; + case HAL_USER_SO: *pin = &db->so_pin; break; + case HAL_USER_NORMAL: *pin = &db->user_pin; break; + default: err = HAL_ERROR_BAD_ARGUMENTS; + } + + hal_ks_unlock(); + + return err; +} + +/* + * Fetch PIN block. hint = 0 because we know that the all-zeros UUID + * should always sort to first slot in the index. + */ + +static hal_error_t fetch_pin_block(unsigned *b, hal_ks_block_t **block) +{ + if (block == NULL) + return HAL_ERROR_IMPOSSIBLE; + + hal_error_t err; + int hint = 0; + unsigned b_; + + if (b == NULL) + b = &b_; + + if ((err = hal_ks_index_find(hal_ks_token, &hal_ks_pin_uuid, b, &hint)) != HAL_OK || + (err = hal_ks_block_read_cached(hal_ks_token, *b, block)) != HAL_OK) + return err; + + hal_ks_cache_mark_used(hal_ks_token, *block, *b); + + if (hal_ks_block_get_type(*block) != HAL_KS_BLOCK_TYPE_PIN) + return HAL_ERROR_IMPOSSIBLE; + + return HAL_OK; +} + +/* + * Update the PIN block. This block should always be present, but we + * have to do the zombie jamboree to make sure we write the new PIN + * block before destroying the old one. hint = 0 because we know that + * the all-zeros UUID should always sort to first slot in the index. + */ + +static hal_error_t update_pin_block(const unsigned b, + hal_ks_block_t *block, + const hal_ks_pin_block_t * const new_data) +{ + if (block == NULL || new_data == NULL || hal_ks_block_get_type(block) != HAL_KS_BLOCK_TYPE_PIN) + return HAL_ERROR_IMPOSSIBLE; + + int hint = 0; + + block->pin = *new_data; + + return hal_ks_block_update(hal_ks_token, b, block, &hal_ks_pin_uuid, &hint); +} + +/* + * Change a PIN. + */ + +hal_error_t hal_set_pin(const hal_user_t user, + const hal_ks_pin_t * const pin) +{ + if (pin == NULL) + return HAL_ERROR_BAD_ARGUMENTS; + + hal_ks_block_t *block; + hal_error_t err; + unsigned b; + + hal_ks_lock(); + + if ((err = fetch_pin_block(&b, &block)) != HAL_OK) + goto done; + + hal_ks_pin_block_t new_data = block->pin; + hal_ks_pin_t *dp, *bp; + + switch (user) { + case HAL_USER_WHEEL: bp = &new_data.wheel_pin; dp = &db->wheel_pin; break; + case HAL_USER_SO: bp = &new_data.so_pin; dp = &db->so_pin; break; + case HAL_USER_NORMAL: bp = &new_data.user_pin; dp = &db->user_pin; break; + default: err = HAL_ERROR_BAD_ARGUMENTS; goto done; + } + + const hal_ks_pin_t old_pin = *dp; + *dp = *bp = *pin; + + if ((err = update_pin_block(b, block, &new_data)) != HAL_OK) + *dp = old_pin; + + done: + hal_ks_unlock(); + return err; +} + +#if HAL_MKM_FLASH_BACKUP_KLUDGE + +/* + * Horrible insecure kludge in lieu of a battery for the MKM. + * + * API here is a little strange: all calls pass a length parameter, + * but any length other than the compiled in constant just returns an + * immediate error, there's no notion of buffer max length vs buffer + * used length, querying for the size of buffer really needed, or + * anything like that. + * + * We might want to rewrite this some day, if we don't replace it with + * a battery first. For now we just preserve the API as we found it + * while re-implementing it on top of the new keystore. + */ + +hal_error_t hal_mkm_flash_read_no_lock(uint8_t *buf, const size_t len) +{ + if (buf != NULL && len != KEK_LENGTH) + return HAL_ERROR_MASTERKEY_BAD_LENGTH; + + hal_ks_block_t *block; + hal_error_t err; + unsigned b; + + if ((err = fetch_pin_block(&b, &block)) != HAL_OK) + return err; + + if (block->pin.kek_set != FLASH_KEK_SET) + return HAL_ERROR_MASTERKEY_NOT_SET; + + if (buf != NULL) + memcpy(buf, block->pin.kek, len); + + return HAL_OK; +} + +hal_error_t hal_mkm_flash_read(uint8_t *buf, const size_t len) +{ + hal_ks_lock(); + const hal_error_t err = hal_mkm_flash_read_no_lock(buf, len); + hal_ks_unlock(); + return err; +} + +hal_error_t hal_mkm_flash_write(const uint8_t * const buf, const size_t len) +{ + if (buf == NULL) + return HAL_ERROR_BAD_ARGUMENTS; + + if (len != KEK_LENGTH) + return HAL_ERROR_MASTERKEY_BAD_LENGTH; + + hal_ks_block_t *block; + hal_error_t err; + unsigned b; + + hal_ks_lock(); + + if ((err = fetch_pin_block(&b, &block)) != HAL_OK) + goto done; + + hal_ks_pin_block_t new_data = block->pin; + + new_data.kek_set = FLASH_KEK_SET; + memcpy(new_data.kek, buf, len); + + err = update_pin_block(b, block, &new_data); + + done: + hal_ks_unlock(); + return err; +} + +hal_error_t hal_mkm_flash_erase(const size_t len) +{ + if (len != KEK_LENGTH) + return HAL_ERROR_MASTERKEY_BAD_LENGTH; + + hal_ks_block_t *block; + hal_error_t err; + unsigned b; + + hal_ks_lock(); + + if ((err = fetch_pin_block(&b, &block)) != HAL_OK) + goto done; + + hal_ks_pin_block_t new_data = block->pin; + + new_data.kek_set = FLASH_KEK_SET; + memset(new_data.kek, 0, len); + + err = update_pin_block(b, block, &new_data); + + done: + hal_ks_unlock(); + return err; +} + +#endif /* HAL_MKM_FLASH_BACKUP_KLUDGE */ + +/* + * Local variables: + * indent-tabs-mode: nil + * End: + */ diff --git a/ks_volatile.c b/ks_volatile.c index 6a17e45..57806e8 100644 --- a/ks_volatile.c +++ b/ks_volatile.c @@ -41,621 +41,272 @@ #include "hal.h" #include "hal_internal.h" - -#define KEK_LENGTH (bitsToBytes(256)) +#include "ks.h" #ifndef STATIC_KS_VOLATILE_SLOTS #define STATIC_KS_VOLATILE_SLOTS HAL_STATIC_PKEY_STATE_BLOCKS #endif -#ifndef STATIC_KS_VOLATILE_ATTRIBUTE_SPACE -#define STATIC_KS_VOLATILE_ATTRIBUTE_SPACE 4096 +#ifndef KS_VOLATILE_CACHE_SIZE +#define KS_VOLATILE_CACHE_SIZE 4 #endif /* - * In-memory keystore database. This should also be usable for - * mmap(), if and when we get around to rewriting that driver (and in - * which case this driver probably ought to be renamed ks_memory). + * Keystore database. */ typedef struct { - hal_key_type_t type; - hal_curve_name_t curve; - hal_key_flags_t flags; hal_client_handle_t client; hal_session_handle_t session; - size_t der_len; - unsigned attributes_len; - uint8_t der[HAL_KS_WRAPPED_KEYSIZE + STATIC_KS_VOLATILE_ATTRIBUTE_SPACE]; -} ks_key_t; + hal_ks_block_t block; +} ks_volatile_key_t; typedef struct { - hal_ks_index_t ksi; - ks_key_t *keys; -} db_t; + hal_ks_t ks; /* Must be first */ + ks_volatile_key_t *keys; +} ks_volatile_db_t; /* - * "Subclass" (well, what one can do in C) of hal_ks_t. This is - * separate from db_t primarily to simplify things like rewriting the - * old ks_mmap driver to piggy-back on the ks_volatile driver: we - * wouldn't want the hal_ks_t into the mmap()ed file. + * This is a bit silly, but it's safe enough, and it lets us avoid a + * nasty mess of forward references. */ -typedef struct { - hal_ks_t ks; /* Must be first */ - db_t *db; /* Which memory-based keystore database */ - int per_session; /* Whether objects are per-session */ -} ks_t; +#define db ((ks_volatile_db_t * const) hal_ks_volatile) /* - * If we also supported mmap, there would be a separate definition for - * HAL_KS_MMAP_SLOTS above, and the bulk of the code would be under a - * conditional testing whether either HAL_KS_*_SLOTS were nonzero. + * Read a block. CRC probably not necessary for RAM. */ -#if STATIC_KS_VOLATILE_SLOTS > 0 +static hal_error_t ks_volatile_read(hal_ks_t *ks, const unsigned blockno, hal_ks_block_t *block) +{ + if (ks != hal_ks_volatile || db->keys == NULL || block == NULL || blockno >= ks->size) + return HAL_ERROR_IMPOSSIBLE; -static ks_t volatile_ks; + memcpy(block, &db->keys[blockno].block, sizeof(*block)); -static inline ks_t *ks_to_ksv(hal_ks_t *ks) -{ - return (ks_t *) ks; + return HAL_OK; } /* - * Check whether the current session can see a particular key. One - * might expect this to be based on whether the session matches, and - * indeed it would be in a sane world, but in the world of PKCS #11, - * keys belong to sessions, are visible to other sessions, and may - * even be modifiable by other sessions, but softly and silently - * vanish away when the original creating session is destroyed. - * - * In our terms, this means that visibility of session objects is - * determined only by the client handle, so taking the session handle - * as an argument here isn't really necessary, but we've flipflopped - * on that enough times that at least for now I'd prefer to leave the - * session handle here and not have to revise all the RPC calls again. - * Remove it at some later date and redo the RPC calls if we manage to - * avoid revising this yet again. + * Convert a live block into a tombstone. */ -static inline int key_visible_to_session(const ks_t * const ksv, - const hal_client_handle_t client, - const hal_session_handle_t session, - const ks_key_t * const k) -{ - return (!ksv->per_session || - client.handle == HAL_HANDLE_NONE || - k->client.handle == client.handle || - hal_rpc_is_logged_in(client, HAL_USER_WHEEL) == HAL_OK); -} - -static inline void *gnaw(uint8_t **mem, size_t *len, const size_t size) +static hal_error_t ks_volatile_deprecate(hal_ks_t *ks, const unsigned blockno) { - if (mem == NULL || *mem == NULL || len == NULL || size > *len) - return NULL; - void *ret = *mem; - *mem += size; - *len -= size; - return ret; -} - -static hal_error_t ks_init(const hal_ks_driver_t * const driver, - const int per_session, - ks_t *ksv, - uint8_t *mem, - size_t len) -{ - if (ksv == NULL) + if (ks != hal_ks_volatile || db->keys == NULL || blockno >= ks->size) return HAL_ERROR_IMPOSSIBLE; - if (mem != NULL) { - memset(ksv, 0, sizeof(*ksv)); - memset(mem, 0, len); - - ksv->db = gnaw(&mem, &len, sizeof(*ksv->db)); - ksv->db->ksi.index = gnaw(&mem, &len, sizeof(*ksv->db->ksi.index) * STATIC_KS_VOLATILE_SLOTS); - ksv->db->ksi.names = gnaw(&mem, &len, sizeof(*ksv->db->ksi.names) * STATIC_KS_VOLATILE_SLOTS); - ksv->db->keys = gnaw(&mem, &len, sizeof(*ksv->db->keys) * STATIC_KS_VOLATILE_SLOTS); - ksv->db->ksi.size = STATIC_KS_VOLATILE_SLOTS; - } - - if (ksv->db == NULL || - ksv->db->ksi.index == NULL || - ksv->db->ksi.names == NULL || - ksv->db->keys == NULL) - return HAL_ERROR_IMPOSSIBLE; - - if (mem == NULL) { - memset(ksv->db->ksi.index, 0, sizeof(*ksv->db->ksi.index) * STATIC_KS_VOLATILE_SLOTS); - memset(ksv->db->ksi.names, 0, sizeof(*ksv->db->ksi.names) * STATIC_KS_VOLATILE_SLOTS); - memset(ksv->db->keys, 0, sizeof(*ksv->db->keys) * STATIC_KS_VOLATILE_SLOTS); - } - - ksv->ks.driver = driver; - ksv->per_session = per_session; - ksv->db->ksi.used = 0; - - /* - * Set up keystore with empty index and full free list. - * Since this driver doesn't care about wear leveling, - * just populate the free list in block numerical order. - */ + db->keys[blockno].block.header.block_status = HAL_KS_BLOCK_STATUS_TOMBSTONE; - for (int i = 0; i < STATIC_KS_VOLATILE_SLOTS; i++) - ksv->db->ksi.index[i] = i; - - return hal_ks_index_setup(&ksv->db->ksi); + return HAL_OK; } -static hal_error_t ks_volatile_init(const hal_ks_driver_t * const driver, const int alloc) -{ - hal_error_t err = HAL_OK; - - hal_ks_lock(); - - const size_t len = (sizeof(*volatile_ks.db) + - sizeof(*volatile_ks.db->ksi.index) * STATIC_KS_VOLATILE_SLOTS + - sizeof(*volatile_ks.db->ksi.names) * STATIC_KS_VOLATILE_SLOTS + - sizeof(*volatile_ks.db->keys) * STATIC_KS_VOLATILE_SLOTS); - - uint8_t *mem = NULL; - - if (alloc && (mem = hal_allocate_static_memory(len)) == NULL) - err = HAL_ERROR_ALLOCATION_FAILURE; - else - err = ks_init(driver, 1, &volatile_ks, mem, len); - - hal_ks_unlock(); - return err; -} +/* + * Zero (not erase) a flash block. + */ -static hal_error_t ks_volatile_shutdown(const hal_ks_driver_t * const driver) +static hal_error_t ks_volatile_zero(hal_ks_t *ks, const unsigned blockno) { - if (volatile_ks.ks.driver != driver) - return HAL_ERROR_KEYSTORE_ACCESS; - return HAL_OK; -} + if (ks != hal_ks_volatile || db->keys == NULL || blockno >= ks->size) + return HAL_ERROR_IMPOSSIBLE; -static hal_error_t ks_volatile_open(const hal_ks_driver_t * const driver, - hal_ks_t **ks) -{ - assert(driver != NULL && ks != NULL); - *ks = &volatile_ks.ks; - return HAL_OK; -} + memset(&db->keys[blockno].block, 0x00, sizeof(db->keys[blockno].block)); + db->keys[blockno].client.handle = HAL_HANDLE_NONE; + db->keys[blockno].session.handle = HAL_HANDLE_NONE; -static hal_error_t ks_volatile_close(hal_ks_t *ks) -{ return HAL_OK; } -static inline int acceptable_key_type(const hal_key_type_t type) -{ - switch (type) { - case HAL_KEY_TYPE_RSA_PRIVATE: - case HAL_KEY_TYPE_EC_PRIVATE: - case HAL_KEY_TYPE_RSA_PUBLIC: - case HAL_KEY_TYPE_EC_PUBLIC: - return 1; - default: - return 0; - } -} +/* + * Erase a flash block. + */ -static hal_error_t ks_store(hal_ks_t *ks, - hal_pkey_slot_t *slot, - const uint8_t * const der, const size_t der_len) +static hal_error_t ks_volatile_erase(hal_ks_t *ks, const unsigned blockno) { - if (ks == NULL || slot == NULL || der == NULL || der_len == 0 || !acceptable_key_type(slot->type)) - return HAL_ERROR_BAD_ARGUMENTS; - - ks_t *ksv = ks_to_ksv(ks); - hal_error_t err = HAL_OK; - unsigned b; - - hal_ks_lock(); - - if (ksv->db == NULL) { - err = HAL_ERROR_KEYSTORE_ACCESS; - goto done; - } - - if ((err = hal_ks_index_add(&ksv->db->ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK) - goto done; - - uint8_t kek[KEK_LENGTH]; - size_t kek_len; - ks_key_t k; - - memset(&k, 0, sizeof(k)); - k.der_len = sizeof(k.der); - k.type = slot->type; - k.curve = slot->curve; - k.flags = slot->flags; - k.client = slot->client_handle; - k.session = slot->session_handle; - - if ((err = hal_mkm_get_kek(kek, &kek_len, sizeof(kek))) == HAL_OK) - err = hal_aes_keywrap(NULL, kek, kek_len, der, der_len, k.der, &k.der_len); - - memset(kek, 0, sizeof(kek)); + if (ks != hal_ks_volatile || db->keys == NULL || blockno >= ks->size) + return HAL_ERROR_IMPOSSIBLE; - if (err == HAL_OK) - ksv->db->keys[b] = k; - else - (void) hal_ks_index_delete(&ksv->db->ksi, &slot->name, 0, NULL, &slot->hint); + memset(&db->keys[blockno].block, 0xFF, sizeof(db->keys[blockno].block)); + db->keys[blockno].client.handle = HAL_HANDLE_NONE; + db->keys[blockno].session.handle = HAL_HANDLE_NONE; - done: - hal_ks_unlock(); - return err; + return HAL_OK; } -static hal_error_t ks_fetch(hal_ks_t *ks, - hal_pkey_slot_t *slot, - uint8_t *der, size_t *der_len, const size_t der_max) -{ - if (ks == NULL || slot == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - ks_t *ksv = ks_to_ksv(ks); - hal_error_t err = HAL_OK; - unsigned b; - - hal_ks_lock(); - - if (ksv->db == NULL) { - err = HAL_ERROR_KEYSTORE_ACCESS; - goto done; - } - - if ((err = hal_ks_index_find(&ksv->db->ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK) - goto done; - - const ks_key_t * const k = &ksv->db->keys[b]; - - if (!key_visible_to_session(ksv, slot->client_handle, slot->session_handle, k)) { - err = HAL_ERROR_KEY_NOT_FOUND; - goto done; - } - - slot->type = k->type; - slot->curve = k->curve; - slot->flags = k->flags; - - if (der == NULL && der_len != NULL) - *der_len = k->der_len; - - if (der != NULL) { - - uint8_t kek[KEK_LENGTH]; - size_t kek_len, der_len_; - - if (der_len == NULL) - der_len = &der_len_; - - *der_len = der_max; - - if ((err = hal_mkm_get_kek(kek, &kek_len, sizeof(kek))) == HAL_OK) - err = hal_aes_keyunwrap(NULL, kek, kek_len, k->der, k->der_len, der, der_len); - - memset(kek, 0, sizeof(kek)); - } - - done: - hal_ks_unlock(); - return err; -} +/* + * Write a flash block. CRC probably not necessary for RAM. + */ -static hal_error_t ks_delete(hal_ks_t *ks, - hal_pkey_slot_t *slot) +static hal_error_t ks_volatile_write(hal_ks_t *ks, const unsigned blockno, hal_ks_block_t *block) { - if (ks == NULL || slot == NULL) - return HAL_ERROR_BAD_ARGUMENTS; - - ks_t *ksv = ks_to_ksv(ks); - hal_error_t err = HAL_OK; - unsigned b; - - hal_ks_lock(); - - if (ksv->db == NULL) { - err = HAL_ERROR_KEYSTORE_ACCESS; - goto done; - } - - if ((err = hal_ks_index_find(&ksv->db->ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK) - goto done; - - if (!key_visible_to_session(ksv, slot->client_handle, slot->session_handle, &ksv->db->keys[b])) { - err = HAL_ERROR_KEY_NOT_FOUND; - goto done; - } - - if ((err = hal_ks_index_delete(&ksv->db->ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK) - goto done; + if (ks != hal_ks_volatile || db->keys == NULL || block == NULL || blockno >= ks->size) + return HAL_ERROR_IMPOSSIBLE; - memset(&ksv->db->keys[b], 0, sizeof(ksv->db->keys[b])); + memcpy(&db->keys[blockno].block, block, sizeof(*block)); - done: - hal_ks_unlock(); - return err; + return HAL_OK; } -static hal_error_t ks_match(hal_ks_t *ks, - hal_client_handle_t client, - hal_session_handle_t session, - const hal_key_type_t type, - const hal_curve_name_t curve, - const hal_key_flags_t mask, - const hal_key_flags_t flags, - const hal_pkey_attribute_t *attributes, - const unsigned attributes_len, - hal_uuid_t *result, - unsigned *result_len, - const unsigned result_max, - const hal_uuid_t * const previous_uuid) +/* + * Set key ownership. + */ + +static hal_error_t ks_volatile_set_owner(hal_ks_t *ks, + const unsigned blockno, + const hal_client_handle_t client, + const hal_session_handle_t session) { - if (ks == NULL || (attributes == NULL && attributes_len > 0) || - result == NULL || result_len == NULL || previous_uuid == NULL) - return HAL_ERROR_BAD_ARGUMENTS; + if (ks != hal_ks_volatile || db->keys == NULL || blockno >= ks->size) + return HAL_ERROR_IMPOSSIBLE; - ks_t *ksv = ks_to_ksv(ks); + db->keys[blockno].client = client; + db->keys[blockno].session = session; - if (ksv->db == NULL) - return HAL_ERROR_KEYSTORE_ACCESS; + return HAL_OK; +} - hal_error_t err = HAL_OK; - int i = -1; +/* + * Test key ownership. + * + * One might expect this to be based on whether the session matches, + * and indeed it would be in a sane world, but in the world of PKCS + * #11, keys belong to sessions, are visible to other sessions, and + * may even be modifiable by other sessions, but softly and silently + * vanish away when the original creating session is destroyed. + * + * In our terms, this means that visibility of session objects is + * determined only by the client handle, so taking the session handle + * as an argument here isn't really necessary, but we've flipflopped + * on that enough times that at least for now I'd prefer to leave the + * session handle here and not have to revise all the RPC calls again. + * Remove it at some later date and redo the RPC calls if we manage to + * avoid revising this yet again. + */ - hal_ks_lock(); +static hal_error_t ks_volatile_test_owner(hal_ks_t *ks, + const unsigned blockno, + const hal_client_handle_t client, + const hal_session_handle_t session) +{ + if (ks != hal_ks_volatile || db->keys == NULL || blockno >= ks->size) + return HAL_ERROR_IMPOSSIBLE; - *result_len = 0; + if (db->keys[blockno].client.handle == HAL_HANDLE_NONE || + db->keys[blockno].client.handle == client.handle) + return HAL_OK; - err = hal_ks_index_find(&ksv->db->ksi, previous_uuid, 0, NULL, &i); + if (hal_rpc_is_logged_in(client, HAL_USER_WHEEL) == HAL_OK) + return HAL_OK; - if (err == HAL_ERROR_KEY_NOT_FOUND) - i--; - else if (err != HAL_OK) - goto done; + return HAL_ERROR_KEY_NOT_FOUND; +} - while (*result_len < result_max && ++i < ksv->db->ksi.used) { +/* + * Copy key ownership. + */ - unsigned b = ksv->db->ksi.index[i]; +static hal_error_t ks_volatile_copy_owner(hal_ks_t *ks, + const unsigned source, + const unsigned target) +{ + if (ks != hal_ks_volatile || db->keys == NULL || source >= ks->size || target >= ks->size) + return HAL_ERROR_IMPOSSIBLE; - if (ksv->db->ksi.names[b].chunk > 0) - continue; + db->keys[target].client = db->keys[source].client; + db->keys[target].session = db->keys[source].session; + return HAL_OK; +} - if (type != HAL_KEY_TYPE_NONE && type != ksv->db->keys[b].type) - continue; +/* + * Zero any blocks owned by a client that we're logging out. + */ - if (curve != HAL_CURVE_NONE && curve != ksv->db->keys[b].curve) - continue; +static hal_error_t ks_volatile_logout(hal_ks_t *ks, + hal_client_handle_t client) +{ + if (ks != hal_ks_volatile || client.handle == HAL_HANDLE_NONE) + return HAL_ERROR_IMPOSSIBLE; - if (((flags ^ ksv->db->keys[b].flags) & mask) != 0) - continue; + for (int i = 0; i < ks->used; i++) { + unsigned b = ks->index[i]; + hal_error_t err; + int hint = i; - if (!key_visible_to_session(ksv, client, session, &ksv->db->keys[b])) + if (db->keys[b].client.handle != client.handle) continue; - if (attributes_len > 0) { - const ks_key_t * const k = &ksv->db->keys[b]; - int ok = 1; - - if (k->attributes_len == 0) - continue; - - hal_pkey_attribute_t key_attrs[k->attributes_len]; - - if ((err = hal_ks_attribute_scan(k->der + k->der_len, sizeof(k->der) - k->der_len, - key_attrs, k->attributes_len, NULL)) != HAL_OK) - goto done; - - for (const hal_pkey_attribute_t *required = attributes; - ok && required < attributes + attributes_len; required++) { - - hal_pkey_attribute_t *present = key_attrs; - while (ok && present->type != required->type) - ok = ++present < key_attrs + k->attributes_len; - - if (ok) - ok = (present->length == required->length && - !memcmp(present->value, required->value, present->length)); - } + if ((err = hal_ks_index_delete(ks, &ks->names[b], 0, NULL, &hint)) != HAL_OK || + (err = hal_ks_block_zero(ks, b)) != HAL_OK) + return err; - if (!ok) - continue; - } - - result[*result_len] = ksv->db->ksi.names[b].name; - ++*result_len; + i--; } - err = HAL_OK; - - done: - hal_ks_unlock(); - return err; + return HAL_OK; } -static hal_error_t ks_set_attributes(hal_ks_t *ks, - hal_pkey_slot_t *slot, - const hal_pkey_attribute_t *attributes, - const unsigned attributes_len) -{ - if (ks == NULL || slot == NULL || attributes == NULL || attributes_len == 0) - return HAL_ERROR_BAD_ARGUMENTS; - - ks_t *ksv = ks_to_ksv(ks); - hal_error_t err = HAL_OK; - unsigned b; - - hal_ks_lock(); - - { - if (ksv->db == NULL) { - err = HAL_ERROR_KEYSTORE_ACCESS; - goto done; - } - - if ((err = hal_ks_index_find(&ksv->db->ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK) - goto done; - - ks_key_t * const k = &ksv->db->keys[b]; - - if (!key_visible_to_session(ksv, slot->client_handle, slot->session_handle, k)) { - err = HAL_ERROR_KEY_NOT_FOUND; - goto done; - } - - hal_pkey_attribute_t attrs[k->attributes_len + attributes_len]; - uint8_t *bytes = k->der + k->der_len; - size_t bytes_len = sizeof(k->der) - k->der_len; - size_t total_len; - - if ((err = hal_ks_attribute_scan(bytes, bytes_len, attrs, k->attributes_len, &total_len)) != HAL_OK) - goto done; - - for (const hal_pkey_attribute_t *a = attributes; a < attributes + attributes_len; a++) { - if (a->length == HAL_PKEY_ATTRIBUTE_NIL) - err = hal_ks_attribute_delete(bytes, bytes_len, attrs, &k->attributes_len, &total_len, - a->type); - else - err = hal_ks_attribute_insert(bytes, bytes_len, attrs, &k->attributes_len, &total_len, - a->type, a->value, a->length); - if (err != HAL_OK) - goto done; - } - - err = HAL_OK; - - } - - done: - hal_ks_unlock(); - return err; -} +/* + * Initialize keystore. + */ -static hal_error_t ks_get_attributes(hal_ks_t *ks, - hal_pkey_slot_t *slot, - hal_pkey_attribute_t *attributes, - const unsigned attributes_len, - uint8_t *attributes_buffer, - const size_t attributes_buffer_len) +static hal_error_t ks_volatile_init(hal_ks_t *ks, const int alloc) { - if (ks == NULL || slot == NULL || attributes == NULL || attributes_len == 0 || - attributes_buffer == NULL) - return HAL_ERROR_BAD_ARGUMENTS; + if (ks != hal_ks_volatile) + return HAL_ERROR_IMPOSSIBLE; - ks_t *ksv = ks_to_ksv(ks); hal_error_t err = HAL_OK; - unsigned b; + void *mem = NULL; hal_ks_lock(); - { - if (ksv->db == NULL) { - err = HAL_ERROR_KEYSTORE_ACCESS; - goto done; - } - - if ((err = hal_ks_index_find(&ksv->db->ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK) + if (alloc) { + if ((err = hal_ks_alloc_common(ks, STATIC_KS_VOLATILE_SLOTS, KS_VOLATILE_CACHE_SIZE, + &mem, sizeof(*db->keys) * STATIC_KS_VOLATILE_SLOTS)) != HAL_OK) goto done; + db->keys = mem; + } - const ks_key_t * const k = &ksv->db->keys[b]; - - if (!key_visible_to_session(ksv, slot->client_handle, slot->session_handle, k)) { - err = HAL_ERROR_KEY_NOT_FOUND; - goto done; - } - - hal_pkey_attribute_t attrs[k->attributes_len > 0 ? k->attributes_len : 1]; + if (db->keys == NULL) { + err = HAL_ERROR_IMPOSSIBLE; + goto done; + } - if ((err = hal_ks_attribute_scan(k->der + k->der_len, sizeof(k->der) - k->der_len, - attrs, k->attributes_len, NULL)) != HAL_OK) + for (unsigned b = 0; b < db->ks.size; b++) + if ((err = hal_ks_block_erase(ks, b)) != HAL_OK) goto done; - uint8_t *abuf = attributes_buffer; - - for (int i = 0; i < attributes_len; i++) { - int j = 0; - while (j < k->attributes_len && attrs[j].type != attributes[i].type) - j++; - const int found = j < k->attributes_len; - - if (attributes_buffer_len == 0) { - attributes[i].value = NULL; - attributes[i].length = found ? attrs[j].length : 0; - continue; - } - - if (!found) { - err = HAL_ERROR_ATTRIBUTE_NOT_FOUND; - goto done; - } - - if (attrs[j].length > attributes_buffer + attributes_buffer_len - abuf) { - err = HAL_ERROR_RESULT_TOO_LONG; - goto done; - } - - memcpy(abuf, attrs[j].value, attrs[j].length); - attributes[i].value = abuf; - attributes[i].length = attrs[j].length; - abuf += attrs[j].length; - } - - err = HAL_OK; + if ((err = hal_ks_init_common(ks)) != HAL_OK) + goto done; - } + err = HAL_OK; done: hal_ks_unlock(); return err; } -static hal_error_t ks_logout(hal_ks_t *ks, - hal_client_handle_t client) -{ - if (ks == NULL || client.handle == HAL_HANDLE_NONE) - return HAL_ERROR_BAD_ARGUMENTS; - - ks_t *ksv = ks_to_ksv(ks); - hal_error_t err = HAL_OK; - - hal_ks_lock(); - - for (int i = 0; i < ksv->db->ksi.used; i++) { - unsigned b = ksv->db->ksi.index[i]; - if (ksv->db->keys[b].client.handle == client.handle) { - int hint = i; - if ((err = hal_ks_index_delete(&ksv->db->ksi, &ksv->db->ksi.names[b].name, 0, NULL, &hint)) != HAL_OK) - goto done; - memset(&ksv->db->keys[b], 0, sizeof(ksv->db->keys[b])); - i--; - } - } - - done: - hal_ks_unlock(); - return err; -} +/* + * Dispatch vector and keystore definition, now that we've defined all + * the driver functions. + */ -const hal_ks_driver_t hal_ks_volatile_driver[1] = {{ +static const hal_ks_driver_t ks_volatile_driver = { .init = ks_volatile_init, - .shutdown = ks_volatile_shutdown, - .open = ks_volatile_open, - .close = ks_volatile_close, - .store = ks_store, - .fetch = ks_fetch, - .delete = ks_delete, - .match = ks_match, - .set_attributes = ks_set_attributes, - .get_attributes = ks_get_attributes, - .logout = ks_logout -}}; - -#endif /* STATIC_KS_VOLATILE_SLOTS > 0 */ + .read = ks_volatile_read, + .write = ks_volatile_write, + .deprecate = ks_volatile_deprecate, + .zero = ks_volatile_zero, + .erase = ks_volatile_erase, + .erase_maybe = ks_volatile_erase, /* sic */ + .set_owner = ks_volatile_set_owner, + .test_owner = ks_volatile_test_owner, + .copy_owner = ks_volatile_copy_owner, + .logout = ks_volatile_logout +}; + +static ks_volatile_db_t _db = { .ks.driver = &ks_volatile_driver }; + +hal_ks_t * const hal_ks_volatile = &_db.ks; /* * Local variables: diff --git a/last_gasp_pin_internal.h b/last_gasp_pin_internal.h index bbcac76..901f797 100644 --- a/last_gasp_pin_internal.h +++ b/last_gasp_pin_internal.h @@ -3,7 +3,7 @@ */ static const hal_ks_pin_t hal_last_gasp_pin = { - 10000, - {0x06, 0xe2, 0x10, 0x7b, 0xb8, 0x40, 0xb5, 0x90, 0x33, 0xc8, 0xdb, 0xcc, 0xde, 0x3e, 0xb0, 0x33, 0x2b, 0x7c, 0x60, 0x7c, 0xb4, 0x52, 0xb1, 0x43, 0xa2, 0x20, 0x71, 0xdd, 0xbc, 0x95, 0x92, 0x04, 0xe6, 0x51, 0x90, 0xda, 0x6e, 0x2b, 0x6d, 0x8c, 0xb8, 0x63, 0x8d, 0x59, 0xad, 0xc5, 0xae, 0x6c, 0xf5, 0x7c, 0x75, 0x5e, 0x38, 0x72, 0x06, 0xc5, 0xa9, 0x3b, 0xaa, 0xe9, 0x64, 0x6e, 0xb1, 0x1a}, - {0x40, 0x49, 0xe4, 0xb6, 0x18, 0x0e, 0xe2, 0xbf, 0x3b, 0x22, 0xc8, 0xfe, 0xeb, 0xef, 0x09, 0x81} + 1000, + {0xd5, 0xde, 0xe9, 0x9f, 0x0c, 0xd0, 0xc1, 0x72, 0xfe, 0xe1, 0x8e, 0xe2, 0xad, 0x94, 0x9e, 0x9a, 0xb2, 0x11, 0x14, 0xe4, 0xa4, 0x04, 0xf0, 0x98, 0xd1, 0x44, 0x22, 0x8a, 0x7c, 0x23, 0x5d, 0xdb, 0xe4, 0x29, 0xa6, 0x95, 0x4b, 0xbb, 0x34, 0xf7, 0x16, 0x8b, 0x3f, 0x67, 0x65, 0xc9, 0xa2, 0x2b, 0xcc, 0x5a, 0x25, 0xa7, 0xef, 0xd5, 0x2e, 0x99, 0x75, 0xc8, 0x0f, 0xd9, 0xff, 0x76, 0xf6, 0x1c}, + {0x34, 0x3f, 0x18, 0x36, 0x94, 0xeb, 0xda, 0xb6, 0x5a, 0x5c, 0xbe, 0xc7, 0x61, 0xa0, 0x43, 0x5f} }; @@ -101,6 +101,11 @@ WEAK_FUNCTION void hal_ks_unlock(void) return; } +WEAK_FUNCTION void hal_task_yield(void) +{ + return; +} + /* * Local variables: * indent-tabs-mode: nil @@ -50,9 +50,11 @@ hal_error_t hal_mkmif_init(hal_core_t *core) cmd.word = htonl(MKMIF_CTRL_CMD_INIT); - err = hal_core_alloc(MKMIF_NAME, &core) || - hal_io_write(core, MKMIF_ADDR_CTRL, cmd.byte, 4) || - hal_io_wait_ready(core); + if ((err = hal_core_alloc(MKMIF_NAME, &core)) != HAL_OK) + return err; + + if ((err = hal_io_write(core, MKMIF_ADDR_CTRL, cmd.byte, 4)) == HAL_OK) + err = hal_io_wait_ready(core); hal_core_free(core); return err; @@ -65,8 +67,10 @@ hal_error_t hal_mkmif_set_clockspeed(hal_core_t *core, const uint32_t divisor) data.word = htonl(divisor); - err = hal_core_alloc(MKMIF_NAME, &core) || - hal_io_write(core, MKMIF_ADDR_SCLK_DIV, data.byte, 4); + if ((err = hal_core_alloc(MKMIF_NAME, &core)) != HAL_OK) + return err; + + err = hal_io_write(core, MKMIF_ADDR_SCLK_DIV, data.byte, 4); hal_core_free(core); return err; @@ -77,10 +81,10 @@ hal_error_t hal_mkmif_get_clockspeed(hal_core_t *core, uint32_t *divisor) byteword_t data; hal_error_t err; - err = hal_core_alloc(MKMIF_NAME, &core) || - hal_io_read(core, MKMIF_ADDR_SCLK_DIV, data.byte, 4); + if ((err = hal_core_alloc(MKMIF_NAME, &core)) != HAL_OK) + return err; - if (err == HAL_OK) + if ((err = hal_io_read(core, MKMIF_ADDR_SCLK_DIV, data.byte, 4)) == HAL_OK) *divisor = htonl(data.word); hal_core_free(core); @@ -101,11 +105,11 @@ hal_error_t hal_mkmif_write(hal_core_t *core, uint32_t addr, const uint8_t *buf, for (; len > 0; addr += 4, buf += 4, len -= 4) { byteword_t write_addr; write_addr.word = htonl((uint32_t)addr); - if ((err = hal_io_write(core, MKMIF_ADDR_EMEM_ADDR, write_addr.byte, 4)) || - (err = hal_io_write(core, MKMIF_ADDR_EMEM_DATA, buf, 4)) || - (err = hal_io_write(core, MKMIF_ADDR_CTRL, cmd.byte, 4)) || - (err = hal_io_wait_ready(core))) - return err; + if ((err = hal_io_write(core, MKMIF_ADDR_EMEM_ADDR, write_addr.byte, 4)) != HAL_OK || + (err = hal_io_write(core, MKMIF_ADDR_EMEM_DATA, buf, 4)) != HAL_OK || + (err = hal_io_write(core, MKMIF_ADDR_CTRL, cmd.byte, 4)) != HAL_OK || + (err = hal_io_wait_ready(core)) != HAL_OK) + break; } } @@ -132,19 +136,19 @@ hal_error_t hal_mkmif_read(hal_core_t *core, uint32_t addr, uint8_t *buf, size_t cmd.word = htonl(MKMIF_CTRL_CMD_READ); - if ((err = hal_core_alloc(MKMIF_NAME, &core)) == HAL_OK) { - for (; len > 0; addr += 4, buf += 4, len -= 4) { - byteword_t read_addr; - read_addr.word = htonl((uint32_t)addr); - if ((err = hal_io_write(core, MKMIF_ADDR_EMEM_ADDR, read_addr.byte, 4)) || - (err = hal_io_write(core, MKMIF_ADDR_CTRL, cmd.byte, 4)) || - (err = hal_io_wait_valid(core)) || - (err = hal_io_read(core, MKMIF_ADDR_EMEM_DATA, buf, 4))) - goto out; - } + if ((err = hal_core_alloc(MKMIF_NAME, &core)) != HAL_OK) + return err; + + for (; len > 0; addr += 4, buf += 4, len -= 4) { + byteword_t read_addr; + read_addr.word = htonl((uint32_t)addr); + if ((err = hal_io_write(core, MKMIF_ADDR_EMEM_ADDR, read_addr.byte, 4)) != HAL_OK || + (err = hal_io_write(core, MKMIF_ADDR_CTRL, cmd.byte, 4)) != HAL_OK || + (err = hal_io_wait_valid(core)) != HAL_OK || + (err = hal_io_read(core, MKMIF_ADDR_EMEM_DATA, buf, 4)) != HAL_OK) + break; } -out: hal_core_free(core); return err; } @@ -226,6 +226,7 @@ hal_error_t hal_modexp(hal_core_t *core, /* Extract result */ check(get_buffer(core, MODEXPS6_ADDR_RESULT, result, mod_len)); + hal_core_free(core); return HAL_OK; } diff --git a/rpc_client.c b/rpc_client.c index aad9edf..e968369 100644 --- a/rpc_client.c +++ b/rpc_client.c @@ -787,7 +787,7 @@ static hal_error_t pkey_remote_match(const hal_client_handle_t client, uint8_t outbuf[nargs(11 + attributes_len * 2) + attributes_buffer_len + pad(sizeof(hal_uuid_t))]; uint8_t *optr = outbuf, *olimit = outbuf + sizeof(outbuf); - uint8_t inbuf[nargs(5) + pad(result_max * sizeof(hal_uuid_t))]; + uint8_t inbuf[nargs(5 + result_max) + pad(result_max * sizeof(hal_uuid_t))]; const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf); hal_error_t rpc_ret; @@ -33,8 +33,6 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include <assert.h> - #include "hal.h" #include "hal_internal.h" @@ -46,7 +44,8 @@ static hal_error_t get_version(uint32_t *version) static hal_error_t get_random(void *buffer, const size_t length) { - assert(buffer != NULL && length > 0); + if (buffer == NULL || length == 0) + return HAL_ERROR_IMPOSSIBLE; return hal_get_random(NULL, buffer, length); } @@ -78,17 +77,25 @@ typedef struct { } client_slot_t; #ifndef HAL_PIN_MINIMUM_ITERATIONS -#define HAL_PIN_MINIMUM_ITERATIONS 10000 +#define HAL_PIN_MINIMUM_ITERATIONS 1000 #endif #ifndef HAL_PIN_DEFAULT_ITERATIONS -#define HAL_PIN_DEFAULT_ITERATIONS 20000 +#define HAL_PIN_DEFAULT_ITERATIONS 2000 #endif static uint32_t hal_pin_default_iterations = HAL_PIN_DEFAULT_ITERATIONS; +/* + * Seconds to delay when given a bad PIN. + */ + +#ifndef HAL_PIN_DELAY_ON_FAILURE +#define HAL_PIN_DELAY_ON_FAILURE 5 +#endif + #ifndef HAL_STATIC_CLIENT_STATE_BLOCKS -#define HAL_STATIC_CLIENT_STATE_BLOCKS 10 +#define HAL_STATIC_CLIENT_STATE_BLOCKS 10 #endif #if HAL_STATIC_CLIENT_STATE_BLOCKS > 0 @@ -101,8 +108,8 @@ static client_slot_t client_handle[HAL_STATIC_CLIENT_STATE_BLOCKS]; * them. HAL_USER_NONE indicates an empty slot in the table. */ -static inline client_slot_t *alloc_slot(const hal_client_handle_t client, - const hal_user_t user) +static inline hal_error_t alloc_slot(const hal_client_handle_t client, + const hal_user_t user) { client_slot_t *slot = NULL; hal_critical_section_start(); @@ -126,7 +133,7 @@ static inline client_slot_t *alloc_slot(const hal_client_handle_t client, } hal_critical_section_end(); - return slot; + return slot == NULL ? HAL_ERROR_NO_CLIENT_SLOTS_AVAILABLE : HAL_OK; } static inline hal_error_t clear_slot(client_slot_t *slot) @@ -167,8 +174,8 @@ 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) { - assert(pin != NULL && pin_len != 0); - assert(user == HAL_USER_NORMAL || user == HAL_USER_SO || user == HAL_USER_WHEEL); + if (pin == NULL || pin_len == 0 || (user != HAL_USER_NORMAL && user != HAL_USER_SO && user != HAL_USER_WHEEL)) + return HAL_ERROR_IMPOSSIBLE; const hal_ks_pin_t *p; hal_error_t err; @@ -187,19 +194,19 @@ static hal_error_t login(const hal_client_handle_t client, for (int i = 0; i < sizeof(buf); i++) diff |= buf[i] ^ p->pin[i]; - if (diff != 0) + if (diff != 0) { + hal_sleep(HAL_PIN_DELAY_ON_FAILURE); return HAL_ERROR_PIN_INCORRECT; + } - if (alloc_slot(client, user) == NULL) - return HAL_ERROR_NO_CLIENT_SLOTS_AVAILABLE; - - return HAL_OK; + return alloc_slot(client, user); } static hal_error_t is_logged_in(const hal_client_handle_t client, const hal_user_t user) { - assert(user == HAL_USER_NORMAL || user == HAL_USER_SO || user == HAL_USER_WHEEL); + if (user != HAL_USER_NORMAL && user != HAL_USER_SO && user != HAL_USER_WHEEL) + return HAL_ERROR_IMPOSSIBLE; client_slot_t *slot = find_handle(client); @@ -216,34 +223,29 @@ static hal_error_t logout(const hal_client_handle_t client) static hal_error_t logout_all(void) { - /* - * This is a bit inefficient, but it lets us keep the control - * structure simple. - */ +#if HAL_STATIC_CLIENT_STATE_BLOCKS > 0 client_slot_t *slot; hal_error_t err; + int i = 0; do { - slot = NULL; - -#if HAL_STATIC_CLIENT_STATE_BLOCKS > 0 hal_critical_section_start(); - for (int i = 0; slot == NULL && i < sizeof(client_handle)/sizeof(*client_handle); i++) + for (slot = NULL; slot == NULL && i < sizeof(client_handle)/sizeof(*client_handle); i++) if (client_handle[i].logged_in != HAL_USER_NONE) slot = &client_handle[i]; hal_critical_section_end(); -#endif - if ((err = clear_slot(slot)) != HAL_OK) return err; } while (slot != NULL); +#endif + return HAL_OK; } @@ -251,7 +253,8 @@ 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 (newpin == NULL || newpin_len < hal_rpc_min_pin_length || newpin_len > hal_rpc_max_pin_length) + return HAL_ERROR_IMPOSSIBLE; if ((user != HAL_USER_NORMAL || is_logged_in(client, HAL_USER_SO) != HAL_OK) && is_logged_in(client, HAL_USER_WHEEL) != HAL_OK) @@ -79,10 +79,10 @@ static inline hal_pkey_slot_t *alloc_slot(const hal_key_flags_t flags) glop |= HAL_PKEY_HANDLE_TOKEN_FLAG; for (int i = 0; slot == NULL && i < sizeof(pkey_slot)/sizeof(*pkey_slot); i++) { - if (pkey_slot[i].pkey_handle.handle != HAL_HANDLE_NONE) + if (pkey_slot[i].pkey.handle != HAL_HANDLE_NONE) continue; memset(&pkey_slot[i], 0, sizeof(pkey_slot[i])); - pkey_slot[i].pkey_handle.handle = i | glop; + pkey_slot[i].pkey.handle = i | glop; pkey_slot[i].hint = -1; slot = &pkey_slot[i]; } @@ -120,7 +120,7 @@ static inline hal_pkey_slot_t *find_handle(const hal_pkey_handle_t handle) #if HAL_STATIC_PKEY_STATE_BLOCKS > 0 const int i = (int) (handle.handle & 0xFFFF); - if (i < sizeof(pkey_slot)/sizeof(*pkey_slot) && pkey_slot[i].pkey_handle.handle == handle.handle) + if (i < sizeof(pkey_slot)/sizeof(*pkey_slot) && pkey_slot[i].pkey.handle == handle.handle) slot = &pkey_slot[i]; #endif @@ -138,30 +138,15 @@ hal_error_t hal_pkey_logout(const hal_client_handle_t client) return HAL_OK; hal_error_t err; - hal_ks_t *ks; - if ((err = hal_ks_open(hal_ks_volatile_driver, &ks)) != HAL_OK) - return err; - if ((err = hal_ks_logout(ks, client)) == HAL_OK) - err = hal_ks_close(ks); - else - (void) hal_ks_close(ks); - if (err != HAL_OK) - return err; - - if ((err = hal_ks_open(hal_ks_token_driver, &ks)) != HAL_OK) - return err; - if ((err = hal_ks_logout(ks, client)) == HAL_OK) - err = hal_ks_close(ks); - else - (void) hal_ks_close(ks); - if (err != HAL_OK) + if ((err = hal_ks_logout(hal_ks_volatile, client)) != HAL_OK || + (err = hal_ks_logout(hal_ks_token, client)) != HAL_OK) return err; hal_critical_section_start(); for (int i = 0; i < sizeof(pkey_slot)/sizeof(*pkey_slot); i++) - if (pkey_slot[i].pkey_handle.handle == client.handle) + if (pkey_slot[i].pkey.handle == client.handle) memset(&pkey_slot[i], 0, sizeof(pkey_slot[i])); hal_critical_section_end(); @@ -311,52 +296,25 @@ static hal_error_t pkcs1_5_pad(const uint8_t * const data, const size_t data_len } /* - * Given key flags, open appropriate keystore driver. - */ - -static inline hal_error_t ks_open_from_flags(hal_ks_t **ks, const hal_key_flags_t flags) -{ - return hal_ks_open((flags & HAL_KEY_FLAG_TOKEN) == 0 - ? hal_ks_volatile_driver - : hal_ks_token_driver, - ks); -} - -/* - * Fetch a key from a driver. + * Given key flags, return appropriate keystore. */ -static inline hal_error_t ks_fetch_from_driver(const hal_ks_driver_t * const driver, - hal_pkey_slot_t *slot, - uint8_t *der, size_t *der_len, const size_t der_max) +static inline hal_ks_t *ks_from_flags(const hal_key_flags_t flags) { - hal_ks_t *ks = NULL; - hal_error_t err; - - if ((err = hal_ks_open(driver, &ks)) != HAL_OK) - return err; - - if ((err = hal_ks_fetch(ks, slot, der, der_len, der_max)) == HAL_OK) - err = hal_ks_close(ks); - else - (void) hal_ks_close(ks); - - return err; + return (flags & HAL_KEY_FLAG_TOKEN) == 0 ? hal_ks_volatile : hal_ks_token; } /* - * Same thing but from key flag in slot object rather than explict driver. + * Fetch a key from keystore indicated by key flag in slot object. */ static inline hal_error_t ks_fetch_from_flags(hal_pkey_slot_t *slot, uint8_t *der, size_t *der_len, const size_t der_max) { - assert(slot != NULL); + if (slot == NULL) + return HAL_ERROR_IMPOSSIBLE; - return ks_fetch_from_driver((slot->flags & HAL_KEY_FLAG_TOKEN) == 0 - ? hal_ks_volatile_driver - : hal_ks_token_driver, - slot, der, der_len, der_max); + return hal_ks_fetch(ks_from_flags(slot->flags), slot, der, der_len, der_max); } @@ -377,7 +335,6 @@ static hal_error_t pkey_local_load(const hal_client_handle_t client, hal_curve_name_t curve; hal_pkey_slot_t *slot; hal_key_type_t type; - hal_ks_t *ks = NULL; hal_error_t err; if ((err = check_writable(client, flags)) != HAL_OK) @@ -392,24 +349,18 @@ static hal_error_t pkey_local_load(const hal_client_handle_t client, if ((err = hal_uuid_gen(&slot->name)) != HAL_OK) return err; - slot->client_handle = client; - slot->session_handle = session; - slot->type = type; - slot->curve = curve; - slot->flags = flags; - - if ((err = ks_open_from_flags(&ks, flags)) == HAL_OK && - (err = hal_ks_store(ks, slot, der, der_len)) == HAL_OK) - err = hal_ks_close(ks); - else if (ks != NULL) - (void) hal_ks_close(ks); + slot->client = client; + slot->session = session; + slot->type = type; + slot->curve = curve; + slot->flags = flags; - if (err != HAL_OK) { + if ((err = hal_ks_store(ks_from_flags(flags), slot, der, der_len)) != HAL_OK) { slot->type = HAL_KEY_TYPE_NONE; return err; } - *pkey = slot->pkey_handle; + *pkey = slot->pkey; *name = slot->name; return HAL_OK; } @@ -434,20 +385,20 @@ static hal_error_t pkey_local_open(const hal_client_handle_t client, if ((slot = alloc_slot(0)) == NULL) return HAL_ERROR_NO_KEY_SLOTS_AVAILABLE; - slot->name = *name; - slot->client_handle = client; - slot->session_handle = session; + slot->name = *name; + slot->client = client; + slot->session = session; - if ((err = ks_fetch_from_driver(hal_ks_token_driver, slot, NULL, NULL, 0)) == HAL_OK) - slot->pkey_handle.handle |= HAL_PKEY_HANDLE_TOKEN_FLAG; + if ((err = hal_ks_fetch(hal_ks_token, slot, NULL, NULL, 0)) == HAL_OK) + slot->pkey.handle |= HAL_PKEY_HANDLE_TOKEN_FLAG; else if (err == HAL_ERROR_KEY_NOT_FOUND) - err = ks_fetch_from_driver(hal_ks_volatile_driver, slot, NULL, NULL, 0); + err = hal_ks_fetch(hal_ks_volatile, slot, NULL, NULL, 0); if (err != HAL_OK) goto fail; - *pkey = slot->pkey_handle; + *pkey = slot->pkey; return HAL_OK; fail: @@ -472,7 +423,6 @@ static hal_error_t pkey_local_generate_rsa(const hal_client_handle_t client, uint8_t keybuf[hal_rsa_key_t_size]; hal_rsa_key_t *key = NULL; hal_pkey_slot_t *slot; - hal_ks_t *ks = NULL; hal_error_t err; if ((err = check_writable(client, flags)) != HAL_OK) @@ -484,11 +434,11 @@ static hal_error_t pkey_local_generate_rsa(const hal_client_handle_t client, if ((err = hal_uuid_gen(&slot->name)) != HAL_OK) return err; - slot->client_handle = client; - slot->session_handle = session; - slot->type = HAL_KEY_TYPE_RSA_PRIVATE; - slot->curve = HAL_CURVE_NONE; - slot->flags = flags; + slot->client = client; + slot->session = session; + slot->type = HAL_KEY_TYPE_RSA_PRIVATE; + slot->curve = HAL_CURVE_NONE; + slot->flags = flags; if ((err = hal_rsa_key_gen(NULL, &key, keybuf, sizeof(keybuf), key_length / 8, public_exponent, public_exponent_len)) != HAL_OK) { @@ -499,12 +449,8 @@ static hal_error_t pkey_local_generate_rsa(const hal_client_handle_t client, uint8_t der[hal_rsa_private_key_to_der_len(key)]; size_t der_len; - if ((err = hal_rsa_private_key_to_der(key, der, &der_len, sizeof(der))) == HAL_OK && - (err = ks_open_from_flags(&ks, flags)) == HAL_OK && - (err = hal_ks_store(ks, slot, der, der_len)) == HAL_OK) - err = hal_ks_close(ks); - else if (ks != NULL) - (void) hal_ks_close(ks); + if ((err = hal_rsa_private_key_to_der(key, der, &der_len, sizeof(der))) == HAL_OK) + err = hal_ks_store(ks_from_flags(flags), slot, der, der_len); memset(keybuf, 0, sizeof(keybuf)); memset(der, 0, sizeof(der)); @@ -514,7 +460,7 @@ static hal_error_t pkey_local_generate_rsa(const hal_client_handle_t client, return err; } - *pkey = slot->pkey_handle; + *pkey = slot->pkey; *name = slot->name; return HAL_OK; } @@ -536,7 +482,6 @@ static hal_error_t pkey_local_generate_ec(const hal_client_handle_t client, uint8_t keybuf[hal_ecdsa_key_t_size]; hal_ecdsa_key_t *key = NULL; hal_pkey_slot_t *slot; - hal_ks_t *ks = NULL; hal_error_t err; if ((err = check_writable(client, flags)) != HAL_OK) @@ -548,11 +493,11 @@ static hal_error_t pkey_local_generate_ec(const hal_client_handle_t client, if ((err = hal_uuid_gen(&slot->name)) != HAL_OK) return err; - slot->client_handle = client; - slot->session_handle = session; - slot->type = HAL_KEY_TYPE_EC_PRIVATE; - slot->curve = curve; - slot->flags = flags; + slot->client = client; + slot->session = session; + slot->type = HAL_KEY_TYPE_EC_PRIVATE; + slot->curve = curve; + slot->flags = flags; if ((err = hal_ecdsa_key_gen(NULL, &key, keybuf, sizeof(keybuf), curve)) != HAL_OK) { slot->type = HAL_KEY_TYPE_NONE; @@ -562,12 +507,8 @@ static hal_error_t pkey_local_generate_ec(const hal_client_handle_t client, uint8_t der[hal_ecdsa_private_key_to_der_len(key)]; size_t der_len; - if ((err = hal_ecdsa_private_key_to_der(key, der, &der_len, sizeof(der))) == HAL_OK && - (err = ks_open_from_flags(&ks, flags)) == HAL_OK && - (err = hal_ks_store(ks, slot, der, der_len)) == HAL_OK) - err = hal_ks_close(ks); - else if (ks != NULL) - (void) hal_ks_close(ks); + if ((err = hal_ecdsa_private_key_to_der(key, der, &der_len, sizeof(der))) == HAL_OK) + err = hal_ks_store(ks_from_flags(flags), slot, der, der_len); memset(keybuf, 0, sizeof(keybuf)); memset(der, 0, sizeof(der)); @@ -577,7 +518,7 @@ static hal_error_t pkey_local_generate_ec(const hal_client_handle_t client, return err; } - *pkey = slot->pkey_handle; + *pkey = slot->pkey; *name = slot->name; return HAL_OK; } @@ -609,17 +550,12 @@ static hal_error_t pkey_local_delete(const hal_pkey_handle_t pkey) if (slot == NULL) return HAL_ERROR_KEY_NOT_FOUND; - hal_ks_t *ks = NULL; hal_error_t err; - if ((err = check_writable(slot->client_handle, slot->flags)) != HAL_OK) + if ((err = check_writable(slot->client, slot->flags)) != HAL_OK) return err; - if ((err = ks_open_from_flags(&ks, slot->flags)) == HAL_OK && - (err = hal_ks_delete(ks, slot)) == HAL_OK) - err = hal_ks_close(ks); - else if (ks != NULL) - (void) hal_ks_close(ks); + err = hal_ks_delete(ks_from_flags(slot->flags), slot); if (err == HAL_OK || err == HAL_ERROR_KEY_NOT_FOUND) clear_slot(slot); @@ -1059,7 +995,7 @@ static hal_error_t pkey_local_verify(const hal_pkey_handle_t pkey, return err; } -static inline hal_error_t match_one_keystore(const hal_ks_driver_t * const driver, +static inline hal_error_t match_one_keystore(hal_ks_t *ks, const hal_client_handle_t client, const hal_session_handle_t session, const hal_key_type_t type, @@ -1073,21 +1009,12 @@ static inline hal_error_t match_one_keystore(const hal_ks_driver_t * const drive const unsigned result_max, const hal_uuid_t * const previous_uuid) { - hal_ks_t *ks = NULL; hal_error_t err; unsigned len; - if ((err = hal_ks_open(driver, &ks)) != HAL_OK) - return err; - if ((err = hal_ks_match(ks, client, session, type, curve, mask, flags, attributes, attributes_len, - *result, &len, result_max, previous_uuid)) != HAL_OK) { - (void) hal_ks_close(ks); - return err; - } - - if ((err = hal_ks_close(ks)) != HAL_OK) + *result, &len, result_max, previous_uuid)) != HAL_OK) return err; *result += len; @@ -1138,7 +1065,7 @@ static hal_error_t pkey_local_match(const hal_client_handle_t client, case MATCH_STATE_TOKEN: if (((mask & HAL_KEY_FLAG_TOKEN) == 0 || (mask & flags & HAL_KEY_FLAG_TOKEN) != 0) && - (err = match_one_keystore(hal_ks_token_driver, client, session, type, curve, + (err = match_one_keystore(hal_ks_token, client, session, type, curve, mask, flags, attributes, attributes_len, &result, result_len, result_max - *result_len, prev)) != HAL_OK) return err; @@ -1149,7 +1076,7 @@ static hal_error_t pkey_local_match(const hal_client_handle_t client, case MATCH_STATE_VOLATILE: if (((mask & HAL_KEY_FLAG_TOKEN) == 0 || (mask & flags & HAL_KEY_FLAG_TOKEN) == 0) && - (err = match_one_keystore(hal_ks_volatile_driver, client, session, type, curve, + (err = match_one_keystore(hal_ks_volatile, client, session, type, curve, mask, flags, attributes, attributes_len, &result, result_len, result_max - *result_len, prev)) != HAL_OK) return err; @@ -1174,19 +1101,12 @@ static hal_error_t pkey_local_set_attributes(const hal_pkey_handle_t pkey, if (slot == NULL) return HAL_ERROR_KEY_NOT_FOUND; - hal_ks_t *ks = NULL; hal_error_t err; - if ((err = check_writable(slot->client_handle, slot->flags)) != HAL_OK) + if ((err = check_writable(slot->client, slot->flags)) != HAL_OK) return err; - if ((err = ks_open_from_flags(&ks, slot->flags)) == HAL_OK && - (err = hal_ks_set_attributes(ks, slot, attributes, attributes_len)) == HAL_OK) - err = hal_ks_close(ks); - else if (ks != NULL) - (void) hal_ks_close(ks); - - return err; + return hal_ks_set_attributes(ks_from_flags(slot->flags), slot, attributes, attributes_len); } static hal_error_t pkey_local_get_attributes(const hal_pkey_handle_t pkey, @@ -1200,17 +1120,8 @@ static hal_error_t pkey_local_get_attributes(const hal_pkey_handle_t pkey, if (slot == NULL) return HAL_ERROR_KEY_NOT_FOUND; - hal_ks_t *ks = NULL; - hal_error_t err; - - if ((err = ks_open_from_flags(&ks, slot->flags)) == HAL_OK && - (err = hal_ks_get_attributes(ks, slot, attributes, attributes_len, - attributes_buffer, attributes_buffer_len)) == HAL_OK) - err = hal_ks_close(ks); - else if (ks != NULL) - (void) hal_ks_close(ks); - - return err; + return hal_ks_get_attributes(ks_from_flags(slot->flags), slot, attributes, attributes_len, + attributes_buffer, attributes_buffer_len); } static hal_error_t pkey_local_export(const hal_pkey_handle_t pkey_handle, diff --git a/rpc_server.c b/rpc_server.c index 8d8af18..f64d7d6 100644 --- a/rpc_server.c +++ b/rpc_server.c @@ -966,13 +966,7 @@ hal_error_t hal_rpc_server_dispatch(const uint8_t * const ibuf, const size_t ile } if (handler) - for (int i = 0; i < 3; ++i) { - ret = handler(&iptr, ilimit, &optr, olimit); - if (ret != HAL_ERROR_CORE_BUSY) - break; - iptr = ibuf + 4; - optr = obuf + 12; - } + ret = handler(&iptr, ilimit, &optr, olimit); else ret = HAL_ERROR_RPC_BAD_FUNCTION; @@ -999,9 +993,9 @@ hal_error_t hal_rpc_server_init(void) { hal_error_t err; - if ((err = hal_ks_init(hal_ks_volatile_driver, 1)) != HAL_OK || - (err = hal_ks_init(hal_ks_token_driver, 1)) != HAL_OK || - (err = hal_rpc_server_transport_init()) != HAL_OK) + if ((err = hal_ks_init(hal_ks_volatile, 1)) != HAL_OK || + (err = hal_ks_init(hal_ks_token, 1)) != HAL_OK || + (err = hal_rpc_server_transport_init()) != HAL_OK) return err; return HAL_OK; @@ -1011,9 +1005,7 @@ hal_error_t hal_rpc_server_close(void) { hal_error_t err; - if ((err = hal_rpc_server_transport_close()) != HAL_OK || - (err = hal_ks_shutdown(hal_ks_token_driver)) != HAL_OK || - (err = hal_ks_shutdown(hal_ks_volatile_driver)) != HAL_OK) + if ((err = hal_rpc_server_transport_close()) != HAL_OK) return err; return HAL_OK; @@ -189,7 +189,7 @@ static hal_error_t unpack_fp(const fp_int * const bn, uint8_t *buffer, const siz * wrap result back up as a bignum. */ -static hal_error_t modexp(const hal_core_t *core, +static hal_error_t modexp(hal_core_t *core, const fp_int * msg, const fp_int * const exp, const fp_int * const mod, @@ -277,7 +277,7 @@ static hal_error_t modexp(const hal_core_t *core, /* ignored */ * try. Come back to this if it looks like a bottleneck. */ -static hal_error_t create_blinding_factors(const hal_core_t *core, const hal_rsa_key_t * const key, fp_int *bf, fp_int *ubf) +static hal_error_t create_blinding_factors(hal_core_t *core, const hal_rsa_key_t * const key, fp_int *bf, fp_int *ubf) { assert(key != NULL && bf != NULL && ubf != NULL); @@ -305,7 +305,7 @@ static hal_error_t create_blinding_factors(const hal_core_t *core, const hal_rsa * RSA decryption via Chinese Remainder Theorem (Garner's formula). */ -static hal_error_t rsa_crt(const hal_core_t *core, const hal_rsa_key_t * const key, fp_int *msg, fp_int *sig) +static hal_error_t rsa_crt(hal_core_t *core, const hal_rsa_key_t * const key, fp_int *msg, fp_int *sig) { assert(key != NULL && msg != NULL && sig != NULL); @@ -376,7 +376,7 @@ static hal_error_t rsa_crt(const hal_core_t *core, const hal_rsa_key_t * const k * to the caller. */ -hal_error_t hal_rsa_encrypt(const hal_core_t *core, +hal_error_t hal_rsa_encrypt(hal_core_t *core, const hal_rsa_key_t * const key, const uint8_t * const input, const size_t input_len, uint8_t * output, const size_t output_len) @@ -401,7 +401,7 @@ hal_error_t hal_rsa_encrypt(const hal_core_t *core, return err; } -hal_error_t hal_rsa_decrypt(const hal_core_t *core, +hal_error_t hal_rsa_decrypt(hal_core_t *core, const hal_rsa_key_t * const key, const uint8_t * const input, const size_t input_len, uint8_t * output, const size_t output_len) @@ -614,7 +614,7 @@ static hal_error_t find_prime(const unsigned prime_length, * Generate a new RSA keypair. */ -hal_error_t hal_rsa_key_gen(const hal_core_t *core, +hal_error_t hal_rsa_key_gen(hal_core_t *core, hal_rsa_key_t **key_, void *keybuf, const size_t keybuf_len, const unsigned key_length, diff --git a/unit-tests.py b/unit-tests.py index 10aa810..9ebf91e 100644 --- a/unit-tests.py +++ b/unit-tests.py @@ -132,6 +132,10 @@ class TestCase(unittest.TestCase): self.endTime = datetime.datetime.now() super(TestCase, self).tearDown() + def skipUnlessAll(self, reason): + if not args.all_tests: + self.skipTest(reason) + class TextTestResult(unittest.TextTestResult): def addSuccess(self, test): @@ -197,15 +201,12 @@ class TestPIN(TestCase): self.assertRaises(HAL_ERROR_FORBIDDEN, hsm.is_logged_in, user2) hsm.logout() - @unittest.skipUnless(args.all_tests, "Slow") def test_login_wheel(self): self.login_logout(HAL_USER_WHEEL) - @unittest.skipUnless(args.all_tests, "Slow") def test_login_so(self): self.login_logout(HAL_USER_SO) - @unittest.skipUnless(args.all_tests, "Slow") def test_login_user(self): self.login_logout(HAL_USER_NORMAL) @@ -292,18 +293,19 @@ class TestPKeyGen(TestCaseLoggedIn): def test_gen_sign_verify_rsa_1024_sha256(self): self.gen_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA256, 1024) - @unittest.skipUnless(args.all_tests, "Slow") def test_gen_sign_verify_rsa_2048_sha384(self): + self.skipUnlessAll("Slow") self.gen_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA384, 2048) - @unittest.skipUnless(args.all_tests, "Hideously slow") def test_gen_sign_verify_rsa_4096_sha512(self): + self.skipUnlessAll("Hideously slow") self.gen_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA512, 4096) def test_gen_unsupported_length(self): with self.assertRaises(HAL_ERROR_BAD_ARGUMENTS): hsm.pkey_generate_rsa(1028).delete() + class TestPKeyHashing(TestCaseLoggedIn): """ Tests involving various ways of doing the hashing for public key operations. @@ -597,17 +599,20 @@ class TestPKeyMatch(TestCaseLoggedIn): tags.extend(PreloadedKey.db) self.assertEqual(len(tags), len(uuids)) + n = 0 self.assertEqual(uuids, set(k.uuid for n, k in self.match(mask = mask, flags = flags, uuids = uuids))) for keytype in set(HALKeyType.index.itervalues()) - {HAL_KEY_TYPE_NONE}: + n = 0 for n, k in self.match(mask = mask, flags = flags, uuids = uuids, type = keytype): self.assertEqual(k.key_type, keytype) self.assertEqual(k.get_attributes({0}).pop(0), str(keytype)) self.assertEqual(n, sum(1 for t1, t2 in tags if t1 == keytype)) for curve in set(HALCurve.index.itervalues()) - {HAL_CURVE_NONE}: + n = 0 for n, k in self.match(mask = mask, flags = flags, uuids = uuids, curve = curve): self.assertEqual(k.key_curve, curve) self.assertEqual(k.get_attributes({1}).pop(1), str(curve)) @@ -616,6 +621,7 @@ class TestPKeyMatch(TestCaseLoggedIn): self.assertEqual(n, sum(1 for t1, t2 in tags if t2 == curve)) for keylen in set(kl for kt, kl in tags if not isinstance(kl, Enum)): + n = 0 for n, k in self.match(mask = mask, flags = flags, uuids = uuids, attributes = {1 : str(keylen)}): self.assertEqual(keylen, int(k.get_attributes({1}).pop(1))) @@ -624,6 +630,7 @@ class TestPKeyMatch(TestCaseLoggedIn): self.assertEqual(n, sum(1 for t1, t2 in tags if not isinstance(t2, Enum) and t2 == keylen)) + n = 0 for n, k in self.match(mask = mask, flags = flags, uuids = uuids, type = HAL_KEY_TYPE_RSA_PUBLIC, attributes = {1 : "2048"}): self.assertEqual(k.key_type, HAL_KEY_TYPE_RSA_PUBLIC) @@ -650,7 +657,7 @@ class TestPKeyAttribute(TestCaseLoggedIn): try: with hsm.pkey_open(uuid) as pkey: pkey.delete() - except: + except Exception as e: logger.debug("Problem deleting key %s: %s", uuid, e) def load_and_fill(self, flags, n_keys = 1, n_attrs = 2, n_fill = 0): @@ -664,17 +671,36 @@ class TestPKeyAttribute(TestCaseLoggedIn): for j in xrange(n_attrs))) pinwheel() + # These sizes work with a 4096-byte keystore block; if you tweak + # the undelrying block size, you may need to tweak these tests too. + + def test_attribute_svelt_volatile_many(self): + self.load_and_fill(0, n_attrs = 64) + def test_attribute_bloat_volatile_many(self): - self.load_and_fill(0, n_attrs = 128) # 192 + with self.assertRaises(HAL_ERROR_RESULT_TOO_LONG): + self.load_and_fill(0, n_attrs = 128) + + def test_attribute_svelt_volatile_big(self): + self.load_and_fill(0, n_attrs = 6, n_fill = 256) def test_attribute_bloat_volatile_big(self): - self.load_and_fill(0, n_attrs = 6, n_fill = 512) + with self.assertRaises(HAL_ERROR_RESULT_TOO_LONG): + self.load_and_fill(0, n_attrs = 6, n_fill = 512) + + def test_attribute_svelt_token_many(self): + self.load_and_fill(HAL_KEY_FLAG_TOKEN, n_attrs = 64) def test_attribute_bloat_token_many(self): - self.load_and_fill(HAL_KEY_FLAG_TOKEN, n_attrs = 128) + with self.assertRaises(HAL_ERROR_RESULT_TOO_LONG): + self.load_and_fill(HAL_KEY_FLAG_TOKEN, n_attrs = 128) + + def test_attribute_svelt_token_big(self): + self.load_and_fill(HAL_KEY_FLAG_TOKEN, n_attrs = 6, n_fill = 256) def test_attribute_bloat_token_big(self): - self.load_and_fill(HAL_KEY_FLAG_TOKEN, n_attrs = 4, n_fill = 512) # [16, 1024] + with self.assertRaises(HAL_ERROR_RESULT_TOO_LONG): + self.load_and_fill(HAL_KEY_FLAG_TOKEN, n_attrs = 6, n_fill = 512) @unittest.skipUnless(ecdsa_loaded, "Requires Python ECDSA package") @@ -903,11 +929,19 @@ class TestPKeyBackup(TestCaseLoggedIn): encryptedPrivateKeyInfo = DerSequence() encryptedPrivateKeyInfo.decode(der) encryptionAlgorithm = DerSequence() - encryptionAlgorithm.decode(encryptedPrivateKeyInfo[0]) algorithm = DerObjectId() - algorithm.decode(encryptionAlgorithm[0]) encryptedData = DerOctetString() - encryptedData.decode(encryptedPrivateKeyInfo[1]) + encryptionAlgorithm.decode(encryptedPrivateKeyInfo[0]) + # <kludge> + # Sigh, bugs in PyCrypto ASN.1 code. Should do: + # + #algorithm.decode(encryptionAlgorithm[0]) + #encryptedData.decode(encryptedPrivateKeyInfo[1]) + # + # but due to bugs in those methods we must instead do: + DerObject.decode(algorithm, encryptionAlgorithm[0]) + DerObject.decode(encryptedData, encryptedPrivateKeyInfo[1]) + # </kludge> if algorithm.payload != oid: raise ValueError return encryptedData.payload diff --git a/utils/last_gasp_default_pin b/utils/last_gasp_default_pin index 50d822f..8a91b8a 100755 --- a/utils/last_gasp_default_pin +++ b/utils/last_gasp_default_pin @@ -54,7 +54,7 @@ parser.add_argument("-p", "--pin", help = "PIN plaintext before PBKDF2 processing") parser.add_argument("-i", "--iterations", type = int, - default = 10000, + default = 1000, help = "PBKDF2 iteration count") parser.add_argument("-d", "--derived-key-length", type = int, |