blob: 24c30bbe527a0a4a725aa7694d9408dfab0b74d9 [file] [log] [blame]
/* 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