/* * rpc_pkey.c * ---------- * Remote procedure call server-side public key implementation. * * 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 #include #include "hal.h" #include "hal_internal.h" #include "asn1_internal.h" #ifndef HAL_STATIC_PKEY_STATE_BLOCKS #define HAL_STATIC_PKEY_STATE_BLOCKS 0 #endif #if HAL_STATIC_PKEY_STATE_BLOCKS > 0 static hal_pkey_slot_t pkey_slot[HAL_STATIC_PKEY_STATE_BLOCKS]; #endif /* * Handle allocation is simple: look for an unused (HAL_HANDLE_NONE) * slot in the table, and, assuming we find one, construct a composite * handle consisting of the index into the table and a counter whose * sole purpose is to keep the same handle from reoccurring anytime * soon, to help identify use-after-free bugs in calling code. * * The high order bit of the pkey handle is left free for * HAL_PKEY_HANDLE_TOKEN_FLAG, which is used by the mixed-mode * handlers to route calls to the appropriate destination. In most * cases this flag is set here, but pkey_local_open() also sets it * directly, so that we can present a unified UUID namespace * regardless of which keystore holds a particular key. */ static inline hal_pkey_slot_t *alloc_slot(const hal_key_flags_t flags) { hal_pkey_slot_t *slot = NULL; hal_critical_section_start(); #if HAL_STATIC_PKEY_STATE_BLOCKS > 0 static uint16_t next_glop = 0; uint32_t glop = ++next_glop << 16; next_glop %= 0x7FFF; assert((glop & HAL_PKEY_HANDLE_TOKEN_FLAG) == 0); if ((flags & HAL_KEY_FLAG_TOKEN) != 0) 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) continue; memset(&pkey_slot[i], 0, sizeof(pkey_slot[i])); pkey_slot[i].pkey_handle.handle = i | glop; pkey_slot[i].hint = -1; slot = &pkey_slot[i]; } #endif hal_critical_section_end(); return slot; } /* * Clear a slot. Probably not necessary to do this in a critical * section, but be safe. */ static inline void clear_slot(hal_pkey_slot_t *slot) { hal_critical_section_start(); if (slot != NULL) memset(slot, 0, sizeof(*slot)); hal_critical_section_end(); } /* * Check a caller-supplied handle. Must be in range, in use, and have * the right glop. Returns slot pointer on success, NULL otherwise. */ static inline hal_pkey_slot_t *find_handle(const hal_pkey_handle_t handle) { hal_pkey_slot_t *slot = NULL; hal_critical_section_start(); #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) slot = &pkey_slot[i]; #endif hal_critical_section_end(); return slot; } /* * Access rules are a bit complicated, mostly due to PKCS #11. * * The simple, obvious rule would be that one must be logged in as * HAL_USER_NORMAL to create, see, use, or delete a key, full stop. * * That's almost the rule that PKCS #11 follows for so-called * "private" objects (CKA_PRIVATE = CK_TRUE), but PKCS #11 has a more * complex model which not only allows wider visibility to "public" * objects (CKA_PRIVATE = CK_FALSE) but also allows write access to * "public session" (CKA_PRIVATE = CK_FALSE, CKA_TOKEN = CK_FALSE) * objects regardless of login state. * * PKCS #11 also has a concept of read-only sessions, which we don't * bother to implement at all on the HSM, since the PIN is required to * be the same as for the corresponding read-write session, so this * would just be additional compexity without adding any security on * the HSM; the PKCS #11 library still has to support read-only * sessions, but that's not our problem here. * * In general, non-PKCS #11 users of this API should probably never * set HAL_KEY_FLAG_PUBLIC, in which case they'll get the simple rule. * * Note that keystore drivers may need to implement additional * additional checks, eg, ks_volatile needs to enforce the rule that * session objects are only visible to the client which created them * (not the session, that would be too simple, thanks PKCS #11). In * practice, this should not be a serious problem, since such checks * will likely only apply to existing objects. The thing we really * want to avoid is doing all the work to create a large key only to * have the keystore driver reject access at the end, but since, by * definition, that only occurs when creating new objects, the access * decision doesn't depend on preexisting data, so the rules here * should suffice. That's the theory, anyway, if this is wrong we may * need to refactor. */ static inline hal_error_t check_normal_or_wheel(const hal_client_handle_t client) { const hal_error_t err = hal_rpc_is_logged_in(client, HAL_USER_NORMAL); return (err == HAL_ERROR_FORBIDDEN ? hal_rpc_is_logged_in(client, HAL_USER_WHEEL) : err); } static inline hal_error_t check_readable(const hal_client_handle_t client, const hal_key_flags_t flags) { if ((flags & HAL_KEY_FLAG_PUBLIC) != 0) return HAL_OK; return check_normal_or_wheel(client); } static inline hal_error_t check_writable(const hal_client_handle_t client, const hal_key_flags_t flags) { if ((flags & (HAL_KEY_FLAG_TOKEN | HAL_KEY_FLAG_PUBLIC)) == HAL_KEY_FLAG_PUBLIC) return HAL_OK; return check_normal_or_wheel(client); } /* * PKCS #1.5 encryption requires non-zero random bytes, which is a bit * messy if done in place, so make it a separate function for readability. */ static inline hal_error_t get_nonzero_random(uint8_t *buffer, size_t n) { assert(buffer != NULL); uint32_t word = 0; hal_error_t err; while (n > 0) { while ((word & 0xFF) == 0) if ((word & ~0xFF) != 0) word >>= 8; else if ((err = hal_get_random(NULL, &word, sizeof(word))) != HAL_OK) return err; *buffer++ = word & 0xFF; word >>= 8; n--; } return HAL_OK; } /* * Pad an octet string with PKCS #1.5 padding for use with RSA. * * This handles type 01 and type 02 encryption blocks. The formats * are identical, except that the padding string is constant 0xFF * bytes for type 01 and non-zero random bytes for type 02. * * We use memmove() instead of memcpy() so that the caller can * construct the data to be padded in the same buffer. */ static hal_error_t pkcs1_5_pad(const uint8_t * const data, const size_t data_len, uint8_t *block, const size_t block_len, const uint8_t type) { assert(data != NULL && block != NULL && (type == 0x01 || type == 0x02)); hal_error_t err; /* * Congregation will now please turn to RFC 2313 8.1 as we * construct a PKCS #1.5 type 01 or type 02 encryption block. */ if (data_len > block_len - 11) return HAL_ERROR_RESULT_TOO_LONG; memmove(block + block_len - data_len, data, data_len); block[0] = 0x00; block[1] = type; switch (type) { case 0x01: /* Signature */ memset(block + 2, 0xFF, block_len - 3 - data_len); break; case 0x02: /* Encryption */ if ((err = get_nonzero_random(block + 2, block_len - 3 - data_len)) != HAL_OK) return err; break; } block[block_len - data_len - 1] = 0x00; return HAL_OK; } /* * 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. */ 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) { 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; } /* * Same thing but from key flag in slot object rather than explict driver. */ 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); 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); } /* * Receive key from application, generate a name (UUID), store it, and * return a key handle and the name. */ static hal_error_t pkey_local_load(const hal_client_handle_t client, const hal_session_handle_t session, hal_pkey_handle_t *pkey, hal_uuid_t *name, const uint8_t * const der, const size_t der_len, const hal_key_flags_t flags) { assert(pkey != NULL && name != NULL && der != NULL); 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) return err; if ((err = hal_asn1_guess_key_type(&type, &curve, der, der_len)) != HAL_OK) return err; if ((slot = alloc_slot(flags)) == NULL) return HAL_ERROR_NO_KEY_SLOTS_AVAILABLE; if ((err = hal_uuid_gen(&slot->name)) != HAL_OK) return err; slot->client_handle = client; slot->session_handle = session; slot->type = type; slot->curve = curve; slot->flags = flags; if ((err = ks_open_from_flags(&ks, flags)) == HAL_OK && (err = hal_ks_store(ks, slot, der, der_len)) == HAL_OK) err = hal_ks_close(ks); else if (ks != NULL) (void) hal_ks_close(ks); if (err != HAL_OK) { slot->type = HAL_KEY_TYPE_NONE; return err; } *pkey = slot->pkey_handle; *name = slot->name; return HAL_OK; } /* * Look up a key given its name,
/*
 * xdr.c
 * -----
 * Serialization/deserialization routines, using XDR (RFC 4506) encoding.
 *
 * Copyright (c) 2016, NORDUnet A/S All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * - Neither the name of the NORDUnet nor the names of its contributors may
 *   be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdint.h>
#include <string.h>             /* memcpy, memset */

#include "hal.h"
#include "hal_internal.h"
#include "xdr_internal.h"

/* encode/decode_int. This covers int, unsigned int, enum, and bool types,
 * which are all encoded as 32-bit big-endian fields. Signed integers are
 * defined to use two's complement, but that's universal these days, yes?
 */

hal_error_t hal_xdr_encode_int(uint8_t ** const outbuf, const uint8_t * const limit, const uint32_t value)
{
    /* arg checks */
    if (outbuf == NULL || *outbuf == NULL || limit == NULL)
        return HAL_ERROR_BAD_ARGUMENTS;

    /* buffer overflow check */
    if (limit - *outbuf < sizeof(value))
        return HAL_ERROR_XDR_BUFFER_OVERFLOW;

    **(uint32_t **)outbuf = htonl(value);
    *outbuf += sizeof(value);
    return HAL_OK;
}

hal_error_t hal_xdr_decode_int(const uint8_t ** const inbuf, const uint8_t * const limit, uint32_t *value)
{
    /* arg checks */
    if (inbuf == NULL || *inbuf == NULL || limit == NULL || value == NULL)
        return HAL_ERROR_BAD_ARGUMENTS;

    /* buffer overflow check */
    if (limit - *inbuf < sizeof(*value))
        return HAL_ERROR_XDR_BUFFER_OVERFLOW;

    *value = ntohl(**(uint32_t **)inbuf);
    *inbuf += sizeof(*value);
    return HAL_OK;
}

/* Undo the last decode_int - roll back the input pointer.
 */
hal_error_t hal_xdr_undecode_int(const uint8_t ** const inbuf)
{
    if (inbuf == NULL || *inbuf == NULL)
        return HAL_ERROR_BAD_ARGUMENTS;

    *inbuf -= sizeof(uint32_t);
    return HAL_OK;
}

/* encode/decode_buffer. This covers variable-length string and opaque types.
 * The data is preceded by a 4-byte length word (encoded as above), and padded
 * to a multiple of 4 bytes as necessary.
 */

hal_error_t hal_xdr_encode_buffer(uint8_t **outbuf, const uint8_t * const limit, const uint8_t *value, const uint32_t len)
{
    hal_error_t ret;

    /* arg checks */
    if (outbuf == NULL || *outbuf == NULL || limit == NULL ||
        (value == NULL && len != 0))
        return HAL_ERROR_BAD_ARGUMENTS;

    /* buffer overflow check */
    if ((limit - *outbuf) < (((len + 3) & ~3) + sizeof(len)))
        return HAL_ERROR_XDR_BUFFER_OVERFLOW;

    /* encode length */
    if ((ret = hal_xdr_encode_int(outbuf, limit, len)) != HAL_OK)
        return ret;

    /* write the string or opaque data */
    memcpy(*outbuf, value, len);
    *outbuf += len;

    /* pad if necessary */
    if (len & 3) {
        size_t n = 4 - (len & 3);
        memset(*outbuf, 0, n);
        *outbuf += n;
    }

    return HAL_OK;
}

/* This version returns a pointer to the data in the input buffer.
 * It is used in the rpc server.
 */
hal_error_t hal_xdr_decode_buffer_in_place(const uint8_t **inbuf, const uint8_t * const limit, const uint8_t ** const value, uint32_t * const len)
{
    hal_error_t ret;
    uint32_t xdr_len;
    const uint8_t *orig_inbuf = *inbuf;

    /* arg checks */
    if (inbuf == NULL || *inbuf == NULL || limit == NULL || value == NULL || len == NULL)
        return HAL_ERROR_BAD_ARGUMENTS;

    /* decode the length */
    if ((ret = hal_xdr_decode_int(inbuf, limit, &xdr_len)) != HAL_OK)
        return ret;

    /* input and output buffer overflow checks vs decoded length */

    /* decoded length is past the end of the input buffer;
     * we're probably out of sync, but nothing we can do now
     */
    if (limit - *inbuf < xdr_len) {
        /* undo read of length */
        *inbuf = orig_inbuf;
        return HAL_ERROR_XDR_BUFFER_OVERFLOW;
    }

    /* return a pointer to the string or opaque data */
    *value = *inbuf;
    *len = xdr_len;

    /* update the buffer pointer, skipping any padding bytes */
    *inbuf += (xdr_len + 3) & ~3;

    return HAL_OK;
}

/* This version copies the data to the user-supplied buffer.
 * It is used in the rpc client.
 */
hal_error_t hal_xdr_decode_buffer(const uint8_t **inbuf, const uint8_t * const limit, uint8_t * const value, uint32_t * const len)
{
    if (inbuf == NULL || value == NULL || len == NULL)
        return HAL_ERROR_BAD_ARGUMENTS;

    hal_error_t ret;
    const uint8_t *vptr;
    const uint8_t *orig_inbuf = *inbuf;
    uint32_t xdr_len;

    if ((ret = hal_xdr_decode_buffer_in_place(inbuf, limit, &vptr, &xdr_len)) != HAL_OK)
        return ret;

    if (*len < xdr_len) {
        /* user buffer is too small, undo read of length */
        *inbuf = orig_inbuf;
	ret = HAL_ERROR_XDR_BUFFER_OVERFLOW;
    }
    else {
        memcpy(value, vptr, xdr_len);
    }

    *len = xdr_len;

    return ret;
}

/* ---------------------------------------------------------------- */

#ifdef TEST
static void hexdump(uint8_t *buf, uint32_t len)
{
    for (uint32_t i = 0; i < len; ++i)
        printf("%02x%c", buf[i], ((i & 0x07) == 0x07) ? '\n' : ' ');
    if ((len & 0x07) != 0)
        printf("\n");
}

int main(int argc, char *argv[])
{
    uint32_t i;
    uint8_t buf[64] = {0};
    uint8_t *bufptr = buf, *readptr;
    uint8_t *limit = buf + sizeof(buf);
    hal_error_t ret;
    uint8_t alphabet[] = "abcdefghijklmnopqrstuvwxyz";
    uint8_t readbuf[64] = {0};

    printf("hal_xdr_encode_int: work to failure\n");
    for (i = 1; i < 100; ++i) {
        if ((ret = hal_xdr_encode_int(&bufptr, limit, i)) != HAL_OK) {
            printf("%d: %s\n", i, hal_error_string(ret));
            break;
        }
    }
    hexdump(buf, ((uint8_t *)bufptr - buf));

    printf("\nhal_xdr_decode_int:\n");
    readptr = buf;
    while (readptr < bufptr) {
        if ((ret = hal_xdr_decode_int(&readptr, limit, &i)) != HAL_OK) {
            printf("%s\n", hal_error_string(ret));
            break;
        }
        printf("%u ", i);
    }
    printf("\n");

    printf("\nhal_xdr_encode_buffer: work to failure\n");
    memset(buf, 0, sizeof(buf));
    bufptr = buf;
     for (i = 1; i < 10; ++i) {
        if ((ret = hal_xdr_encode_buffer(&bufptr, limit, alphabet, i)) != HAL_OK) {
            printf("%d: %s\n", i, hal_error_string(ret));
            break;
        }
    }
    hexdump(buf, ((uint8_t *)bufptr - buf));

    printf("\nhal_xdr_decode_buffer:\n");
    readptr = buf;
    i = sizeof(readbuf);
    while (readptr < bufptr) {
        if ((ret = hal_xdr_decode_buffer(&readptr, limit, readbuf, &i)) != HAL_OK) {
            printf("%s\n", hal_error_string(ret));
            break;
        }
        printf("%u: ", i); for (int j = 0; j < i; ++j) putchar(readbuf[j]); putchar('\n');
        i = sizeof(readbuf);
        memset(readbuf, 0, sizeof(readbuf));
    }

    return 0;
}
#endif