mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-09 00:51:29 +00:00
The new vendor command operates in two modes: when received with a
zero size payload, it triggers the Cr50 to generate a new RMA
authentication challenge and the expected authentication code value.
When receive with the payload, it compares the received payload with
the pre-calculate authentication code, and returns to the host the
comparison result (passed/not passed).
A care is taken not to accept payload until at least there is a valid
calculated auth code present (to avoid reporting a match on a payload
of all zeros).
Test config needed to be modified to allow compiling of the ccprintf
wrapper.
BRANCH=cr50
BUG=b:37952913
TEST=with the rest of the patches applied observed expected behavior
of generating challenge/response and verifying the auth code.
Change-Id: I30638b0ceef68830565f222dd1f4af17cfc8d7ef
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/690992
276 lines
7.0 KiB
C
276 lines
7.0 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 "chip/g/board_id.h"
|
|
#include "console.h"
|
|
#include "curve25519.h"
|
|
#include "extension.h"
|
|
#include "rma_auth.h"
|
|
#include "system.h"
|
|
#include "timer.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;
|
|
|
|
/* The server wants this as a string, not a number. */
|
|
bid.type = htobe32(bid.type);
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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("%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");
|
|
|
|
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;
|
|
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);
|