/* * hal.h * ---------- * Memory map, access functions, and HAL for Cryptech cores. * * Authors: Joachim Strombergson, Paul Selkirk, Rob Austein * 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. */ #ifndef _HAL_H_ #define _HAL_H_ #include #include #include #include /* * A handy macro from cryptlib. */ #ifndef bitsToBytes #define bitsToBytes(x) ((x) / 8) #endif /* * Current name and version values for crypto cores. * * Should these even be here? Dunno. * Should the versions be here even if the names should be? */ #define NOVENA_BOARD_NAME "PVT1 " #define NOVENA_BOARD_VERSION "0.10" #define EIM_INTERFACE_NAME "eim " #define EIM_INTERFACE_VERSION "0.10" #define I2C_INTERFACE_NAME "i2c " #define I2C_INTERFACE_VERSION "0.10" #define TRNG_NAME "trng " #define TRNG_VERSION "0.51" #define AVALANCHE_ENTROPY_NAME "extnoise" #define AVALANCHE_ENTROPY_VERSION "0.10" #define ROSC_ENTROPY_NAME "rosc ent" #define ROSC_ENTROPY_VERSION "0.10" #define CSPRNG_NAME "csprng " #define CSPRNG_VERSION "0.50" #define SHA1_NAME "sha1 " #define SHA1_VERSION "0.50" #define SHA256_NAME "sha2-256" #define SHA256_VERSION "1.80" #define SHA512_NAME "sha2-512" #define SHA512_VERSION "0.80" #define AES_CORE_NAME "aes " #define AES_CORE_VERSION "0.80" #define CHACHA_NAME "chacha " #define CHACHA_VERSION "0.80" #define MODEXP_NAME "modexp" #define MODEXP_VERSION "0.10" #define MODEXPS6_NAME "modexps6" #define MODEXPS6_VERSION "0.10" #define MODEXPA7_NAME "modexpa7" #define MODEXPA7_VERSION "0.10" #define MKMIF_NAME "mkmif " #define MKMIF_VERSION "0.10" #define ECDSA256_NAME "ecdsa256" #define ECDSA256_VERSION "0.11" #define ECDSA256_NAME "ecdsa384" #define ECDSA256_VERSION "0.11" /* * C API error codes. Defined in this form so we can keep the tokens * and error strings together. See errorstrings.c. */ #define HAL_ERROR_LIST \ DEFINE_HAL_ERROR(HAL_OK, "No error") \ DEFINE_HAL_ERROR(HAL_ERROR_BAD_ARGUMENTS, "Bad arguments given") \ DEFINE_HAL_ERROR(HAL_ERROR_UNSUPPORTED_KEY, "Unsupported key type or key length") \ DEFINE_HAL_ERROR(HAL_ERROR_IO_SETUP_FAILED, "Could not set up I/O with FPGA") \ DEFINE_HAL_ERROR(HAL_ERROR_IO_TIMEOUT, "I/O with FPGA timed out") \ DEFINE_HAL_ERROR(HAL_ERROR_IO_UNEXPECTED, "Unexpected response from FPGA") \ DEFINE_HAL_ERROR(HAL_ERROR_IO_OS_ERROR, "Operating system error talking to FPGA") \ DEFINE_HAL_ERROR(HAL_ERROR_IO_BAD_COUNT, "Bad byte count") \ DEFINE_HAL_ERROR(HAL_ERROR_CSPRNG_BROKEN, "CSPRNG is returning nonsense") \ DEFINE_HAL_ERROR(HAL_ERROR_KEYWRAP_BAD_MAGIC, "Bad magic number while unwrapping key") \ DEFINE_HAL_ERROR(HAL_ERROR_KEYWRAP_BAD_LENGTH, "Length out of range while unwrapping key") \ DEFINE_HAL_ERROR(HAL_ERROR_KEYWRAP_BAD_PADDING, "Non-zero padding detected unwrapping key") \ DEFINE_HAL_ERROR(HAL_ERROR_IMPOSSIBLE, "\"Impossible\" error") \ DEFINE_HAL_ERROR(HAL_ERROR_ALLOCATION_FAILURE, "Memory allocation failed") \ DEFINE_HAL_ERROR(HAL_ERROR_RESULT_TOO_LONG, "Result too long for buffer") \ DEFINE_HAL_ERROR(HAL_ERROR_ASN1_PARSE_FAILED, "ASN.1 parse failed") \ DEFINE_HAL_ERROR(HAL_ERROR_KEY_NOT_ON_CURVE, "EC key is not on its purported curve") \ DEFINE_HAL_ERROR(HAL_ERROR_INVALID_SIGNATURE, "Invalid signature") \ DEFINE_HAL_ERROR(HAL_ERROR_CORE_NOT_FOUND, "Requested core not found") \ DEFINE_HAL_ERROR(HAL_ERROR_CORE_BUSY, "Requested core busy") \ DEFINE_HAL_ERROR(HAL_ERROR_KEYSTORE_ACCESS, "Could not access keystore") \ DEFINE_HAL_ERROR(HAL_ERROR_KEY_NOT_FOUND, "Key not found") \ DEFINE_HAL_ERROR(HAL_ERROR_KEY_NAME_IN_USE, "Key name in use") \ DEFINE_HAL_ERROR(HAL_ERROR_NO_KEY_SLOTS_AVAILABLE, "No key slots available") \ DEFINE_HAL_ERROR(HAL_ERROR_PIN_INCORRECT, "PIN incorrect") \ DEFINE_HAL_ERROR(HAL_ERROR_NO_CLIENT_SLOTS_AVAILABLE, "No client slots available") \ DEFINE_HAL_ERROR(HAL_ERROR_FORBIDDEN, "Forbidden") \ DEFINE_HAL_ERROR(HAL_ERROR_XDR_BUFFER_OVERFLOW, "XDR buffer overflow") \ DEFINE_HAL_ERROR(HAL_ERROR_RPC_TRANSPORT, "RPC transport error") \ DEFINE_HAL_ERROR(HAL_ERROR_RPC_PACKET_OVERFLOW, "RPC packet overflow") \ DEFINE_HAL_ERROR(HAL_ERROR_RPC_BAD_FUNCTION, "Bad RPC function number") \ DEFINE_HAL_ERROR(HAL_ERROR_KEY_NAME_TOO_LONG, "Key name too long") \ DEFINE_HAL_ERROR(HAL_ERROR_MASTERKEY_NOT_SET, "Master key (Key Encryption Key) not set") \ DEFINE_HAL_ERROR(HAL_ERROR_MASTERKEY_FAIL, "Master key generic failure") \ DEFINE_HAL_ERROR(HAL_ERROR_MASTERKEY_BAD_LENGTH, "Master key of unacceptable length") \ DEFINE_HAL_ERROR(HAL_ERROR_KS_DRIVER_NOT_FOUND, "Keystore driver not found") \ DEFINE_HAL_ERROR(HAL_ERROR_KEYSTORE_BAD_CRC, "Bad CRC in keystore") \ DEFINE_HAL_ERROR(HAL_ERROR_KEYSTORE_BAD_BLOCK_TYPE, "Unsupported keystore block type") \ DEFINE_HAL_ERROR(HAL_ERROR_KEYSTORE_LOST_DATA, "Keystore appears to have lost data") \ 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_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") \ END_OF_HAL_ERROR_LIST /* Marker to forestall silly line continuation errors */ #define END_OF_HAL_ERROR_LIST /* Define the error code enum here. See errorstrings.c for the text strings. */ #define DEFINE_HAL_ERROR(_code_,_text_) _code_, typedef enum { HAL_ERROR_LIST N_HAL_ERRORS } hal_error_t; #undef DEFINE_HAL_ERROR /* * Error translation. */ extern const char *hal_error_string(const hal_error_t err); /* * Very low level public API for working directly with crypto cores. */ /* * Typedef to isolate code from our current choice of representation * for a Cryptech bus address. */ typedef off_t hal_addr_t; /* * Opaque structure representing a core. */ typedef struct hal_core hal_core_t; /* * Public I/O functions. */ extern void hal_io_set_debug(int onoff); extern hal_error_t hal_io_write(const hal_core_t *core, hal_addr_t offset, const uint8_t *buf, size_t len); extern hal_error_t hal_io_read(const hal_core_t *core, hal_addr_t offset, uint8_t *buf, size_t len); extern hal_error_t hal_io_init(const hal_core_t *core); extern hal_error_t hal_io_next(const hal_core_t *core); extern hal_error_t hal_io_wait(const hal_core_t *core, uint8_t status, int *count); extern hal_error_t hal_io_wait_ready(const hal_core_t *core); extern hal_error_t hal_io_wait_valid(const hal_core_t *core); /* * Core management functions. * * Given our druthers, we'd handle public information about a core * using the opaque type and individual access methods, but C's * insistence on discarding array bounds information makes * non-delimited character arrays problematic unless we wrap them in a * structure. */ typedef struct { char name[8]; char version[4]; hal_addr_t base; } hal_core_info_t; extern hal_core_t *hal_core_find(const char *name, hal_core_t *core); extern const hal_core_info_t *hal_core_info(const hal_core_t *core); extern hal_addr_t hal_core_base(const hal_core_t *core); extern hal_core_t * hal_core_iterate(hal_core_t *core); extern void hal_core_reset_table(void); extern hal_error_t hal_core_alloc(const char *name, hal_core_t **core); extern void hal_core_free(hal_core_t *core); extern void hal_critical_section_start(void); extern void hal_critical_section_end(void); extern const int hal_core_busy(const hal_core_t *core); /* * Slightly higher level public API, still working directly with cores. */ /* * Get random bytes from the CSPRNG. */ extern hal_error_t hal_get_random(hal_core_t *core, void *buffer, const size_t length); /* * Hash and HMAC API. */ /* * Opaque driver structure for digest algorithms. */ typedef struct hal_hash_driver hal_hash_driver_t; /* * Public information about a digest algorithm. * * The _state_length values in the descriptor and the typed opaque * pointers in the API are all intended to hide internal details of * the implementation while making memory allocation the caller's * problem. */ typedef enum { HAL_DIGEST_ALGORITHM_NONE, HAL_DIGEST_ALGORITHM_SHA1, HAL_DIGEST_ALGORITHM_SHA224, HAL_DIGEST_ALGORITHM_SHA256, HAL_DIGEST_ALGORITHM_SHA512_224, HAL_DIGEST_ALGORITHM_SHA512_256, HAL_DIGEST_ALGORITHM_SHA384, HAL_DIGEST_ALGORITHM_SHA512 } hal_digest_algorithm_t; typedef struct { hal_digest_algorithm_t digest_algorithm; size_t block_length; size_t digest_length; size_t hash_state_length; size_t hmac_state_length; const uint8_t * const digest_algorithm_id; size_t digest_algorithm_id_length; const hal_hash_driver_t *driver; char core_name[8]; unsigned can_restore_state : 1; } hal_hash_descriptor_t; /* * Opaque structures for internal state. */ typedef struct hal_hash_state hal_hash_state_t; typedef struct hal_hmac_state hal_hmac_state_t; /* * Supported digest algorithms. These are one-element arrays so that * they can be used as constant pointers. */ extern const hal_hash_descriptor_t hal_hash_sha1[1]; extern const hal_hash_descriptor_t hal_hash_sha224[1]; extern const hal_hash_descriptor_t hal_hash_sha256[1]; extern const hal_hash_descriptor_t hal_hash_sha512_224[1]; extern const hal_hash_descriptor_t hal_hash_sha512_256[1]; extern const hal_hash_descriptor_t hal_hash_sha384[1]; extern const hal_hash_descriptor_t hal_hash_sha512[1]; /* * Hash and HMAC functions. */ extern void hal_hash_set_debug(int onoff); extern hal_error_t hal_hash_initialize(hal_core_t *core, const hal_hash_descriptor_t * const descriptor, hal_hash_state_t **state, void *state_buffer, const size_t state_length); extern hal_error_t hal_hash_update(hal_hash_state_t *state, const uint8_t * data, const size_t length); extern hal_error_t hal_hash_finalize(hal_hash_state_t *state, uint8_t *digest, const size_t length); extern hal_error_t hal_hmac_initialize(hal_core_t *core, const hal_hash_descriptor_t * const descriptor, hal_hmac_state_t **state, void *state_buffer, const size_t state_length, const uint8_t * const key, const size_t key_length); extern hal_error_t hal_hmac_update(hal_hmac_state_t *state, const uint8_t * data, const size_t length); extern hal_error_t hal_hmac_finalize(hal_hmac_state_t *state, uint8_t *hmac, const size_t length); extern void hal_hash_cleanup(hal_hash_state_t **state); extern void hal_hmac_cleanup(hal_hmac_state_t **state); extern const hal_hash_descriptor_t *hal_hash_get_descriptor(const hal_hash_state_t * const state); extern const hal_hash_descriptor_t *hal_hmac_get_descriptor(const hal_hmac_state_t * const state); /* * AES key wrap functions. */ extern hal_error_t hal_aes_keywrap(hal_core_t *core, const uint8_t *kek, const size_t kek_length, const uint8_t *plaintext, const size_t plaintext_length, uint8_t *cyphertext, size_t *ciphertext_length); extern hal_error_t hal_aes_keyunwrap(hal_core_t *core, const uint8_t *kek, const size_t kek_length, const uint8_t *ciphertext, const size_t ciphertext_length, unsigned char *plaintext, size_t *plaintext_length); extern size_t hal_aes_keywrap_ciphertext_length(const size_t plaintext_length); /* * PBKDF2 function. Uses HMAC with the specified digest algorithm as * the pseudo-random function (PRF). */ extern hal_error_t hal_pbkdf2(hal_core_t *core, const hal_hash_descriptor_t * const descriptor, const uint8_t * const password, const size_t password_length, const uint8_t * const salt, const size_t salt_length, uint8_t * derived_key, const size_t derived_key_length, unsigned iterations_desired); /* * Modular exponentiation. */ extern void hal_modexp_set_debug(const int onoff); extern hal_error_t hal_modexp(hal_core_t *core, const uint8_t * const msg, const size_t msg_len, /* Message */ const uint8_t * const exp, const size_t exp_len, /* Exponent */ const uint8_t * const mod, const size_t mod_len, /* Modulus */ uint8_t * result, const size_t result_len); /* * Master Key Memory Interface */ extern hal_error_t hal_mkmif_init(hal_core_t *core); extern hal_error_t hal_mkmif_set_clockspeed(hal_core_t *core, const uint32_t divisor); extern hal_error_t hal_mkmif_get_clockspeed(hal_core_t *core, uint32_t *divisor); extern hal_error_t hal_mkmif_write(hal_core_t *core, uint32_t addr, const uint8_t *buf, size_t len); extern hal_error_t hal_mkmif_write_word(hal_core_t *core, uint32_t addr, const uint32_t data); extern hal_error_t hal_mkmif_read(hal_core_t *core, uint32_t addr, uint8_t *buf, size_t len); extern hal_error_t hal_mkmif_read_word(hal_core_t *core, uint32_t addr, uint32_t *data); /* * Key types and curves, used in various places. */ typedef enum { HAL_KEY_TYPE_NONE = 0, HAL_KEY_TYPE_RSA_PRIVATE, HAL_KEY_TYPE_RSA_PUBLIC, HAL_KEY_TYPE_EC_PRIVATE, HAL_KEY_TYPE_EC_PUBLIC } hal_key_type_t; typedef enum { HAL_CURVE_NONE, HAL_CURVE_P256, HAL_CURVE_P384, HAL_CURVE_P521 } hal_curve_name_t; /* * RSA. */ typedef struct hal_rsa_key hal_rsa_key_t; extern const size_t hal_rsa_key_t_size; extern void hal_rsa_set_debug(const int onoff); extern void hal_rsa_set_blinding(const int onoff); extern hal_error_t hal_rsa_key_load_private(hal_rsa_key_t **key, void *keybuf, const size_t keybuf_len, const uint8_t * const n, const size_t n_len, const uint8_t * const e, const size_t e_len, const uint8_t * const d, const size_t d_len, const uint8_t * const p, const size_t p_len,
#!/usr/bin/env python

"""
Securely back up private keys from one Cryptech HSM to another.

This works by having the destination HSM (the one importing keys)
create an RSA keypair (the "KEKEK"), the public key of which can then
be imported into the source HSM (the one exporting keys) and used to
encrypt AES key encryption keys (KEKs) which in turn can be used to
wrap the private keys being transfered.  Transfers are encoded in
JSON; the underlying ASN.1 formats are SubjectPublicKeyInfo (KEKEK
public key) and PKCS #8 EncryptedPrivateKeyInfo (everything else).

NOTE WELL: while this process makes it POSSIBLE to back up keys
securely, it is not sufficient by itself: the operator MUST make
sure only to export keys using a KEKEK known to have been generated by
the target HSM.  See the unit tests in the source repository for
an example of how to fake this in a few lines of Python.

We also implement a software-based variant on this backup mechanism,
for cases where there is no second HSM.  The protocol is much the
same, but the KEKEK is generated in software and encrypted using a
symmetric key derived from a passphrase using PBKDF2.  This requires
the PyCrypto library, and is only as secure as memory on the machine
where you're running it (so it's theoretically vulnerable to root or
anybody with access to /dev/mem).  Don't use this mode unless you
understand the risks, and see the "NOTE WELL" above.

YOU HAVE BEEN WARNED.  Be careful out there.
"""

# Diagram of the trivial protocol we're using:
#
#    SOURCE HSM                            DESTINATION HSM
#
#                                          Generate and export KEKEK:
#                                               hal_rpc_pkey_generate_rsa()
#                                               hal_rpc_pkey_get_public_key()
#
#   Load KEKEK public        <---------    Export KEKEK public
#        hal_rpc_pkey_load()
#        hal_rpc_pkey_export()
#
#   Export PKCS #8 and KEK   ---------->   Load PKCS #8 and KEK, import key
#                                               hal_rpc_pkey_import()

import sys
import json
import uuid
import atexit
import getpass
import argparse

from cryptech.libhal import *

def main():

    parser = argparse.ArgumentParser(
        formatter_class = argparse.RawDescriptionHelpFormatter,
        description = __doc__)
    subparsers = parser.add_subparsers(
        title = "Commands (use \"--help\" after command name for help with individual commands)",
        metavar = "")
    setup_parser  = defcmd(subparsers, cmd_setup)
    export_parser = defcmd(subparsers, cmd_export)
    import_parser = defcmd(subparsers, cmd_import)
    setup_mutex_group = setup_parser.add_mutually_exclusive_group()


    parser.add_argument(
        "-p", "--pin",
        help    = "wheel PIN")


    setup_mutex_group.add_argument(
        "-n", "--new",
        action  = "store_true",
        help    = "force creation of new KEKEK")

    setup_mutex_group.add_argument(
        "-u", "--uuid",
        help    = "UUID of existing KEKEK to use")

    setup_mutex_group.add_argument(
        "-s", "--soft-backup",
        action = "store_true",
        help   = "software-based backup, see warnings")

    setup_parser.add_argument(
        "-k", "--keylen",
        type    = int,
        default = 2048,
        help    = "length of new KEKEK if we need to create one")

    setup_parser.add_argument(
        "-o", "--output",
        type    = argparse.FileType("w"),
        default = "-",
        help    = "output file")


    export_parser.add_argument(
        "-i", "--input",
        type    = argparse.FileType("r"),
        default = "-",
        help    = "input file")

    export_parser.add_argument(
        "-o", "--output",
        type    = argparse.FileType("w"),
        default = "-",
        help    = "output file")


    import_parser.add_argument(
        "-i", "--input",
        type    = argparse.FileType("r"),
        default = "-",
        help    = "input file")


    args = parser.parse_args()

    hsm = HSM()

    try:
        hsm.login(HAL_USER_WHEEL, args.pin or getpass.getpass("Wheel PIN: "))

    except HALError as e:
        sys.exit("Couldn't log into HSM: {}".format(e))

    try:
        sys.exit(args.func(args, hsm))

    finally:
        hsm.logout()


def defcmd(subparsers, func):
    assert func.__name__.startswith("cmd_")
    subparser = subparsers.add_parser(func.__name__[4:],
                                      description = func.__doc__,
                                      help = func.__doc__.strip().splitlines()[0])
    subparser.set_defaults(func = func)
    return subparser


def b64(bytes):
    return bytes.encode("base64").splitlines()

def b64join(lines):
    return "".join(lines).decode("base64")


def cmd_setup(args, hsm):
    """
    Set up backup HSM for subsequent import.
    Generates an RSA keypair with appropriate usage settings
    to use as a key-encryption-key-encryption-key (KEKEK), and
    writes the KEKEK to a JSON file for transfer to primary HSM.
    """

    result = {}
    uuids  = []

    if args.soft_backup:
        SoftKEKEK.generate(args, result)
    elif args.uuid:
        uuids.append(args.uuid)
    elif not args.new:
        uuids.extend(hsm.pkey_match(
            type  = HAL_KEY_TYPE_RSA_PRIVATE,
            mask  = HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT | HAL_KEY_FLAG_TOKEN,
            flags = HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT | HAL_KEY_FLAG_TOKEN))

    for uuid in uuids:
        with hsm.pkey_open(uuid) as kekek:
            if kekek.key_type != HAL_KEY_TYPE_RSA_PRIVATE:
                sys.stderr.write("Key {} is not an RSA private key\n".format(uuid))
            elif (kekek.key_flags & HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT) == 0:
                sys.stderr.write("Key {} does not allow key encipherment\n".format(uuid))
            else:
                result.update(kekek_uuid   = str(kekek.uuid),
                              kekek_pubkey = b64(kekek.public_key))
                break

    if not result and not args.uuid:
        with hsm.pkey_generate_rsa(
                keylen = args.keylen,
                flags = HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT | HAL_KEY_FLAG_TOKEN) as kekek:
            result.update(kekek_uuid   = str(kekek.uuid),
                          kekek_pubkey = b64(kekek.public_key))
    if not result:
        sys.exit("Could not find suitable KEKEK")

    if args.soft_backup:
        result.update(comment = "KEKEK software keypair")
    else:
        result.update(comment = "KEKEK public key")

    json.dump(result, args.output, indent = 4, sort_keys = True)
    args.output.write("\n")


def key_flag_names(flags):
    names = dict(digitalsignature = HAL_KEY_FLAG_USAGE_DIGITALSIGNATURE,
                 keyencipherment  = HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT,
                 dataencipherment = HAL_KEY_FLAG_USAGE_DATAENCIPHERMENT,
                 token            = HAL_KEY_FLAG_TOKEN,
                 public           = HAL_KEY_FLAG_PUBLIC,
                 exportable       = HAL_KEY_FLAG_EXPORTABLE)
    return ", ".join(sorted(k for k, v in names.iteritems() if (flags & v) != 0))


def cmd_export(args, hsm):
    """
    Export encrypted keys from primary HSM.
    Takes a JSON file containing KEKEK (generated by running this
    script's "setup" command against the backup HSM), installs that
    key on the primary HSM, and backs up keys encrypted to the KEKEK
    by writing them to another JSON file for transfer to the backup HSM.
    """

    db = json.load(args.input)

    result = []

    kekek = None
    try:
        kekek = hsm.pkey_load(der   = b64join(db["kekek_pubkey"]),
                              flags = HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT)

        for uuid in hsm.pkey_match(mask  = HAL_KEY_FLAG_EXPORTABLE,
                                   flags = HAL_KEY_FLAG_EXPORTABLE):
            with hsm.pkey_open(uuid) as pkey:

                if pkey.key_type in (HAL_KEY_TYPE_RSA_PRIVATE, HAL_KEY_TYPE_EC_PRIVATE):
                    pkcs8, kek = kekek.export_pkey(pkey)
                    result.append(dict(
                        comment = "Encrypted private key",
                        pkcs8   = b64(pkcs8),
                        kek     = b64(kek),
                        uuid    = str(pkey.uuid),
                        flags   = pkey.key_flags))

                elif pkey.key_type in (HAL_KEY_TYPE_RSA_PUBLIC, HAL_KEY_TYPE_EC_PUBLIC):
                    result.append(dict(
                        comment = "Public key",
                        spki    = b64(pkey.public_key),
                        uuid    = str(pkey.uuid),
                        flags   = pkey.key_flags))

    finally:
        if kekek is not None:
            kekek.delete()

    db.update(comment = "Cryptech Alpha encrypted key backup",
              keys    = result)
    json.dump(db, args.output, indent = 4, sort_keys = True)
    args.output.write("\n")


def cmd_import(args, hsm):
    """
    Import encrypted keys into backup HSM.
    Takes a JSON file containing a key backup (generated by running
    this script's "export" command against the primary HSM) and imports
    keys into the backup HSM.
    """

    db = json.load(args.input)

    soft_key = SoftKEKEK.is_soft_key(db)

    with (hsm.pkey_load(SoftKEKEK.recover(db), HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT)
          if soft_key else
          hsm.pkey_open(uuid.UUID(db["kekek_uuid"]).bytes)
    ) as kekek:

        for k in db["keys"]:
            pkcs8 = b64join(k.get("pkcs8", ""))
            spki  = b64join(k.get("spki",  ""))
            kek   = b64join(k.get("kek",   ""))
            flags =         k.get("flags",  0)
            if pkcs8 and kek:
                with kekek.import_pkey(pkcs8 = pkcs8, kek = kek, flags = flags) as pkey:
                    print "Imported {} as {}".format(k["uuid"], pkey.uuid)
            elif spki:
                with hsm.pkey_load(der = spki, flags = flags) as pkey:
                    print "Loaded {} as {}".format(k["uuid"], pkey.uuid)

        if soft_key:
            kekek.delete()


class AESKeyWrapWithPadding(object):
    """
    Implementation of AES Key Wrap With Padding from RFC 5649.
    """

    class UnwrapError(Exception):
        "Something went wrong during unwrap."

    def __init__(self, key):
        from Crypto.Cipher import AES
        self.ctx = AES.new(key, AES.MODE_ECB)

    def _encrypt(self, b1, b2):
        aes_block = self.ctx.encrypt(b1 + b2)
        return aes_block[:8], aes_block[8:]

    def _decrypt(self, b1, b2):
        aes_block = self.ctx.decrypt(b1 + b2)
        return aes_block[:8], aes_block[8:]

    @staticmethod
    def _start_stop(start, stop):               # Syntactic sugar
        step = -1 if start > stop else 1
        return xrange(start, stop + step, step)

    @staticmethod
    def _xor(R0, t):
        from struct import pack, unpack
        return pack(">Q", unpack(">Q", R0)[0] ^ t)

    def wrap(self, Q):
        "RFC 5649 section 4.1."
        from struct import pack
        m = len(Q)                              # Plaintext length
        if m % 8 != 0:                          # Pad Q if needed
            Q += "\x00" * (8 - (m % 8))
        R = [pack(">LL", 0xa65959a6, m)]        # Magic MSB(32,A), build LSB(32,A)
        R.extend(Q[i : i + 8]                   # Append Q
                 for i in xrange(0, len(Q), 8))
        n = len(R) - 1
        if n == 1:
            R[0], R[1] = self._encrypt(R[0], R[1])
        else:
            # RFC 3394 section 2.2.1
            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])
                    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)

    def unwrap(self, C):
        "RFC 5649 section 4.2."
        from struct import unpack
        if len(C) % 8 != 0:
            raise self.UnwrapError("Ciphertext length {} is not an integral number of blocks"
                                   .format(len(C)))
        n = (len(C) / 8) - 1
        R = [C[i : i + 8] for i in xrange(0, len(C), 8)]
        if n == 1:
            R[0], R[1] = self._decrypt(R[0], R[1])
        else:
            # 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):
                    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:
            raise self.UnwrapError("Magic value in AIV should have been 0xa65959a6, was 0x{:02x}"
                              .format(magic))
        if m <= 8 * (n - 1) or m > 8 * n:
            raise self.UnwrapError("Length encoded in AIV out of range: m {}, n {}".format(m, n))
        R = "".join(R[1:])
        assert len(R) ==  8 * n
        if any(r != "\x00" for r in R[m:]):
            raise self.UnwrapError("Nonzero trailing bytes {}".format(R[m:].encode("hex")))
        return R[:m]


class SoftKEKEK(object):
    """
    Wrapper around all the goo we need to implement soft backups.
    Requires PyCrypto on about every other line.
    """

    oid_aesKeyWrap = "\x60\x86\x48\x01\x65\x03\x04\x01\x30"

    def parse_EncryptedPrivateKeyInfo(self, der):
        from Crypto.Util.asn1 import DerObject, DerSequence, DerOctetString, DerObjectId
        encryptedPrivateKeyInfo = DerSequence()
        encryptedPrivateKeyInfo.decode(der)
        encryptionAlgorithm = DerSequence()
        algorithm = DerObjectId()
        encryptedData = DerOctetString()
        encryptionAlgorithm.decode(encryptedPrivateKeyInfo[0])
        DerObject.decode(algorithm, encryptionAlgorithm[0])
        DerObject.decode(encryptedData, encryptedPrivateKeyInfo[1])
        if algorithm.payload != self.oid_aesKeyWrap:
            raise ValueError
        return encryptedData.payload

    def encode_EncryptedPrivateKeyInfo(self, der):
        from Crypto.Util.asn1 import DerSequence, DerOctetString
        return DerSequence([
            DerSequence([
                chr(0x06) + chr(len(self.oid_aesKeyWrap)) + self.oid_aesKeyWrap
            ]).encode(),
            DerOctetString(der).encode()
        ]).encode()

    def gen_salt(self, bytes = 16):
        from Crypto import Random
        return Random.new().read(bytes)

    def wrapper(self, salt, keylen = 256, iterations = 8000):
        from Crypto.Protocol.KDF import PBKDF2
        from Crypto.Hash         import SHA256, HMAC
        return AESKeyWrapWithPadding(PBKDF2(
            password = getpass.getpass("KEKEK Passphrase: "),
            salt     = salt,
            dkLen    = keylen/8,
            count    = iterations,
            prf      = lambda p, s: HMAC.new(p, s, SHA256).digest()))

    @classmethod
    def is_soft_key(cls, db):
        return all(k in db for k in ("kekek_pkcs8", "kekek_salt"))

    @classmethod
    def generate(cls, args, result):
        from Crypto.PublicKey import RSA
        self = cls()
        k = RSA.generate(args.keylen)
        salt  = self.gen_salt()
        spki  = k.publickey().exportKey(format = "DER")
        pkcs8 = self.encode_EncryptedPrivateKeyInfo(self.wrapper(salt).wrap(
            k.exportKey(format = "DER", pkcs = 8)))
        result.update(kekek_salt   = b64(salt),
                      kekek_pkcs8  = b64(pkcs8),
                      kekek_pubkey = b64(spki))

    @classmethod
    def recover(cls, db):
        self = cls()
        return self.wrapper(b64join(db["kekek_salt"])).unwrap(
            self.parse_EncryptedPrivateKeyInfo(b64join(db["kekek_pkcs8"])))


if __name__ == "__main__":
    main()