458 lines
12 KiB
C
458 lines
12 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"
|
|
#ifdef CONFIG_CURVE25519
|
|
#include "curve25519.h"
|
|
#endif
|
|
#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"
|
|
#ifdef CONFIG_RMA_AUTH_USE_P256
|
|
#include "trng.h"
|
|
#endif
|
|
#include "util.h"
|
|
|
|
#ifndef TEST_BUILD
|
|
#include "cryptoc/util.h"
|
|
#include "rma_key_from_blob.h"
|
|
#else
|
|
/* Cryptoc library is not available to the test layer. */
|
|
#define always_memset memset
|
|
#endif
|
|
|
|
#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
|
|
|
|
#ifdef CONFIG_RMA_AUTH_USE_P256
|
|
#define RMA_SERVER_PUB_KEY_SZ 65
|
|
#else
|
|
#define RMA_SERVER_PUB_KEY_SZ 32
|
|
#endif
|
|
|
|
/* Server public key and key ID */
|
|
static const struct {
|
|
union {
|
|
uint8_t raw_blob[RMA_SERVER_PUB_KEY_SZ + 1];
|
|
struct {
|
|
uint8_t server_pub_key[RMA_SERVER_PUB_KEY_SZ];
|
|
volatile uint8_t server_key_id;
|
|
};
|
|
};
|
|
} __packed rma_key_blob = {
|
|
.raw_blob = RMA_KEY_BLOB
|
|
};
|
|
|
|
BUILD_ASSERT(sizeof(rma_key_blob) == (RMA_SERVER_PUB_KEY_SZ + 1));
|
|
|
|
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);
|
|
}
|
|
|
|
#ifdef CONFIG_RMA_AUTH_USE_P256
|
|
/*
|
|
* Generate a p256 key pair, such that Y coordinate component of the public
|
|
* key is an odd value. Use the X component value as the compressed public key
|
|
* to be sent to the server. Multiply server public key by our private key to
|
|
* generate the shared secret.
|
|
*
|
|
* @pub_key - array to return 32 bytes of the X coordinate public key
|
|
* component.
|
|
* @secet - array to return the X coordinate of the product of the server
|
|
* public key multiplied by our private key.
|
|
*/
|
|
static void p256_get_pub_key_and_secret(uint8_t pub_key[P256_NBYTES],
|
|
uint8_t secret[P256_NBYTES])
|
|
{
|
|
uint8_t buf[SHA256_DIGEST_SIZE];
|
|
p256_int d;
|
|
p256_int pk_x;
|
|
p256_int pk_y;
|
|
|
|
/* Get some noise for private key. */
|
|
rand_bytes(buf, sizeof(buf));
|
|
|
|
/*
|
|
* By convention with the RMA server the Y coordinate of the Cr50
|
|
* public key component is required to be an odd value. Keep trying
|
|
* until the genreated bublic key has the compliant Y coordinate.
|
|
*/
|
|
while (1) {
|
|
HASH_CTX sha;
|
|
|
|
if (DCRYPTO_p256_key_from_bytes(&pk_x, &pk_y, &d, buf)) {
|
|
|
|
/* Is Y coordinate an odd value? */
|
|
if (p256_is_odd(&pk_y))
|
|
break; /* Yes it is, got a good key. */
|
|
}
|
|
|
|
/* Did not succeed, rehash the private key and try again. */
|
|
DCRYPTO_SHA256_init(&sha, 0);
|
|
HASH_update(&sha, buf, sizeof(buf));
|
|
memcpy(buf, HASH_final(&sha), sizeof(buf));
|
|
}
|
|
|
|
/* X coordinate is passed to the server as the public key. */
|
|
p256_to_bin(&pk_x, pub_key);
|
|
|
|
/*
|
|
* Now let's calculate the secret as a the server pub key multiplied
|
|
* by our private key.
|
|
*/
|
|
p256_from_bin(rma_key_blob.raw_blob + 1, &pk_x);
|
|
p256_from_bin(rma_key_blob.raw_blob + 1 + P256_NBYTES, &pk_y);
|
|
|
|
/* Use input space for storing multiplication results. */
|
|
DCRYPTO_p256_point_mul(&pk_x, &pk_y, &d, &pk_x, &pk_y);
|
|
|
|
/* X value is the seed for the shared secret. */
|
|
p256_to_bin(&pk_x, secret);
|
|
|
|
/* Wipe out the private key just in case. */
|
|
always_memset(&d, 0, sizeof(d));
|
|
}
|
|
#endif
|
|
|
|
void get_rma_device_id(uint8_t rma_device_id[RMA_DEVICE_ID_SIZE])
|
|
{
|
|
uint8_t *chip_unique_id;
|
|
int chip_unique_id_size = system_get_chip_unique_id(&chip_unique_id);
|
|
|
|
if (chip_unique_id_size < 0)
|
|
chip_unique_id_size = 0;
|
|
/* Smaller unique chip IDs will fill rma_device_id only partially. */
|
|
if (chip_unique_id_size <= RMA_DEVICE_ID_SIZE) {
|
|
/* The size matches, let's just copy it as is. */
|
|
memcpy(rma_device_id, chip_unique_id, chip_unique_id_size);
|
|
if (chip_unique_id_size < RMA_DEVICE_ID_SIZE) {
|
|
memset(rma_device_id + chip_unique_id_size, 0,
|
|
RMA_DEVICE_ID_SIZE - chip_unique_id_size);
|
|
}
|
|
} else {
|
|
/*
|
|
* The unique chip ID size exceeds space allotted in
|
|
* rma_challenge:device_id, let's use first few bytes of
|
|
* its hash.
|
|
*/
|
|
hash_buffer(rma_device_id, RMA_DEVICE_ID_SIZE,
|
|
chip_unique_id, chip_unique_id_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 *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, rma_key_blob.server_key_id);
|
|
|
|
if (read_board_id(&bid))
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
memcpy(c.board_id, &bid.type, sizeof(c.board_id));
|
|
get_rma_device_id(c.device_id);
|
|
|
|
/* Calculate a new ephemeral key pair and the shared secret. */
|
|
#ifdef CONFIG_RMA_AUTH_USE_P256
|
|
p256_get_pub_key_and_secret(c.device_pub_key, secret);
|
|
#endif
|
|
#ifdef CONFIG_CURVE25519
|
|
X25519_keypair(c.device_pub_key, temp);
|
|
X25519(secret, temp, rma_key_blob.server_pub_key);
|
|
#endif
|
|
/* Encode the challenge */
|
|
if (base32_encode(challenge, sizeof(challenge), cptr, 8 * sizeof(c), 9))
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
|
|
/*
|
|
* 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;
|
|
size_t i;
|
|
|
|
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);
|
|
|
|
|
|
CPRINTF("generated challenge:\n\n");
|
|
for (i = 0; i < *buf_size; i++)
|
|
CPRINTF("%c", ((uint8_t *)buf)[i]);
|
|
CPRINTF("\n\n");
|
|
|
|
#ifdef CR50_DEV
|
|
|
|
CPRINTF("expected authcode: ");
|
|
for (i = 0; i < RMA_AUTHCODE_CHARS; i++)
|
|
CPRINTF("%c", authcode[i]);
|
|
CPRINTF("\n");
|
|
#endif
|
|
return VENDOR_RC_SUCCESS;
|
|
}
|
|
/*
|
|
* 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;
|
|
enable_ccd_factory_mode(0);
|
|
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;
|
|
}
|
|
|
|
rv = shared_mem_acquire(RMA_CMD_BUF_SIZE, (char **)&tpmh);
|
|
if (rv != EC_SUCCESS)
|
|
return rv;
|
|
|
|
/* Common fields of the RMA AUTH challenge/response vendor command. */
|
|
tpmh->tag = htobe16(0x8001); /* TPM_ST_NO_SESSIONS */
|
|
tpmh->command_code = htobe32(TPM_CC_VENDOR_BIT_MASK);
|
|
tpmh->subcommand_code = htobe16(VENDOR_CC_RMA_CHALLENGE_RESPONSE);
|
|
|
|
if (argc == 2) {
|
|
/*
|
|
* The user entered a value, must be the auth code, build and
|
|
* send vendor command to check it.
|
|
*/
|
|
const char *authcode = argv[1];
|
|
|
|
if (strlen(authcode) != RMA_AUTHCODE_CHARS) {
|
|
ccprintf("Wrong auth code size.\n");
|
|
return EC_ERROR_PARAM1;
|
|
}
|
|
|
|
tpmh->size = htobe32(sizeof(struct tpm_cmd_header) +
|
|
RMA_AUTHCODE_CHARS);
|
|
|
|
memcpy(tpmh + 1, authcode, RMA_AUTHCODE_CHARS);
|
|
|
|
tpm_alt_extension(tpmh, RMA_CMD_BUF_SIZE);
|
|
|
|
if (tpmh->command_code) {
|
|
ccprintf("Auth code does not match.\n");
|
|
return EC_ERROR_PARAM1;
|
|
}
|
|
ccprintf("Auth code match, reboot might be coming!\n");
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/* Prepare and send the request to get RMA auth challenge. */
|
|
tpmh->size = htobe32(sizeof(struct tpm_cmd_header));
|
|
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;
|
|
}
|
|
|
|
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
|