From eef5c0f395adfc56048683196f45b613c2cc416f Mon Sep 17 00:00:00 2001 From: Randall Spangler Date: Wed, 21 Jun 2017 13:10:57 -0700 Subject: common: Add RMA reset auth challenge-response crypto RMA auth uses X25519 to generate a relatively small challenge and response. Currently, nothing calls the rma_auth code. We'll need console and TPM vendor commands to do so. Conflicts: include/config.h BUG=b:37952913 BRANCH=none TEST=make buildall Change-Id: Iec7f2d0e3dc8243f79b009ead16bb3ba9f1bef9d Signed-off-by: Randall Spangler Reviewed-on: https://chromium-review.googlesource.com/544184 (cherry picked from commit 282765fdd409fd16ed1e092e5d7fee8de5af7a5a) Signed-off-by: Vadim Bendebury Reviewed-on: https://chromium-review.googlesource.com/828662 --- common/build.mk | 1 + common/rma_auth.c | 123 ++++++++++++++++++++++++++++++ include/config.h | 6 ++ include/rma_auth.h | 80 ++++++++++++++++++++ test/build.mk | 2 + test/rma_auth.c | 199 +++++++++++++++++++++++++++++++++++++++++++++++++ test/rma_auth.tasklist | 17 +++++ test/test_config.h | 10 +++ 8 files changed, 438 insertions(+) create mode 100644 common/rma_auth.c create mode 100644 include/rma_auth.h create mode 100644 test/rma_auth.c create mode 100644 test/rma_auth.tasklist diff --git a/common/build.mk b/common/build.mk index ae48325949..1c1b2415d7 100644 --- a/common/build.mk +++ b/common/build.mk @@ -78,6 +78,7 @@ common-$(CONFIG_POWER_BUTTON_X86)+=power_button_x86.o common-$(CONFIG_PSTORE)+=pstore_commands.o common-$(CONFIG_PWM)+=pwm.o common-$(CONFIG_PWM_KBLIGHT)+=pwm_kblight.o +common-$(CONFIG_RMA_AUTH)+=rma_auth.o common-$(CONFIG_RSA)+=rsa.o common-$(CONFIG_RWSIG)+=rwsig.o common-$(CONFIG_MATH_UTIL)+=math_util.o diff --git a/common/rma_auth.c b/common/rma_auth.c new file mode 100644 index 0000000000..f178524927 --- /dev/null +++ b/common/rma_auth.c @@ -0,0 +1,123 @@ +/* Copyright 2017 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* RMA authorization challenge-response */ + +#include "common.h" +#include "base32.h" +#include "chip/g/board_id.h" +#include "curve25519.h" +#include "rma_auth.h" +#include "sha256.h" +#include "system.h" +#include "timer.h" +#include "util.h" + +/* Minimum time since system boot or last challenge before making a new one */ +#define CHALLENGE_INTERVAL (10 * SECOND) + +/* Number of tries to properly enter auth code */ +#define MAX_AUTHCODE_TRIES 3 + +/* Server public key and key ID */ +static const uint8_t server_pub_key[32] = CONFIG_RMA_AUTH_SERVER_PUBLIC_KEY; +static const uint8_t server_key_id = CONFIG_RMA_AUTH_SERVER_KEY_ID; + +static char challenge[RMA_CHALLENGE_BUF_SIZE]; +static char authcode[RMA_AUTHCODE_BUF_SIZE]; +static int tries_left; +static uint64_t last_challenge_time; + +/** + * Create a new RMA challenge/response + * + * @return EC_SUCCESS, EC_ERROR_TIMEOUT if too soon since the last challenge, + * or other non-zero error code. + */ +int rma_create_challenge(void) +{ + uint8_t temp[32]; /* Private key or HMAC */ + uint8_t secret[32]; + struct rma_challenge c; + struct board_id bid; + uint8_t *device_id; + uint8_t *cptr = (uint8_t *)&c; + uint64_t t; + + /* Clear the current challenge and authcode, if any */ + memset(challenge, 0, sizeof(challenge)); + memset(authcode, 0, sizeof(authcode)); + + /* Rate limit challenges */ + t = get_time().val; + if (t - last_challenge_time < CHALLENGE_INTERVAL) + return EC_ERROR_TIMEOUT; + last_challenge_time = t; + + memset(&c, 0, sizeof(c)); + c.version_key_id = RMA_CHALLENGE_VKID_BYTE( + RMA_CHALLENGE_VERSION, server_key_id); + + if (read_board_id(&bid)) + return EC_ERROR_UNKNOWN; + memcpy(c.board_id, &bid.type, sizeof(c.board_id)); + + if (system_get_chip_unique_id(&device_id) != sizeof(c.device_id)) + return EC_ERROR_UNKNOWN; + memcpy(c.device_id, device_id, sizeof(c.device_id)); + + /* Calculate a new ephemeral key pair */ + X25519_keypair(c.device_pub_key, temp); + + /* Encode the challenge */ + if (base32_encode(challenge, sizeof(challenge), cptr, 8 * sizeof(c), 9)) + return EC_ERROR_UNKNOWN; + + /* Calculate the shared secret */ + X25519(secret, temp, server_pub_key); + + /* + * Auth code is a truncated HMAC of the ephemeral public key, BoardID, + * and DeviceID. Those are all in the right order in the challenge + * struct, after the version/key id byte. + */ + hmac_SHA256(temp, secret, sizeof(secret), cptr + 1, sizeof(c) - 1); + if (base32_encode(authcode, sizeof(authcode), temp, + RMA_AUTHCODE_CHARS * 5, 0)) + return EC_ERROR_UNKNOWN; + + tries_left = MAX_AUTHCODE_TRIES; + return EC_SUCCESS; +} + +const char *rma_get_challenge(void) +{ + return challenge; +} + +int rma_try_authcode(const char *code) +{ + int rv = EC_ERROR_INVAL; + + /* Fail if out of tries */ + if (!tries_left) + return EC_ERROR_ACCESS_DENIED; + + if (safe_memcmp(authcode, code, RMA_AUTHCODE_CHARS)) { + /* Mismatch */ + tries_left--; + } else { + rv = EC_SUCCESS; + tries_left = 0; + } + + /* Clear challenge and response if out of tries */ + if (!tries_left) { + memset(challenge, 0, sizeof(challenge)); + memset(authcode, 0, sizeof(authcode)); + } + + return rv; +} diff --git a/include/config.h b/include/config.h index 81141eaa3a..93721c9baa 100644 --- a/include/config.h +++ b/include/config.h @@ -1764,6 +1764,12 @@ /* Support IR357x Link voltage regulator debugging / reprogramming */ #undef CONFIG_REGULATOR_IR357X +/* Support RMA auth challenge-response */ +#undef CONFIG_RMA_AUTH +/* If that's defined, the server public key and ID must also be defined */ +#undef CONFIG_RMA_AUTH_SERVER_PUBLIC_KEY /* 32 bytes: {0xNN, 0xNN, ... 0xNN} */ +#undef CONFIG_RMA_AUTH_SERVER_KEY_ID /* 6-bit key ID, 0xMM */ + /* Support verifying 2048-bit RSA signature */ #undef CONFIG_RSA diff --git a/include/rma_auth.h b/include/rma_auth.h new file mode 100644 index 0000000000..db39468595 --- /dev/null +++ b/include/rma_auth.h @@ -0,0 +1,80 @@ +/* Copyright 2017 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* RMA challenge-response */ + +#ifndef __CROS_EC_RMA_AUTH_H +#define __CROS_EC_RMA_AUTH_H + +#include + +/* Test server public and private keys */ +#define RMA_TEST_SERVER_PUBLIC_KEY { \ + 0x03, 0xae, 0x2d, 0x2c, 0x06, 0x23, 0xe0, 0x73, \ + 0x0d, 0xd3, 0xb7, 0x92, 0xac, 0x54, 0xc5, 0xfd, \ + 0x7e, 0x9c, 0xf0, 0xa8, 0xeb, 0x7e, 0x2a, 0xb5, \ + 0xdb, 0xf4, 0x79, 0x5f, 0x8a, 0x0f, 0x28, 0x3f} +#define RMA_TEST_SERVER_PRIVATE_KEY { \ + 0x47, 0x3b, 0xa5, 0xdb, 0xc4, 0xbb, 0xd6, 0x77, \ + 0x20, 0xbd, 0xd8, 0xbd, 0xc8, 0x7a, 0xbb, 0x07, \ + 0x03, 0x79, 0xba, 0x7b, 0x52, 0x8c, 0xec, 0xb3, \ + 0x4d, 0xaa, 0x69, 0xf5, 0x65, 0xb4, 0x31, 0xad} +#define RMA_TEST_SERVER_KEY_ID 0x10 + +/* Current challenge protocol version */ +#define RMA_CHALLENGE_VERSION 0 + +/* Getters and setters for version_key_id byte */ +#define RMA_CHALLENGE_VKID_BYTE(version, keyid) \ + (((version) << 6) | ((keyid) & 0x3f)) +#define RMA_CHALLENGE_GET_VERSION(vkidbyte) ((vkidbyte) >> 6) +#define RMA_CHALLENGE_GET_KEY_ID(vkidbyte) ((vkidbyte) & 0x3f) + +struct __packed rma_challenge { + /* Top 2 bits are protocol version; bottom 6 are server KeyID */ + uint8_t version_key_id; + + /* Ephemeral public key from device */ + uint8_t device_pub_key[32]; + + /* Board ID (.type) */ + uint8_t board_id[4]; + + /* Device ID */ + uint8_t device_id[8]; +}; + +/* Size of encoded challenge and response, and buffer sizes to hold them */ +#define RMA_CHALLENGE_CHARS 80 +#define RMA_CHALLENGE_BUF_SIZE (RMA_CHALLENGE_CHARS + 1) + +#define RMA_AUTHCODE_CHARS 8 +#define RMA_AUTHCODE_BUF_SIZE (RMA_AUTHCODE_CHARS + 1) + +/** + * Create a new RMA challenge/response + * + * @return EC_SUCCESS, EC_ERROR_TIMEOUT if too soon since the last challenge, + * or other non-zero error code. + */ +int rma_create_challenge(void); + +/** + * Get the current challenge string + * + * @return a pointer to the challenge string. String will be empty if there + * is no active challenge. + */ +const char *rma_get_challenge(void); + +/** + * Try a RMA authorization code + * + * @param code Authorization code to try + * @return EC_SUCCESS if the response was correct, or non-zero error code. + */ +int rma_try_authcode(const char *code); + +#endif diff --git a/test/build.mk b/test/build.mk index b114794dbf..ab9804a34c 100644 --- a/test/build.mk +++ b/test/build.mk @@ -60,6 +60,7 @@ test-list-host += nvmem_vars test-list-host += pingpong test-list-host += power_button test-list-host += queue +test-list-host += rma_auth test-list-host += rsa test-list-host += rsa3 test-list-host += sbs_charging_v2 @@ -105,6 +106,7 @@ pingpong-y=pingpong.o power_button-y=power_button.o powerdemo-y=powerdemo.o queue-y=queue.o +rma_auth-y=rma_auth.o rsa-y=rsa.o rsa3-y=rsa.o sbs_charging-y=sbs_charging.o diff --git a/test/rma_auth.c b/test/rma_auth.c new file mode 100644 index 0000000000..d833a2c33b --- /dev/null +++ b/test/rma_auth.c @@ -0,0 +1,199 @@ +/* Copyright 2017 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Test RMA auth challenge/response + */ + +#include +#include "common.h" +#include "chip/g/board_id.h" +#include "curve25519.h" +#include "base32.h" +#include "sha256.h" +#include "rma_auth.h" +#include "test_util.h" +#include "timer.h" +#include "util.h" + +/* Dummy implementations for testing */ +static uint8_t dummy_board_id[4] = {'Z', 'Z', 'C', 'R'}; +static uint8_t dummy_device_id[8] = {'T', 'H', 'X', 1, 1, 3, 8, 0xfe}; +static int server_protocol_version = RMA_CHALLENGE_VERSION; +static uint8_t server_private_key[32] = RMA_TEST_SERVER_PRIVATE_KEY; +static int server_key_id = RMA_TEST_SERVER_KEY_ID; + +void rand_bytes(void *buffer, size_t len) +{ + FILE *f = fopen("/dev/urandom", "rb"); + + assert(f); + fread(buffer, 1, len, f); + fclose(f); +} + +int read_board_id(struct board_id *id) +{ + memcpy(&id->type, dummy_board_id, sizeof(id->type)); + id->type_inv = ~id->type; + id->flags = 0xFF00; + return EC_SUCCESS; +} + +int system_get_chip_unique_id(uint8_t **id) +{ + *id = dummy_device_id; + return sizeof(dummy_device_id); +} + +/** + * Simulate the server side of a RMA challenge-response. + * + * @param out_auth_code Buffer for generated authorization code + * (must be >= CR50_AUTH_CODE_CHARS + 1 chars) + * @param challenge Challenge from device + * @return 0 if success, non-zero if error. + */ +int rma_server_side(char *out_auth_code, const char *challenge) +{ + int version, key_id; + uint32_t device_id[2]; + uint8_t secret[32]; + uint8_t hmac[32]; + struct rma_challenge c; + uint8_t *cptr = (uint8_t *)&c; + + /* Convert the challenge back into binary */ + if (base32_decode(cptr, 8 * sizeof(c), challenge, 9) != 8 * sizeof(c)) { + printf("Error decoding challenge\n"); + return -1; + } + + version = RMA_CHALLENGE_GET_VERSION(c.version_key_id); + if (version != server_protocol_version) { + printf("Unsupported challenge version %d\n", version); + return -1; + } + + key_id = RMA_CHALLENGE_GET_KEY_ID(c.version_key_id); + + printf("\nChallenge: %s\n", challenge); + printf(" Version: %d\n", version); + printf(" Server KeyID: %d\n", key_id); + printf(" BoardID: %c%c%c%c\n", + isprint(c.board_id[0]) ? c.board_id[0] : '?', + isprint(c.board_id[1]) ? c.board_id[1] : '?', + isprint(c.board_id[2]) ? c.board_id[2] : '?', + isprint(c.board_id[3]) ? c.board_id[3] : '?'); + + memcpy(device_id, c.device_id, sizeof(device_id)); + printf(" DeviceID: 0x%08x 0x%08x\n", device_id[0], device_id[1]); + + if (key_id != server_key_id) { + printf("Unsupported KeyID %d\n", key_id); + return -1; + } + + /* + * Make sure the current user is authorized to reset this board. + * + * Since this is just a test, here we'll just make sure the BoardID + * and DeviceID match what we expected. + */ + if (memcmp(c.board_id, dummy_board_id, sizeof(c.board_id))) { + printf("BoardID mismatch\n"); + return -1; + } + if (memcmp(c.device_id, dummy_device_id, sizeof(c.device_id))) { + printf("DeviceID mismatch\n"); + return -1; + } + + /* Calculate the shared secret */ + X25519(secret, server_private_key, c.device_pub_key); + + /* + * Auth code is a truncated HMAC of the ephemeral public key, BoardID, + * and DeviceID. + */ + hmac_SHA256(hmac, secret, sizeof(secret), cptr + 1, sizeof(c) - 1); + if (base32_encode(out_auth_code, RMA_AUTHCODE_BUF_SIZE, + hmac, RMA_AUTHCODE_CHARS * 5, 0)) { + printf("Error encoding auth code\n"); + return -1; + } + printf("Authcode: %s\n", out_auth_code); + + return 0; +}; + +#define FORCE_TIME(t) { ts.val = (t); force_time(ts); } + +static int test_rma_auth(void) +{ + const char *challenge; + char authcode[RMA_AUTHCODE_BUF_SIZE]; + timestamp_t ts; + + /* Test rate limiting */ + FORCE_TIME(9 * SECOND); + TEST_ASSERT(rma_create_challenge() == EC_ERROR_TIMEOUT); + TEST_ASSERT(rma_try_authcode("Bad") == EC_ERROR_ACCESS_DENIED); + TEST_ASSERT(strlen(rma_get_challenge()) == 0); + + FORCE_TIME(10 * SECOND); + TEST_ASSERT(rma_create_challenge() == 0); + TEST_ASSERT(strlen(rma_get_challenge()) == RMA_CHALLENGE_CHARS); + + /* Test using up tries */ + TEST_ASSERT(rma_try_authcode("Bad") == EC_ERROR_INVAL); + TEST_ASSERT(strlen(rma_get_challenge()) == RMA_CHALLENGE_CHARS); + TEST_ASSERT(rma_try_authcode("BadCodeZ") == EC_ERROR_INVAL); + TEST_ASSERT(strlen(rma_get_challenge()) == RMA_CHALLENGE_CHARS); + TEST_ASSERT(rma_try_authcode("BadLongCode") == EC_ERROR_INVAL); + /* Out of tries now */ + TEST_ASSERT(strlen(rma_get_challenge()) == 0); + TEST_ASSERT(rma_try_authcode("Bad") == EC_ERROR_ACCESS_DENIED); + + FORCE_TIME(19 * SECOND); + TEST_ASSERT(rma_create_challenge() == EC_ERROR_TIMEOUT); + TEST_ASSERT(strlen(rma_get_challenge()) == 0); + + FORCE_TIME(21 * SECOND); + TEST_ASSERT(rma_create_challenge() == 0); + challenge = rma_get_challenge(); + TEST_ASSERT(strlen(challenge) == RMA_CHALLENGE_CHARS); + TEST_ASSERT(rma_server_side(authcode, challenge) == 0); + TEST_ASSERT(rma_try_authcode(authcode) == EC_SUCCESS); + + /* + * Make sure the server-side checks for fields work. That is, test + * our ability to test those fields... + */ + server_protocol_version++; + TEST_ASSERT(rma_server_side(authcode, challenge) == -1); + server_protocol_version--; + + server_key_id++; + TEST_ASSERT(rma_server_side(authcode, challenge) == -1); + server_key_id--; + + dummy_board_id[0]++; + TEST_ASSERT(rma_server_side(authcode, challenge) == -1); + dummy_board_id[0]--; + + dummy_device_id[0]++; + TEST_ASSERT(rma_server_side(authcode, challenge) == -1); + dummy_device_id[0]--; + + return EC_SUCCESS; +} + +void run_test(void) +{ + test_reset(); + + RUN_TEST(test_rma_auth); + + test_print_result(); +} diff --git a/test/rma_auth.tasklist b/test/rma_auth.tasklist new file mode 100644 index 0000000000..e241aab4bb --- /dev/null +++ b/test/rma_auth.tasklist @@ -0,0 +1,17 @@ +/* Copyright 2017 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/** + * List of enabled tasks in the priority order + * + * The first one has the lowest priority. + * + * For each task, use the macro TASK_TEST(n, r, d, s) where : + * 'n' in the name of the task + * 'r' in the main routine of the task + * 'd' in an opaque parameter passed to the routine at startup + * 's' is the stack size in bytes; must be a multiple of 8 + */ +#define CONFIG_TEST_TASK_LIST /* No test task */ diff --git a/test/test_config.h b/test/test_config.h index 5079b173fd..b208b4a009 100644 --- a/test/test_config.h +++ b/test/test_config.h @@ -58,6 +58,16 @@ #define CONFIG_TABLET_MODE #endif +#ifdef TEST_RMA_AUTH +#define CONFIG_BASE32 +#define CONFIG_CURVE25519 +#define CONFIG_RMA_AUTH +#define CONFIG_RMA_AUTH_SERVER_PUBLIC_KEY RMA_TEST_SERVER_PUBLIC_KEY +#define CONFIG_RMA_AUTH_SERVER_KEY_ID RMA_TEST_SERVER_KEY_ID +#define CONFIG_RNG +#define CONFIG_SHA256 +#endif + #ifdef TEST_RSA #define CONFIG_RSA #define CONFIG_RSA_KEY_SIZE 2048 -- cgit v1.2.1