diff options
-rw-r--r-- | Makefile | 34 | ||||
-rw-r--r-- | README.md | 91 | ||||
-rw-r--r-- | core.c | 2 | ||||
-rw-r--r-- | cryptech/libhal.py | 5 | ||||
-rwxr-xr-x | cryptech_muxd | 41 | ||||
-rw-r--r-- | hal.h | 5 | ||||
-rw-r--r-- | hal_internal.h | 414 | ||||
-rw-r--r-- | ks.c | 938 | ||||
-rw-r--r-- | ks.h | 430 | ||||
-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 | 666 | ||||
-rw-r--r-- | ks_volatile.c | 668 | ||||
-rw-r--r-- | rpc_misc.c | 92 | ||||
-rw-r--r-- | rpc_pkey.c | 200 | ||||
-rw-r--r-- | rpc_server.c | 10 | ||||
-rw-r--r-- | unit-tests.py | 41 |
18 files changed, 2674 insertions, 3723 deletions
@@ -36,15 +36,13 @@ STATIC_HMAC_STATE_BLOCKS = 16 STATIC_PKEY_STATE_BLOCKS = 256 STATIC_KS_VOLATILE_SLOTS = 128 -INC = hal.h hal_internal.h 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 ?= yes @@ -54,7 +52,6 @@ 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 +59,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 +135,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. @@ -269,16 +258,19 @@ daemon: mixed .PHONY: client mixed server serial daemon -${OBJ}: ${INC} - ${LIB}: ${OBJ} ${AR} rcs $@ $^ -asn1.o rsa.o ecdsa.o: asn1_internal.h -ecdsa.o: ecdsa_curves.h -novena-eim.o hal_io_eim.o: novena-eim.h -slip.o rpc_client_serial.o rpc_server_serial.o: slip_internal.h -ks_flash.o: last_gasp_pin_internal.h +asn1.o rsa.o ecdsa.o: asn1_internal.h +ecdsa.o: ecdsa_curves.h +${OBJ}: hal.h +${OBJ}: hal_internal.h +ks.o ks_token.o ks_volatile.o ks_attribute.o ks_index.o: ks.h +ks_token.o: last_gasp_pin_internal.h +novena-eim.o hal_io_eim.o: novena-eim.h +slip.o rpc_client_serial.o rpc_server_serial.o: slip_internal.h +${OBJ}: verilog_constants.h +rpc_client.o rpc_server.o xdr.o: xdr_internal.h last_gasp_pin_internal.h: ./utils/last_gasp_default_pin >$@ @@ -64,6 +64,7 @@ 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 ## The RSA implementation includes a compile-time option to bypass the @@ -72,6 +73,7 @@ a tad slow at the moment (others are hard at work fixing this). The RSA implementation includes optional blinding (enabled by default). + ## ECDSA ## The ECDSA implementation is specific to the NIST prime curves P-256, @@ -112,6 +114,92 @@ 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. + +## Keystore ## + +The keystore is basically a light-weight database intended to be run +directly over some kind of block-access device, with an internal +low-level driver interface so that we can use the same API for +multiple keystore devices (eg, flash for "token objects" and RAM for +"session objects", in the PKCS #11 senses of those terms). + +The available storage is divided up into "blocks" of a fixed size; for +simplicity, the block size is a multiple of the subsector size of the +flash chip on the Alpha platform, since that's the minimum erasable +unit. All state stored in the keystore itself follows the conventions +needed for flash devices, whether the device in question is flash or +not. The basic rule here is that one can only clear bits, never set +them: the only way to set a bit is to erase the whole block and start +over. So blocks progress from an initial state ("erased") where all +bits are set to one, through several states where the block contains +useful data, and ending in a state where all bits are set to zero +("zeroed"), because that's the way that flash hardware works. + +The keystore implementation also applies a light-weight form of wear +leveling to all keystore devices, whether they're flash devices or +not. The wear-leveling mechanism is not particularly sophisticated, +but should suffice. The wear-leveling code treats the entirety of a +particular keystore device as a ring buffer of blocks, and keeps track +of which blocks have been used recently by zeroing blocks upon freeing +them rather than erasing them immediately, while also always keeping +the block at the current head of the free list in the erased state. +Taken together, this is enough to recover location of the block at the +head of the free list after a reboot, which is sufficient for a +round-robin wear leveling strategy. + +The block format includes a field for a CRC-32 checksum, which covers +the entire block except for a few specific fields which need to be +left out. On reboot, blocks with bad CRC-32 values are considered +candidates for reuse, but are placed at the end of the free list, +preserve their contents for as long as possible in case the real +problem is a buggy firmware update. + +At the moment, the decision about whether to use the CRC-32 mechanism +is up to the individual driver: the flash driver uses it, the RAM +driver (which never stores anything across reboots anyway) does not. + +Since the flash-like semantics do not allow setting bits, updates to a +block always consist of allocating a new block and copying the +modified data. The keystore code uses a trivial lock-step protocol +for this: first: + +1. The old block is marked as a "tombstone"; +2. The new block (with modified data) is written; +3. The old block is erased. + +This protocol is deliberately as simple as possible, so that there is +always a simple recovery path on reboot. + +Active blocks within a keystore are named by UUIDs. With one +exception, these are always type-4 (random) UUIDs, generated directly +from output of the TRNG. The one exception is the current PIN block, +which always uses the reserved all-zeros UUID, which cannot possibly +conflict with a type-4 UUID (by definition). + +The core of the keystore mechanism is the `ks->index[]` array, which +contains nothing but a list of block numbers. This array is divided +into two parts: the first part is the index of active blocks, which is +kept sorted (by UUID); the second part is the round-robin free list. +Everything else in the keystore is indexed by these block numbers, +which means that the index array is the only data structure which the +keystore code needs to sort or rotate when adding, removing, or +updating a block. Because the block numbers themselves are small +integers, the index array itself is small enough that shuffling data +within it using `memmove()` is a relatively cheap operation, which in +turn avoids a lot of complexity that would be involved in managing +more sophisticated data structures. + +The keystore code includes both caching of recently used keystore +blocks (to avoid unnecessary flash reads) and caching of the location +of the block corresponding to a particular UUID (to avoid unnecessary +index searches). Aside from whatever direct performance benefits this +might bring, this also frees the pkey layer that sits directly on top +of the keystore code from needing to keep a lot of active state on +particular keystore objects, which is important given that this whole +thing sits under an RPC protocol driven by a client program which can +impose arbitrary delays between any two operations at the pkey layer. + + ## Key backup ## The key backup mechanism is a straightforward three-step process, @@ -134,6 +222,7 @@ 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 @@ -149,6 +238,7 @@ The multiplexer requires two external Python libraries, Tornado 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 @@ -156,5 +246,6 @@ Yeah, we ought to document the API, Real Soon Now, perhaps using 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/ @@ -245,7 +245,7 @@ hal_error_t hal_core_alloc(const char *name, hal_core_t **pcore) hal_task_yield(); else break; - } + } return err; } 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 d5de227..d306eaf 100755 --- a/cryptech_muxd +++ b/cryptech_muxd @@ -58,6 +58,8 @@ import tornado.queues import tornado.locks import tornado.gen +from cryptech.libhal import HAL_OK, RPC_FUNC_GET_VERSION, RPC_FUNC_LOGOUT, RPC_FUNC_LOGOUT_ALL + logger = logging.getLogger("cryptech_muxd") @@ -89,6 +91,10 @@ def client_handle_set(msg, handle): return msg[:4] + struct.pack(">L", handle) + msg[8:] +logout_msg = struct.pack(">LL", RPC_FUNC_LOGOUT, 0) +logout_all_msg = struct.pack(">LL", RPC_FUNC_LOGOUT_ALL, 0) + + class SerialIOStream(tornado.iostream.BaseIOStream): """ Implementation of a Tornado IOStream over a PySerial device. @@ -157,10 +163,11 @@ class RPCIOStream(SerialIOStream): self.rpc_input_lock = tornado.locks.Lock() @tornado.gen.coroutine - def rpc_input(self, query, handle, queue): + def rpc_input(self, query, handle = 0, queue = None): "Send a query to the HSM." logger.debug("RPC send: %s", ":".join("{:02x}".format(ord(c)) for c in query)) - self.queues[handle] = queue + if queue is not None: + self.queues[handle] = queue with (yield self.rpc_input_lock.acquire()): yield self.write(query) logger.debug("RPC sent") @@ -182,13 +189,18 @@ class RPCIOStream(SerialIOStream): continue try: handle = client_handle_get(slip_decode(reply)) - queue = self.queues[handle] except: logger.debug("RPC skipping bad packet") continue - logger.debug("RPC queue put: handle 0x%x, qsize %s, maxsize %s", - handle, queue.qsize(), queue.maxsize) - queue.put_nowait(reply) + if handle not in self.queues: + logger.debug("RPC ignoring response: handle 0x%x", handle) + continue + logger.debug("RPC queue put: handle 0x%x, qsize %s", handle, self.queues[handle].qsize()) + self.queues[handle].put_nowait(reply) + + def logout_all(self): + "Execute an RPC LOGOUT_ALL operation." + return self.rpc_input(slip_encode(logout_all_msg)) class QueuedStreamClosedError(tornado.iostream.StreamClosedError): @@ -203,7 +215,7 @@ class RPCServer(PFUnixServer): @tornado.gen.coroutine def handle_stream(self, stream, address): "Handle one network connection." - handle = stream.socket.fileno() + handle = self.next_client_handle() queue = tornado.queues.Queue() logger.info("RPC connected %r, handle 0x%x", stream, handle) while True: @@ -223,8 +235,18 @@ class RPCServer(PFUnixServer): except tornado.iostream.StreamClosedError: logger.info("RPC closing %r, handle 0x%x", stream, handle) stream.close() + query = slip_encode(client_handle_set(logout_msg, handle)) + yield self.serial.rpc_input(query, handle) return + client_handle = int(time.time()) << 4 + + @classmethod + def next_client_handle(cls): + cls.client_handle += 1 + cls.client_handle &= 0xFFFFFFFF + return cls.client_handle + class CTYIOStream(SerialIOStream): """ @@ -331,8 +353,8 @@ class ProbeIOStream(SerialIOStream): @tornado.gen.coroutine def run_probe(self): - RPC_query = chr(0) * 8 # client_handle = 0, function code = RPC_FUNC_GET_VERSION - RPC_reply = chr(0) * 12 # opcode = RPC_FUNC_GET_VERSION, client_handle = 0, valret = HAL_OK + RPC_query = struct.pack(">LL", RPC_FUNC_GET_VERSION, 0) + RPC_reply = struct.pack(">LLL", RPC_FUNC_GET_VERSION, 0, HAL_OK) probe_string = SLIP_END + Control_U + SLIP_END + RPC_query + SLIP_END + Control_U + Control_M @@ -434,6 +456,7 @@ def main(): rpc_stream = RPCIOStream(device = args.rpc_device) rpc_server = RPCServer(rpc_stream, args.rpc_socket) futures.append(rpc_stream.rpc_output_loop()) + futures.append(rpc_stream.logout_all()) if args.cty_device is None: logger.warn("No CTY device found") @@ -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") \ diff --git a/hal_internal.h b/hal_internal.h index 3aadb48..2486fd2 100644 --- a/hal_internal.h +++ b/hal_internal.h @@ -397,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). * @@ -434,6 +432,12 @@ extern hal_error_t hal_mkm_flash_erase(const size_t len); #endif /* + * Clean up pkey stuff that's tied to a particular client on logout. + */ + +extern hal_error_t hal_pkey_logout(const hal_client_handle_t client); + +/* * Keystore API for use by the pkey implementation. * * In an attempt to emulate what current theory says will eventually @@ -451,9 +455,9 @@ extern hal_error_t hal_mkm_flash_erase(const size_t len); */ 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,377 +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); - -}; - - -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); -} - -/* - * 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 @@ -0,0 +1,938 @@ +/* + * 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->header.legacy_1, + sizeof(block->header.legacy_1)); + + crc = hal_crc32_update(crc, &block->header.legacy_2, + sizeof(block->header.legacy_2)); + + 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) +{ + if (ks == NULL || ks->driver == NULL) + return HAL_ERROR_BAD_ARGUMENTS; + + if (ks->driver->init == NULL) + return HAL_ERROR_NOT_IMPLEMENTED; + + hal_ks_lock(); + + const hal_error_t err = ks->driver->init(ks, alloc); + + hal_ks_unlock(); + + return err; +} + +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) + return err; + + else if ((block->header.legacy_1 != 0xFF || block->header.legacy_2 != 0xFF) && + (block->header.legacy_1 != 0x01 || block->header.legacy_2 != 0x00)) + block_types[i] = HAL_KS_BLOCK_TYPE_UNKNOWN; + + else + block_types[i] = hal_ks_block_get_type(block); + + + 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) +{ + if (ks == NULL || ks->driver == NULL) + return HAL_ERROR_BAD_ARGUMENTS; + + if (ks->driver->logout == NULL) + return HAL_ERROR_NOT_IMPLEMENTED; + + hal_ks_lock(); + + const hal_error_t err = ks->driver->logout(ks, client); + + hal_ks_unlock(); + + return err; +} + +/* + * 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,430 @@ +/* + * 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. + * + * The legacy_1 and legacy_2 fields were used in the more complex + * "chunked" layout used in an earlier iteration of this keystore + * design, which proved more complex than it was worth. At the + * moment, the only thing we do with these fields is include them in + * the CRC and check them for allowed values, to avoid gratuitously + * breaking backwards compatability with the earlier design. + */ + +typedef struct { + uint8_t block_type; + uint8_t block_status; + uint8_t legacy_1; + uint8_t legacy_2; + 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..38ca5d8 --- /dev/null +++ b/ks_token.c @@ -0,0 +1,666 @@ +/* + * 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; + + if (alloc && (err = hal_ks_alloc_common(ks, NUM_FLASH_BLOCKS, KS_TOKEN_CACHE_SIZE, NULL, 0)) != HAL_OK) + return err; + + if ((err = hal_ks_init_common(ks)) != HAL_OK) + return err; + + /* + * 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) { + /* + * 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) + return HAL_ERROR_IMPOSSIBLE; + + 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) + return err; + + hal_ks_cache_mark_used(ks, block, b); + + err = ks_token_write(ks, b, block); + + hal_ks_cache_release(ks, block); + } + + 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 d565c60..1586f3d 100644 --- a/ks_volatile.c +++ b/ks_volatile.c @@ -41,593 +41,265 @@ #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) +static hal_error_t ks_volatile_deprecate(hal_ks_t *ks, const unsigned blockno) { - 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) -{ - 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; + db->keys[blockno].block.header.block_status = HAL_KS_BLOCK_STATUS_TOMBSTONE; - 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. - */ - - 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 ((err = hal_ks_index_delete(ks, &ks->names[b], NULL, &hint)) != HAL_OK || + (err = hal_ks_block_zero(ks, b)) != HAL_OK) + return err; - 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 (!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; - - 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; - } - - hal_pkey_attribute_t attrs[k->attributes_len > 0 ? k->attributes_len : 1]; - - if ((err = hal_ks_attribute_scan(k->der + k->der_len, sizeof(k->der) - k->der_len, - attrs, k->attributes_len, NULL)) != HAL_OK) - goto done; - - uint8_t *abuf = attributes_buffer; + if (ks != hal_ks_volatile) + return HAL_ERROR_IMPOSSIBLE; - 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; + void *mem = NULL; + hal_error_t err; - if (attributes_buffer_len == 0) { - attributes[i].value = NULL; - attributes[i].length = found ? attrs[j].length : 0; - continue; - } + if (alloc && + (err = hal_ks_alloc_common(ks, STATIC_KS_VOLATILE_SLOTS, KS_VOLATILE_CACHE_SIZE, + &mem, sizeof(*db->keys) * STATIC_KS_VOLATILE_SLOTS)) != HAL_OK) + return err; - if (!found) { - err = HAL_ERROR_ATTRIBUTE_NOT_FOUND; - goto done; - } + if (alloc) + db->keys = mem; - if (attrs[j].length > attributes_buffer + attributes_buffer_len - abuf) { - err = HAL_ERROR_RESULT_TOO_LONG; - goto done; - } + if (db->keys == NULL) + return HAL_ERROR_IMPOSSIBLE; - memcpy(abuf, attrs[j].value, attrs[j].length); - attributes[i].value = abuf; - attributes[i].length = attrs[j].length; - abuf += attrs[j].length; - } + for (unsigned b = 0; b < db->ks.size; b++) + if ((err = hal_ks_block_erase(ks, b)) != HAL_OK) + return err; - err = HAL_OK; + if ((err = hal_ks_init_common(ks)) != HAL_OK) + return err; - } - - done: - hal_ks_unlock(); - return err; + return HAL_OK; } -const hal_ks_driver_t hal_ks_volatile_driver[1] = {{ +/* + * Dispatch vector and keystore definition, now that we've defined all + * the driver functions. + */ + +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 -}}; - -#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: @@ -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); } @@ -96,7 +95,7 @@ static uint32_t hal_pin_default_iterations = HAL_PIN_DEFAULT_ITERATIONS; #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 @@ -109,19 +108,51 @@ 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(void) +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(); #if HAL_STATIC_CLIENT_STATE_BLOCKS > 0 + + for (int i = 0; slot == NULL && i < sizeof(client_handle)/sizeof(*client_handle); i++) + if (client_handle[i].logged_in != HAL_USER_NONE && + client_handle[i].handle.handle == client.handle) + slot = &client_handle[i]; + for (int i = 0; slot == NULL && i < sizeof(client_handle)/sizeof(*client_handle); i++) if (client_handle[i].logged_in == HAL_USER_NONE) slot = &client_handle[i]; + #endif + if (slot != NULL) { + slot->handle = client; + slot->logged_in = user; + } + 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) +{ + if (slot == NULL) + return HAL_OK; + + hal_error_t err; + + if ((err = hal_pkey_logout(slot->handle)) != HAL_OK) + return err; + + hal_critical_section_start(); + + memset(slot, 0, sizeof(*slot)); + + hal_critical_section_end(); + + return HAL_OK; } static inline client_slot_t *find_handle(const hal_client_handle_t handle) @@ -143,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; @@ -168,21 +199,14 @@ static hal_error_t login(const hal_client_handle_t client, return HAL_ERROR_PIN_INCORRECT; } - client_slot_t *slot = find_handle(client); - - if (slot == NULL && (slot = alloc_slot()) == NULL) - return HAL_ERROR_NO_CLIENT_SLOTS_AVAILABLE; - - slot->handle = client; - slot->logged_in = user; - - 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); @@ -194,19 +218,32 @@ static hal_error_t is_logged_in(const hal_client_handle_t client, static hal_error_t logout(const hal_client_handle_t client) { - client_slot_t *slot = find_handle(client); - - if (slot != NULL) - slot->logged_in = HAL_USER_NONE; - - return HAL_OK; + return clear_slot(find_handle(client)); } static hal_error_t logout_all(void) { #if HAL_STATIC_CLIENT_STATE_BLOCKS > 0 - for (int i = 0; i < sizeof(client_handle)/sizeof(*client_handle); i++) - client_handle[i].logged_in = HAL_USER_NONE; + + client_slot_t *slot; + hal_error_t err; + int i = 0; + + do { + + hal_critical_section_start(); + + 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(); + + if ((err = clear_slot(slot)) != HAL_OK) + return err; + + } while (slot != NULL); + #endif return HAL_OK; @@ -216,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 @@ -129,6 +129,32 @@ static inline hal_pkey_slot_t *find_handle(const hal_pkey_handle_t handle) } /* + * Clean up key state associated with a client when logging out. + */ + +hal_error_t hal_pkey_logout(const hal_client_handle_t client) +{ + if (client.handle == HAL_HANDLE_NONE) + return HAL_OK; + + hal_error_t err; + + 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 == client.handle) + memset(&pkey_slot[i], 0, sizeof(pkey_slot[i])); + + hal_critical_section_end(); + + return HAL_OK; +} + +/* * Access rules are a bit complicated, mostly due to PKCS #11. * * The simple, obvious rule would be that one must be logged in as @@ -270,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); } @@ -336,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) @@ -351,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; + slot->client = client; + slot->session = 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); - - 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; } @@ -393,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: @@ -431,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) @@ -443,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) { @@ -458,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)); @@ -473,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; } @@ -495,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) @@ -507,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; @@ -521,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)); @@ -536,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; } @@ -568,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); @@ -1018,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, @@ -1032,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; @@ -1097,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; @@ -1108,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; @@ -1133,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, @@ -1159,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 a01572e..f64d7d6 100644 --- a/rpc_server.c +++ b/rpc_server.c @@ -993,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; @@ -1005,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; diff --git a/unit-tests.py b/unit-tests.py index 338af64..8b86d44 100644 --- a/unit-tests.py +++ b/unit-tests.py @@ -657,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): @@ -671,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") @@ -1016,6 +1035,10 @@ class AESKeyWrapWithPadding(object): step = -1 if start > stop else 1 return xrange(start, stop + step, step) + @staticmethod + def _xor(R0, t): + return pack(">Q", unpack(">Q", R0)[0] ^ t) + def wrap(self, Q): "RFC 5649 section 4.1." m = len(Q) # Plaintext length @@ -1032,9 +1055,7 @@ class AESKeyWrapWithPadding(object): for j in self._start_stop(0, 5): for i in self._start_stop(1, n): R[0], R[i] = self._encrypt(R[0], R[i]) - W0, W1 = unpack(">LL", R[0]) - W1 ^= n * j + i - R[0] = pack(">LL", W0, W1) + R[0] = self._xor(R[0], n * j + i) assert len(R) == (n + 1) and all(len(r) == 8 for r in R) return "".join(R) @@ -1051,9 +1072,7 @@ class AESKeyWrapWithPadding(object): # RFC 3394 section 2.2.2 steps (1), (2), and part of (3) for j in self._start_stop(5, 0): for i in self._start_stop(n, 1): - W0, W1 = unpack(">LL", R[0]) - W1 ^= n * j + i - R[0] = pack(">LL", W0, W1) + R[0] = self._xor(R[0], n * j + i) R[0], R[i] = self._decrypt(R[0], R[i]) magic, m = unpack(">LL", R[0]) if magic != 0xa65959a6: |