diff --git a/CMakeLists.txt b/CMakeLists.txt index cc05025..de79df5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,3 +15,4 @@ add_subdirectory(raspinfo) add_subdirectory(vcgencmd) add_subdirectory(vclog) add_subdirectory(vcmailbox) +add_subdirectory(rpifwcrypto) diff --git a/README.md b/README.md index 8d62e8d..ebb3385 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,16 @@ A collection of scripts and simple applications * [piolib](piolib/) - A library for accessing the Pi 5's PIO hardware. * [raspinfo](raspinfo/) - A short script to dump information about the Pi. Intended for the submission of bug reports. +* [rpifwcrypto](rpifwcrypto/) - A command line application and shared library for the + firmware cryptography service. Intended for use with Raspberry Pi Connect and + secure-boot provisioner. * [vclog](vclog/) - A tool to get VideoCore 'assert' or 'msg' logs with optional -f to wait for new logs to arrive. **Build Instructions** -Install the prerequisites with "sudo apt install cmake device-tree-compiler libfdt-dev" - you need at least version 3.10 of cmake. Run the following commands to build and install everything, or see the README files in the subdirectories to just build utilities individually: +Install the prerequisites with "sudo apt install cmake device-tree-compiler libfdt-dev libgnutls28-dev" - you need at least version 3.10 of cmake. Run the following commands to build and install everything, or see the README files in the subdirectories to just build utilities individually: - *cmake .* N.B. Use *cmake -DBUILD_SHARED_LIBS=1 .* to build the libraries in the subprojects (libdtovl, gpiolib and piolib) as shared (as opposed to static) libraries. diff --git a/rpifwcrypto/CMakeLists.txt b/rpifwcrypto/CMakeLists.txt new file mode 100644 index 0000000..f23b397 --- /dev/null +++ b/rpifwcrypto/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.10...3.27) +include(GNUInstallDirs) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror") + +# Set project name +project(rpifwcrypto) + +# Find GnuTLS package +find_package(GnuTLS REQUIRED) + +option(BUILD_SHARED_LIBS "Build using shared libraries" ON) + +# Create the shared library +add_library(rpifwcrypto rpifwcrypto.c) +target_sources(rpifwcrypto PUBLIC rpifwcrypto.h) +set_target_properties(rpifwcrypto PROPERTIES PUBLIC_HEADER rpifwcrypto.h) +set_target_properties(rpifwcrypto PROPERTIES SOVERSION 0) + +# Create the executable +add_executable(rpi-fw-crypto main.c) +target_link_libraries(rpi-fw-crypto rpifwcrypto ${GNUTLS_LIBRARIES}) +target_include_directories(rpi-fw-crypto PRIVATE ${GNUTLS_INCLUDE_DIRS}) + +# Install rules +install(TARGETS rpi-fw-crypto RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(TARGETS rpifwcrypto + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/rpifwcrypto/README.md b/rpifwcrypto/README.md new file mode 100644 index 0000000..8e7fdc3 --- /dev/null +++ b/rpifwcrypto/README.md @@ -0,0 +1,43 @@ + +# rpifwcrypto + +The Raspberry Pi Firmware Cryptography service is a mailbox based API +that allows a limited set of cryptographic operations to be performed +by the firmware without exposing private keys to userspace. + +The initial implementation is designed to support PiConnect and +provides an ECDSA P-256 SHA256 signature API. + +A SHA256 HMAC API is provided to provide basic support for derived keys +instead of using the raw device unique private key +e.g. HMAC(serial-number + EMMC CID) could be used for a LUKS passphrase. + +Although this service can be used via raw vcmailbox commands the +recommended API is either the command line rpi-fw-crypto application +or the librpifwcrypto.so shared library. + +**Build Instructions** +Install prerequisites with "sudo apt install cmake libgnutls28-dev"" - you need at least version 3.10. + + - *mkdir build* + - *cd build* + - *cmake ..* + - *make* + - *sudo make install* + +**Usage** + +* rpi-fw-crypto -h (Displays usage instructions for all operations) +* rpi-fw-crypto get-num-otp-keys (Returns the number of OTP key slots) +* rpi-fw-crypto sign --in message.bin --key-id 1 --alg ec --out sig.bin (Signs message.bin with the device unique OTP key (id 1)) +* rpi-fw-crypto get-key-status 1 (Gets the status of key-id 1) +* rpi-fw-crypto set-key-status 1 LOCKED (Blocks the raw OTP read API on this key until the device is rebooted) +* rpi-fw-crypto hmac --in message.bin --key-id 1 --out hmac.bin (Generates the SHA256 HMAC of message.bin and OTP key id 1) + +** Notes ** +The device unique private key can be provisioned with the `rpi-otp-private-key` utility. +This MUST be a raw ECDSA P-256 key and not just a random number. + +This service is not a hardware security module and the current implementation +does not protect the key and/or OTP from being accessed directly with root level privileges. +It just removes the need to expose the key to userspace (e.g. initramfs) scripts. diff --git a/rpifwcrypto/main.c b/rpifwcrypto/main.c new file mode 100644 index 0000000..db24095 --- /dev/null +++ b/rpifwcrypto/main.c @@ -0,0 +1,356 @@ +#include +#include +#include +#include +#include +#include "rpifwcrypto.h" + +#define SHA256_HASH_SIZE 32 + +static void usage(const char *progname) +{ + fprintf(stderr, + "Usage: %s [args]\n" + "Commands:\n" + " get-num-otp-keys Get number of OTP keys\n" + " get-key-status Get status of specified key\n" + " STATUS is a bitmask of the following flags:\n" + " - DEVICE - Key is the device unique private key\n" + " - LOCKED - The key cannot be read raw format. LOCKED persists until reboot.\n" + " set-key-status [LOCKED] Sets the status attributes for the specified key.\n" + " sign --in --key-id --alg [--out ] [--outform hex]\n" + " Supported algorithms: ec\n" + " --outform hex: Output signature in hexadecimal format\n" + " If --out is omitted, writes to stdout\n" + " hmac --in --key-id [--out ] [--outform hex]\n" + " Calculates HMAC-SHA256 of input file using specified key\n" + " --outform hex: Output HMAC in hexadecimal format\n" + " If --out is omitted, writes to stdout\n", + progname); + exit(1); +} + +static int hash_file(const char *filename, unsigned char *hash, size_t hash_size) +{ + FILE *f; + gnutls_hash_hd_t hash_handle; + unsigned char buffer[4096]; + unsigned char temp_hash[SHA256_HASH_SIZE]; + size_t bytes; + int rc; + + if (hash_size < SHA256_HASH_SIZE) { + fprintf(stderr, "Hash buffer too small. Need at least %d bytes\n", SHA256_HASH_SIZE); + return -1; + } + + if ((rc = gnutls_hash_init(&hash_handle, GNUTLS_DIG_SHA256)) < 0) { + fprintf(stderr, "Error initializing hash: %s\n", gnutls_strerror(rc)); + return -1; + } + + f = fopen(filename, "rb"); + if (!f) { + perror("Failed to open input file"); + gnutls_hash_deinit(hash_handle, NULL); + return -1; + } + + while ((bytes = fread(buffer, 1, sizeof(buffer), f)) > 0) { + if ((rc = gnutls_hash(hash_handle, buffer, bytes)) < 0) { + fprintf(stderr, "Error updating hash: %s\n", gnutls_strerror(rc)); + fclose(f); + gnutls_hash_deinit(hash_handle, NULL); + return -1; + } + } + + if (ferror(f)) { + perror("Error reading file"); + fclose(f); + gnutls_hash_deinit(hash_handle, NULL); + return -1; + } + + fclose(f); + + /* Get the hash output, ensuring we don't write beyond the provided buffer */ + gnutls_hash_deinit(hash_handle, temp_hash); + memcpy(hash, temp_hash, hash_size < SHA256_HASH_SIZE ? hash_size : SHA256_HASH_SIZE); + + return 0; +} + +static int write_hex_output_to_stream(FILE *f, const unsigned char *data, size_t len) +{ + int rc = 0; + + for (size_t i = 0; i < len; i++) { + if (fprintf(f, "%02x", data[i]) < 0) { + rc = -1; + break; + } + } + + if (fprintf(f, "\n") < 0) + rc = -1; + + return rc; +} + +static int write_hex_output(const char *outfile, const unsigned char *data, size_t len) +{ + FILE *f; + int rc; + + if (!outfile) + return write_hex_output_to_stream(stdout, data, len); + + f = fopen(outfile, "w"); + if (!f) + return -1; + + rc = write_hex_output_to_stream(f, data, len); + fclose(f); + return rc; +} + +static int write_binary_output(const char *outfile, const unsigned char *data, size_t len) +{ + FILE *f; + size_t written; + + if (!outfile) { + written = fwrite(data, 1, len, stdout); + return written == len ? 0 : -1; + } + + f = fopen(outfile, "wb"); + if (!f) + return -1; + + written = fwrite(data, 1, len, f); + fclose(f); + return written == len ? 0 : -1; +} + +static int cmd_hmac(int argc, char *argv[]) +{ + const char *infile = NULL; + const char *outfile = NULL; + const char *outform = NULL; + int key_id = -1; + int i; + int rc; + FILE *f; + unsigned char hmac[32]; + uint8_t message[RPI_FW_CRYPTO_HMAC_MSG_MAX_SIZE]; + long file_size; + + for (i = 2; i < argc; i++) + { + if (strcmp(argv[i], "--in") == 0 && i + 1 < argc) + infile = argv[++i]; + else if (strcmp(argv[i], "--out") == 0 && i + 1 < argc) + outfile = argv[++i]; + else if (strcmp(argv[i], "--key-id") == 0 && i + 1 < argc) + key_id = atoi(argv[++i]); + else if (strcmp(argv[i], "--outform") == 0 && i + 1 < argc) + outform = argv[++i]; + } + + if (!infile || key_id < 0) + usage(argv[0]); + + f = fopen(infile, "rb"); + if (!f) + goto fail_read; + + if (fseek(f, 0, SEEK_END) != 0) + goto fail_read; + + file_size = ftell(f); + if (file_size < 0) + goto fail_read; + + if (file_size > RPI_FW_CRYPTO_HMAC_MSG_MAX_SIZE) { + fprintf(stderr, "Input file too large (max %d bytes)\n", RPI_FW_CRYPTO_HMAC_MSG_MAX_SIZE); + goto fail_read; + } + rewind(f); + + if (fread(message, 1, file_size, f) != (size_t)file_size) + goto fail_read; + + // Calculate HMAC-SHA256 and write the output to a file + rc = rpi_fw_crypto_hmac_sha256(key_id, 0, message, file_size, hmac); + if (rc < 0) + goto fail_write; + + if (outform && strcmp(outform, "hex") == 0) { + if (write_hex_output(outfile, hmac, sizeof(hmac)) < 0) + goto fail_write; + } else { + if (write_binary_output(outfile, hmac, sizeof(hmac)) < 0) + goto fail_write; + } + + rc = 0; + goto end; + +fail_read: + if (f) + fclose(f); + rc = -1; + goto end; +fail_write: + rc = -1; + goto end; +end: + return rc; +} + +static int cmd_sign(int argc, char *argv[]) +{ + const char *infile = NULL; + const char *outfile = NULL; + const char *alg = NULL; + const char *outform = NULL; + int key_id = -1; + int i; + int rc; + unsigned char hash[SHA256_HASH_SIZE]; + unsigned char sig[128]; + size_t sig_len = 0; + uint32_t flags = 0; + + for (i = 2; i < argc; i++) { + if (strcmp(argv[i], "--in") == 0 && i + 1 < argc) + infile = argv[++i]; + else if (strcmp(argv[i], "--out") == 0 && i + 1 < argc) + outfile = argv[++i]; + else if (strcmp(argv[i], "--key-id") == 0 && i + 1 < argc) + key_id = atoi(argv[++i]); + else if (strcmp(argv[i], "--alg") == 0 && i + 1 < argc) + alg = argv[++i]; + else if (strcmp(argv[i], "--outform") == 0 && i + 1 < argc) + outform = argv[++i]; + } + + if (!infile || key_id < 0 || !alg) + usage(argv[0]); + + if (strcmp(alg, "ec") != 0) { + fprintf(stderr, "Unsupported algorithm: %s\n", alg); + return -1; + } + + if (hash_file(infile, hash, sizeof(hash)) < 0) + return -1; + + rc = rpi_fw_crypto_ecdsa_sign(flags, (uint32_t)key_id, hash, sizeof(hash), sig, sizeof(sig), &sig_len); + if (rc < 0) + return rc; + + /* Write signature to output file */ + if (outform && strcmp(outform, "hex") == 0) { + if (write_hex_output(outfile, sig, sig_len) < 0) { + perror("Failed to write signature"); + return -1; + } + } else { + if (write_binary_output(outfile, sig, sig_len) < 0) { + perror("Failed to write signature"); + return -1; + } + } + + return 0; +} + +static int parse_key_status_args(int argc, char *argv[], int start_idx, int *out_status) { + int status = 0; + int i; + for (i = start_idx; i < argc; ++i) { + if (strcmp(argv[i], "LOCKED") == 0) { + status |= ARM_CRYPTO_KEY_STATUS_LOCKED; + } else { + fprintf(stderr, "Unknown or unsupported key status string: %s\n", argv[i]); + return -1; + } + } + *out_status = status; + return 0; +} + +int main(int argc, char *argv[]) +{ + int last_err = 0; + int rc = -1; + int num_keys; + int status; + uint32_t key_id; + uint32_t key_status; + + if (argc < 2) + usage(argv[0]); + + if (strcmp(argv[1], "get-num-otp-keys") == 0) { + num_keys = rpi_fw_crypto_get_num_otp_keys(); + if (num_keys < 0) + goto error; + printf("Number of OTP keys: %d\n", num_keys); + return 0; + } + + if (strcmp(argv[1], "get-key-status") == 0) { + if (argc != 3) + usage(argv[0]); + key_id = atoi(argv[2]); + rc = rpi_fw_crypto_get_key_status(key_id, &key_status); + if (rc < 0) + goto error; + printf("Key %u status: 0x%08x (%s)\n", key_id, key_status, rpi_fw_crypto_key_status_str(key_status)); + return 0; + } + + if (strcmp(argv[1], "set-key-status") == 0) { + if (argc < 4) + usage(argv[0]); + key_id = atoi(argv[2]); + status = 0; + if (parse_key_status_args(argc, argv, 3, &status) != 0) { + fprintf(stderr, "Failed to parse key status arguments.\n"); + return -1; + } + rc = rpi_fw_crypto_set_key_status(key_id, status); + if (rc < 0) { + fprintf(stderr, "Failed to set key status: %s\n", rpi_fw_crypto_strerror(rc)); + return rc; + } + printf("Set key %u status to 0x%08x (%s)\n", key_id, status, rpi_fw_crypto_key_status_str(status)); + return 0; + } + + if (strcmp(argv[1], "sign") == 0) { + rc = cmd_sign(argc, argv); + if (rc < 0) + goto error; + return 0; + } + + if (strcmp(argv[1], "hmac") == 0) { + rc = cmd_hmac(argc, argv); + if (rc < 0) + goto error; + return 0; + } + + usage(argv[0]); + return -1; + +error: + last_err = rpi_fw_crypto_get_last_error(); + fprintf(stderr, "Last crypto error: %d (%s)\n", last_err, rpi_fw_crypto_strerror(last_err)); + return rc; +} diff --git a/rpifwcrypto/rpifwcrypto.c b/rpifwcrypto/rpifwcrypto.c new file mode 100644 index 0000000..d768e4a --- /dev/null +++ b/rpifwcrypto/rpifwcrypto.c @@ -0,0 +1,329 @@ +#include +#include +#include +#include +#include +#include +#include +#include "rpifwcrypto.h" +#include + +#define DEVICE_FILE_NAME "/dev/vcio" +#define MAJOR_NUM 100 +#define IOCTL_MBOX_PROPERTY _IOWR(MAJOR_NUM, 0, char *) + +#if 0 +#define LOG_DEBUG(...) do { \ + fprintf(stderr, __VA_ARGS__); \ +} while (0) +#else +#define LOG_DEBUG(...) do { } while (0) +#endif + +/* Mailbox channel for property interface */ +#define MBOX_CHAN_PROPERTY 8 + +/* VideoCore mailbox error flag */ +#define VC_MAILBOX_ERROR 0x80000000 + +/* Common header structure for firmware mailbox messages */ +struct firmware_msg_header { + uint32_t buf_size; + uint32_t code; + uint32_t tag; + uint32_t tag_buf_size; + uint32_t tag_req_resp_size; +}; + +/* Standard message structure for firmware mailbox */ +struct firmware_msg { + struct firmware_msg_header hdr; + uint32_t value[4]; /* Value buffer for request/response */ +}; + + +// HMAC message +#define RPI_FW_CRYPTO_HMAC_MSG_MAX_SIZE 2048 +struct firmware_hmac_msg { + struct firmware_msg_header hdr; + union { + struct { + uint32_t flags; + uint32_t key_id; + uint32_t length; + uint8_t message[RPI_FW_CRYPTO_HMAC_MSG_MAX_SIZE]; + } hmac; + struct { + uint32_t status; + uint32_t length; + uint8_t hmac[32]; // HMAC-SHA256 is always 32 bytes + } resp; + }; +}; + +// ECDSA sign message +#define ECDSA_RESP_MAX_SIZE 128 +struct firmware_ecdsa_sign_msg { + struct firmware_msg_header hdr; + union { + struct { + uint32_t flags; + uint32_t key_id; + uint32_t length; + uint8_t hash[32]; + } sign; + struct { + uint32_t status; + uint32_t length; + uint8_t sig[ECDSA_RESP_MAX_SIZE]; + } resp; + }; +}; + +static int mbox_open(void) +{ + int file_desc = open(DEVICE_FILE_NAME, 0); + if (file_desc < 0) + fprintf(stderr, "Failed to open %s: %s\n", DEVICE_FILE_NAME, strerror(errno)); + return file_desc; +} + +static void mbox_close(int file_desc) +{ + close(file_desc); +} + +static int mbox_property(int file_desc, struct firmware_msg *msg) +{ + int rc = ioctl(file_desc, IOCTL_MBOX_PROPERTY, msg); + if (rc < 0) + fprintf(stderr, "ioctl_mbox_property failed: %d\n", rc); + + LOG_DEBUG("msg.hdr.code: %d\n", msg->hdr.code); + LOG_DEBUG("msg.hdr.tag: %d\n", msg->hdr.tag); + LOG_DEBUG("msg.hdr.tag_buf_size: %d\n", msg->hdr.tag_buf_size); + LOG_DEBUG("msg.hdr.tag_req_resp_size: %d\n", msg->hdr.tag_req_resp_size); + + if (!(msg->hdr.code & VC_MAILBOX_ERROR) || + !(msg->hdr.tag_req_resp_size & VC_MAILBOX_ERROR)) + return -1; + + return 0; +} + +int rpi_fw_crypto_get_num_otp_keys(void) +{ + int mb; + int rc; + struct firmware_msg msg = {0}; + + mb = mbox_open(); + if (mb < 0) + return -RPI_FW_CRYPTO_ERROR_UNKNOWN; + + msg.hdr.buf_size = sizeof(msg); + msg.hdr.tag = TAG_GET_CRYPTO_NUM_OTP_KEYS; + msg.hdr.tag_buf_size = 4; + + rc = mbox_property(mb, &msg); + mbox_close(mb); + + return (rc < 0) ? rc : (int)msg.value[0]; +} + +int rpi_fw_crypto_get_key_status(uint32_t key_id, uint32_t *status) +{ + int mb; + int rc; + struct firmware_msg msg = {0}; + + if (!status) + return -RPI_FW_CRYPTO_EINVAL; + + mb = mbox_open(); + if (mb < 0) + return -RPI_FW_CRYPTO_ERROR_UNKNOWN; + + msg.hdr.buf_size = sizeof(msg); + msg.hdr.tag = TAG_GET_CRYPTO_KEY_STATUS; + msg.hdr.tag_buf_size = 4; + msg.value[0] = key_id; + + rc = mbox_property(mb, &msg); + mbox_close(mb); + + if (rc < 0) + return rc; + + if (msg.value[0] & VC_MAILBOX_ERROR) + return -RPI_FW_CRYPTO_KEY_NOT_FOUND; + + *status = msg.value[0]; + return RPI_FW_CRYPTO_SUCCESS; +} + +int rpi_fw_crypto_get_last_error(void) +{ + int mb; + int rc; + struct firmware_msg msg = {0}; + + mb = mbox_open(); + if (mb < 0) + return -RPI_FW_CRYPTO_ERROR_UNKNOWN; + + msg.hdr.buf_size = sizeof(msg); + msg.hdr.tag = TAG_GET_CRYPTO_LAST_ERROR; + msg.hdr.tag_buf_size = 4; + + rc = mbox_property(mb, &msg); + mbox_close(mb); + + return (rc < 0) ? rc : (int)msg.value[0]; +} + +const char *rpi_fw_crypto_strerror(int status) +{ + switch (status) { + case RPI_FW_CRYPTO_SUCCESS: + return "Success"; + case RPI_FW_CRYPTO_ERROR_UNKNOWN: + return "Unknown error"; + case RPI_FW_CRYPTO_EINVAL: + return "Invalid argument"; + case RPI_FW_CRYPTO_KEY_NOT_FOUND: + return "Key not found"; + case RPI_FW_CRYPTO_KEY_LOCKED: + return "Key is locked"; + case RPI_FW_CRYPTO_KEY_OTP_ERROR: + return "OTP read error"; + case RPI_FW_CRYPTO_KEY_NOT_SET: + return "Key not set (all zeros)"; + case RPI_FW_CRYPTO_KEY_INVALID: + return "Invalid key type/format"; + case RPI_FW_CRYPTO_NOT_SUPPORTED: + return "Operation not supported"; + case RPI_FW_CRYPTO_OPERATION_FAILED: + return "Crypto algorithm error"; + default: + return "Unrecognized error code"; + } +} + +// Converts a key_status value to a human-readable string, e.g. "CUSTOMER LOCKED" +const char *rpi_fw_crypto_key_status_str(uint32_t key_status) +{ + static char buf[64]; + buf[0] = '\0'; + uint32_t known = 0; + if (key_status & ARM_CRYPTO_KEY_STATUS_TYPE_DEVICE_PRIVATE_KEY) { + strcat(buf, "DEVICE"); + known |= ARM_CRYPTO_KEY_STATUS_TYPE_DEVICE_PRIVATE_KEY; + } + if (key_status & ARM_CRYPTO_KEY_STATUS_LOCKED) { + if (buf[0]) + strcat(buf, " "); + strcat(buf, "LOCKED"); + known |= ARM_CRYPTO_KEY_STATUS_LOCKED; + } + if (key_status & ~known) { + if (buf[0]) + strcat(buf, " "); + strcat(buf, "UNKNOWN"); + } + return buf; +} + +// Implementation of ECDSA sign via firmware mailbox +int rpi_fw_crypto_ecdsa_sign(uint32_t flags, uint32_t key_id, const uint8_t *hash, size_t hash_len, + uint8_t *sig, size_t sig_max_len, size_t *sig_len) +{ + int mb; + int rc; + struct firmware_ecdsa_sign_msg msg = {0}; + + if (!hash || !sig || !sig_len || hash_len != 32 || sig_max_len < 32) + return -RPI_FW_CRYPTO_EINVAL; + + mb = mbox_open(); + if (mb < 0) + return -RPI_FW_CRYPTO_ERROR_UNKNOWN; + + msg.hdr.buf_size = sizeof(msg); + msg.hdr.tag = TAG_GET_CRYPTO_ECDSA_SIGN; + msg.hdr.tag_buf_size = 128; + msg.sign.flags = flags; + msg.sign.key_id = key_id; + msg.sign.length = hash_len; + memcpy(msg.sign.hash, hash, hash_len); + + rc = mbox_property(mb, (struct firmware_msg *)&msg); + mbox_close(mb); + if (rc < 0) + return rc; + + if (msg.resp.length > sig_max_len) { + fprintf(stderr, "msg.length %d > sig_max_len %ld\n", msg.resp.length, sig_max_len); + return -RPI_FW_CRYPTO_EINVAL; + } + + memcpy(sig, msg.resp.sig, msg.resp.length); + *sig_len = msg.resp.length; + return RPI_FW_CRYPTO_SUCCESS; +} + +int rpi_fw_crypto_hmac_sha256(uint32_t key_id, uint32_t flags, const uint8_t *message, size_t message_len, uint8_t *hmac) +{ + int mb; + int rc; + struct firmware_hmac_msg msg = {0}; + + if (!message || !hmac || message_len > RPI_FW_CRYPTO_HMAC_MSG_MAX_SIZE) + return -RPI_FW_CRYPTO_EINVAL; + + mb = mbox_open(); + if (mb < 0) + return -RPI_FW_CRYPTO_ERROR_UNKNOWN; + + msg.hdr.buf_size = sizeof(msg); + msg.hdr.tag = TAG_GET_CRYPTO_HMAC_SHA256; + msg.hdr.tag_buf_size = 4 + 4 + 4 + message_len; // flags + key_id + length + message + msg.hmac.flags = flags; + msg.hmac.key_id = key_id; + msg.hmac.length = message_len; + memcpy(msg.hmac.message, message, message_len); + + rc = mbox_property(mb, (struct firmware_msg *)&msg); + mbox_close(mb); + + if (rc < 0) + return rc; + + if (msg.resp.status & VC_MAILBOX_ERROR) + return -RPI_FW_CRYPTO_OPERATION_FAILED; + + memcpy(hmac, msg.resp.hmac, sizeof(msg.resp.hmac)); + return RPI_FW_CRYPTO_SUCCESS; +} + +int rpi_fw_crypto_set_key_status(uint32_t key_id, uint32_t status) +{ + int mb; + int rc; + struct firmware_msg msg = {0}; + + mb = mbox_open(); + if (mb < 0) + return -RPI_FW_CRYPTO_ERROR_UNKNOWN; + + msg.hdr.buf_size = sizeof(msg); + msg.hdr.tag = TAG_SET_CRYPTO_KEY_STATUS; + msg.hdr.tag_buf_size = 8; + msg.value[0] = key_id; + msg.value[1] = status; + + rc = mbox_property(mb, &msg); + mbox_close(mb); + + return (rc < 0) ? rc : RPI_FW_CRYPTO_SUCCESS; +} diff --git a/rpifwcrypto/rpifwcrypto.h b/rpifwcrypto/rpifwcrypto.h new file mode 100644 index 0000000..a80e9a7 --- /dev/null +++ b/rpifwcrypto/rpifwcrypto.h @@ -0,0 +1,111 @@ +#ifndef RPI_FW_CRYPTO_H +#define RPI_FW_CRYPTO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define ARM_CRYPTO_KEY_STATUS_TYPE_DEVICE_PRIVATE_KEY (1 << 0) +#define ARM_CRYPTO_KEY_STATUS_LOCKED (1 << 8) + +#define RPI_FW_CRYPTO_HMAC_MSG_MAX_SIZE 2048 + +/* Crypto-related mailbox tags */ +typedef enum { + TAG_GET_CRYPTO_LAST_ERROR = 0x0003008e, // Get last error code + TAG_GET_CRYPTO_NUM_OTP_KEYS = 0x0003008f, // Get number of available OTP keys + TAG_GET_CRYPTO_KEY_STATUS = 0x00030090, // Get key status + TAG_SET_CRYPTO_KEY_STATUS = 0x00038090, // Set key status + TAG_GET_CRYPTO_ECDSA_SIGN = 0x00030091, // Sign data using ECDSA + TAG_GET_CRYPTO_HMAC_SHA256 = 0x00030092, // Compute HMAC-SHA256 +} RPI_FW_CRYPTO_TAG; + +/* Error codes */ +typedef enum { + RPI_FW_CRYPTO_SUCCESS = 0, + RPI_FW_CRYPTO_ERROR_UNKNOWN = 1, // Unknown error + RPI_FW_CRYPTO_EINVAL = 2, // Invalid argument errors e.g. zero length etc + RPI_FW_CRYPTO_KEY_NOT_FOUND = 3, // No key for the given key_id + RPI_FW_CRYPTO_KEY_LOCKED = 4, // Requested operation for that key is locked + RPI_FW_CRYPTO_KEY_OTP_ERROR = 5, // OTP read error + RPI_FW_CRYPTO_KEY_NOT_SET = 6, // Key is all zeros + RPI_FW_CRYPTO_KEY_INVALID = 7, // Invalid key type/format + RPI_FW_CRYPTO_NOT_SUPPORTED = 8, // Requested operation is not supported + RPI_FW_CRYPTO_OPERATION_FAILED = 9 // Crypto algorithm error +} RPI_FW_CRYPTO_STATUS; + +/** + * Get the number of OTP keys available in firmware + * + * @return The number of available OTP keys, or negative value on error + */ +int rpi_fw_crypto_get_num_otp_keys(void); + +/** + * Get the status of a specific OTP key + * + * @param key_id The ID of the key to query + * @param status Pointer to store the key status + * @return 0 on success, negative error code on failure + */ +int rpi_fw_crypto_get_key_status(uint32_t key_id, uint32_t *status); + +/** + * Set the status of a specific OTP key + * + * @param key_id The ID of the key to set + * @param status The new key status value + * @return 0 on success, negative error code on failure + */ +int rpi_fw_crypto_set_key_status(uint32_t key_id, uint32_t status); + +/** + * Get the last error code from the firmware crypto subsystem + * + * @return The last error code (see RPI_FW_CRYPTO_STATUS) + */ +int rpi_fw_crypto_get_last_error(void); + +/** + * Translate a firmware crypto error status to a human-readable string + * + * @param status The error code (see RPI_FW_CRYPTO_STATUS) + * @return A constant string describing the error + */ +const char *rpi_fw_crypto_strerror(int status); + +/** + * Request an ECDSA signature from the firmware + * + * @param flags Flags for the signing operation + * @param key_id The ID of the key to use for signing + * @param hash Pointer to the hash to sign (must be 32 bytes for SHA256) + * @param hash_len Length of the hash (should be 32) + * @param sig Output buffer for the signature + * @param sig_max_len Size of the output buffer + * @param sig_len Pointer to store the actual signature length + * @return 0 on success, negative error code on failure + */ +int rpi_fw_crypto_ecdsa_sign(uint32_t flags, uint32_t key_id, const uint8_t *hash, size_t hash_len, + uint8_t *sig, size_t sig_max_len, size_t *sig_len); + +/** + * Calculate HMAC-SHA256 using a key in OTP + * + * @param key_id The ID of the key to use + * @param flags Operation flags (currently unused, set to 0) + * @param message Pointer to the message to HMAC + * @param message_len Length of the message + * @param hmac Output buffer for the HMAC (must be 32 bytes) + * @return 0 on success, negative error code on failure + */ +int rpi_fw_crypto_hmac_sha256(uint32_t key_id, uint32_t flags, const uint8_t *message, size_t message_len, + uint8_t *hmac); + +const char *rpi_fw_crypto_key_status_str(uint32_t key_status); + +#ifdef __cplusplus +} +#endif + +#endif /* RPI_FW_CRYPTO_H */