215 lines
5.9 KiB
C
215 lines
5.9 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.
|
||
|
*
|
||
|
* Test RMA auth challenge/response
|
||
|
*/
|
||
|
|
||
|
#include <endian.h>
|
||
|
#include <stdio.h>
|
||
|
#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); }
|
||
|
|
||
|
/*
|
||
|
* rma_try_authcode expects a buffer that is at least RMA_AUTHCODE_CHARS long,
|
||
|
* so copy the input string to a buffer before calling the function.
|
||
|
*/
|
||
|
static int rma_try_authcode_pad(const char *code)
|
||
|
{
|
||
|
char authcode[RMA_AUTHCODE_BUF_SIZE];
|
||
|
|
||
|
memset(authcode, 0, sizeof(authcode));
|
||
|
strncpy(authcode, code, sizeof(authcode));
|
||
|
|
||
|
return rma_try_authcode(authcode);
|
||
|
}
|
||
|
|
||
|
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_pad("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_pad("Bad") == EC_ERROR_INVAL);
|
||
|
TEST_ASSERT(strlen(rma_get_challenge()) == RMA_CHALLENGE_CHARS);
|
||
|
TEST_ASSERT(rma_try_authcode_pad("BadCodeZ") == EC_ERROR_INVAL);
|
||
|
TEST_ASSERT(strlen(rma_get_challenge()) == RMA_CHALLENGE_CHARS);
|
||
|
TEST_ASSERT(rma_try_authcode_pad("BadLongCode") == EC_ERROR_INVAL);
|
||
|
/* Out of tries now */
|
||
|
TEST_ASSERT(strlen(rma_get_challenge()) == 0);
|
||
|
TEST_ASSERT(rma_try_authcode_pad("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();
|
||
|
}
|