| /* 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); } |
| |
| 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("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("Bad") == EC_ERROR_INVAL); |
| TEST_ASSERT(strlen(rma_get_challenge()) == RMA_CHALLENGE_CHARS); |
| TEST_ASSERT(rma_try_authcode("BadCodeZ") == EC_ERROR_INVAL); |
| TEST_ASSERT(strlen(rma_get_challenge()) == RMA_CHALLENGE_CHARS); |
| TEST_ASSERT(rma_try_authcode("BadLongCode") == EC_ERROR_INVAL); |
| /* Out of tries now */ |
| TEST_ASSERT(strlen(rma_get_challenge()) == 0); |
| TEST_ASSERT(rma_try_authcode("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(); |
| } |