Files
OpenCellular/common/rma_auth.c
Vadim Bendebury aeb0c8e4b9 rma: process proper auth code
When RMA process has been initialized and the user entered the
expected authentication code value, the Cr50 needs to wipe out TPM
memory, open and unlock CCD and reboot the device.

This patch adds a function to accomplish that. User response is
validated on the TPM task context, and TPM reset request also requires
TPM task processing. To decouple response processing from the
following reset, the response processing is handled by a hook task
callback, delayed for 1s to make sure that user receives the response.

After TPM has successfully reset the CCD is reinitialized with RMA
'opened' and the device is rebooted.

Just in case something goes wrong with the unlock and the hook is not
invoked, add a 10s deferred function to take the EC out of reset so
that the device still can reboot.

BRANCH=none
BUG=b:67007905
TEST=on a Bob device:

   - on the Cr50 console enter 'ccd lock', verify that ccd is locked
     (by examining output of the 'ccd' command)

   - at the bash prompt enter gsctool -r -s -t, copy the
     authentication code from the Cr50 console and pass it to gsctool.

   - observe the device reset TPM wiping out its memory, enable CCD
     and reboot.

Change-Id: I6fafb5e642cb2b6f2040507a7f1989607fd31316
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/729983
Reviewed-by: Randall Spangler <rspangler@chromium.org>
Reviewed-by: Mary Ruthven <mruthven@chromium.org>
2017-10-24 12:57:45 -07:00

422 lines
10 KiB
C

/* 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 "byteorder.h"
#include "ccd_config.h"
#include "chip/g/board_id.h"
#include "console.h"
#include "curve25519.h"
#include "extension.h"
#include "hooks.h"
#include "rma_auth.h"
#include "shared_mem.h"
#include "system.h"
#include "timer.h"
#include "tpm_registers.h"
#include "tpm_vendor_cmds.h"
#include "util.h"
#ifdef CONFIG_DCRYPTO
#include "dcrypto.h"
#else
#include "sha256.h"
#endif
#define CPRINTF(format, args...) cprintf(CC_EXTENSION, format, ## args)
/* 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;
static void get_hmac_sha256(void *hmac_out, const uint8_t *secret,
size_t secret_size, const void *ch_ptr,
size_t ch_size)
{
#ifdef CONFIG_DCRYPTO
LITE_HMAC_CTX hmac;
DCRYPTO_HMAC_SHA256_init(&hmac, secret, secret_size);
HASH_update(&hmac.hash, ch_ptr, ch_size);
memcpy(hmac_out, DCRYPTO_HMAC_final(&hmac), 32);
#else
hmac_SHA256(hmac_out, secret, secret_size, ch_ptr, ch_size);
#endif
}
static void hash_buffer(void *dest, size_t dest_size,
const void *buffer, size_t buf_size)
{
/* We know that the destination is no larger than 32 bytes. */
uint8_t temp[32];
get_hmac_sha256(temp, buffer, buf_size, buffer, buf_size);
/* Or should we do XOR of the temp modulo dest size? */
memcpy(dest, temp, dest_size);
}
/**
* 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;
int unique_device_id_size;
/* 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));
unique_device_id_size = system_get_chip_unique_id(&device_id);
/* Smaller unique device IDs will fill c.device_id only partially. */
if (unique_device_id_size <= sizeof(c.device_id)) {
/* The size matches, let's just copy it as is. */
memcpy(c.device_id, device_id, unique_device_id_size);
} else {
/*
* The unique device ID size exceeds space allotted in
* rma_challenge:device_id, let's use first few bytes of
* its hash.
*/
hash_buffer(c.device_id, sizeof(c.device_id),
device_id, unique_device_id_size);
}
/* 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.
*/
get_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;
/* Fail if auth code has not been calculated yet. */
if (!*authcode)
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;
}
#ifndef TEST_BUILD
/*
* Trigger generating of the new challenge/authcode pair. If successful, store
* the challenge in the vendor command response buffer and send it to the
* sender. If not successful - return the error value to the sender.
*/
static enum vendor_cmd_rc get_challenge(uint8_t *buf, size_t *buf_size)
{
int rv;
if (*buf_size < sizeof(challenge)) {
*buf_size = 1;
buf[0] = VENDOR_RC_RESPONSE_TOO_BIG;
return buf[0];
}
rv = rma_create_challenge();
if (rv != EC_SUCCESS) {
*buf_size = 1;
buf[0] = rv;
return buf[0];
}
*buf_size = sizeof(challenge) - 1;
memcpy(buf, rma_get_challenge(), *buf_size);
#ifdef CR50_DEV
{
size_t i;
CPRINTF("%s: generated challenge:\n", __func__);
for (i = 0; i < *buf_size; i++)
CPRINTF("%c", ((uint8_t *)buf)[i]);
CPRINTF("\n");
CPRINTF("%s: expected authcode: ", __func__);
for (i = 0; i < RMA_AUTHCODE_CHARS; i++)
CPRINTF("%c", authcode[i]);
CPRINTF("\n");
}
#endif
return VENDOR_RC_SUCCESS;
}
static uint8_t ccd_hook_active;
static void ccd_config_changed(void)
{
if (!ccd_hook_active)
return;
CPRINTF("%s: CCD change saved, rebooting\n", __func__);
cflush();
system_reset(SYSTEM_RESET_HARD);
}
DECLARE_HOOK(HOOK_CCD_CHANGE, ccd_config_changed, HOOK_PRIO_LAST);
static void rma_reset_failed(void)
{
ccd_hook_active = 0;
CPRINTF("%s: CCD RMA reset failed\n");
deassert_ec_rst();
}
DECLARE_DEFERRED(rma_reset_failed);
/* The below time constants are way longer than should be required in practice:
*
* Time it takes to finish processing TPM command which provided valid RMA
* authentication code.
*/
#define TPM_PROCESSING_TIME (1 * SECOND)
/*
* Time it takse TPM reset function to wipe out the NVMEM and reboot the
* device.
*/
#define TPM_RESET_TIME (10 * SECOND)
/* Total time deep sleep should not be allowed. */
#define DISABLE_SLEEP_TIME (TPM_PROCESSING_TIME + TPM_RESET_TIME)
static void enter_rma_mode(void)
{
int rv;
CPRINTF("%s: resetting TPM\n", __func__);
/*
* Let's make sure the rest of the system is out of the way while TPM
* is being wiped out.
*/
assert_ec_rst();
if (tpm_reset_request(1, 1) != EC_SUCCESS) {
CPRINTF("%s: TPM reset attempt failed\n", __func__);
deassert_ec_rst();
return;
}
tpm_reinstate_nvmem_commits();
CPRINTF("%s: TPM reset succeeded, RMA resetting CCD\n", __func__);
ccd_hook_active = 1;
rv = ccd_reset_config(CCD_RESET_RMA);
if (rv != EC_SUCCESS)
rma_reset_failed();
/*
* Make sure we never end up with the EC held in reset, no matter what
* prevents the proper RMA flow from succeeding.
*/
hook_call_deferred(&rma_reset_failed_data, TPM_RESET_TIME);
}
DECLARE_DEFERRED(enter_rma_mode);
/*
* Compare response sent by the operator with the pre-compiled auth code.
* Return error code or success depending on the comparison results.
*/
static enum vendor_cmd_rc process_response(uint8_t *buf,
size_t input_size,
size_t *response_size)
{
int rv;
*response_size = 1; /* Just in case there is an error. */
if (input_size != RMA_AUTHCODE_CHARS) {
CPRINTF("%s: authcode size %d\n",
__func__, input_size);
buf[0] = VENDOR_RC_BOGUS_ARGS;
return buf[0];
}
rv = rma_try_authcode(buf);
if (rv == EC_SUCCESS) {
CPRINTF("%s: success!\n", __func__);
*response_size = 0;
delay_sleep_by(DISABLE_SLEEP_TIME);
hook_call_deferred(&enter_rma_mode_data, TPM_PROCESSING_TIME);
return VENDOR_RC_SUCCESS;
}
CPRINTF("%s: authcode mismatch\n", __func__);
buf[0] = VENDOR_RC_INTERNAL_ERROR;
return buf[0];
}
/*
* Handle the VENDOR_CC_RMA_CHALLENGE_RESPONSE command. When received with
* empty payload - this is a request to generate a new challenge, when
* received with a payload, this is a request to check if the payload matches
* the previously calculated auth code.
*/
static enum vendor_cmd_rc rma_challenge_response(enum vendor_cmd_cc code,
void *buf,
size_t input_size,
size_t *response_size)
{
if (!input_size)
/*
* This is a request for the challenge, get it and send it
* back.
*/
return get_challenge(buf, response_size);
return process_response(buf, input_size, response_size);
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_RMA_CHALLENGE_RESPONSE,
rma_challenge_response);
#define RMA_CMD_BUF_SIZE (sizeof(struct tpm_cmd_header) + \
RMA_CHALLENGE_BUF_SIZE)
static int rma_auth_cmd(int argc, char **argv)
{
struct tpm_cmd_header *tpmh;
int rv;
if (argc > 2) {
ccprintf("Error: the only accepted parameter is"
" the auth code to check\n");
return EC_ERROR_PARAM_COUNT;
}
if (argc == 2) {
if (rma_try_authcode(argv[1]) != EC_SUCCESS) {
ccprintf("Auth code does not match.\n");
return EC_ERROR_PARAM1;
}
ccprintf("Auth code match!\n");
return EC_SUCCESS;
}
rv = shared_mem_acquire(RMA_CMD_BUF_SIZE, (char **)&tpmh);
if (rv != EC_SUCCESS)
return rv;
/* Build the extension command to request RMA AUTH challenge. */
tpmh->tag = htobe16(0x8001); /* TPM_ST_NO_SESSIONS */
tpmh->size = htobe32(sizeof(struct tpm_cmd_header));
tpmh->command_code = htobe32(TPM_CC_VENDOR_BIT_MASK);
tpmh->subcommand_code = htobe16(VENDOR_CC_RMA_CHALLENGE_RESPONSE);
tpm_alt_extension(tpmh, RMA_CMD_BUF_SIZE);
/* Return status in the command code field now. */
if (tpmh->command_code) {
ccprintf("RMA Auth error 0x%x\n", be32toh(tpmh->command_code));
rv = EC_ERROR_UNKNOWN;
} else {
/* Success, let's print out the challenge. */
int i;
char *challenge = (char *)(tpmh + 1);
for (i = 0; i < RMA_CHALLENGE_CHARS; i++) {
if (!(i % 5)) {
if (!(i % 20))
ccprintf("\n");
ccprintf(" ");
}
ccprintf("%c", challenge[i]);
}
ccprintf("\n");
}
shared_mem_release(tpmh);
return EC_SUCCESS;
}
DECLARE_SAFE_CONSOLE_COMMAND(rma_auth, rma_auth_cmd, NULL,
"rma_auth [auth code] - "
"Generate RMA challenge or check auth code match\n");
#endif