blob: 7a63e796819ea40a34f02d45eca00e4dd60da7af [file] [log] [blame] [edit]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "rpmb_dev.h"
#include <assert.h>
#include <errno.h>
#include <lk/compiler.h>
#include <openssl/hmac.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
/* verbose is an int for getopt */
int verbose = false;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
HMAC_CTX* HMAC_CTX_new(void) {
HMAC_CTX* ctx = malloc(sizeof(*ctx));
if (ctx != NULL) {
HMAC_CTX_init(ctx);
}
return ctx;
}
void HMAC_CTX_free(HMAC_CTX* ctx) {
if (ctx != NULL) {
HMAC_CTX_cleanup(ctx);
free(ctx);
}
}
#endif
/* TODO: move to common location */
static int rpmb_mac(struct rpmb_key key,
struct rpmb_packet* packet,
size_t packet_count,
struct rpmb_key* mac) {
size_t i;
int hmac_ret;
unsigned int md_len;
HMAC_CTX* hmac_ctx;
hmac_ctx = HMAC_CTX_new();
hmac_ret = HMAC_Init_ex(hmac_ctx, &key, sizeof(key), EVP_sha256(), NULL);
if (!hmac_ret) {
fprintf(stderr, "HMAC_Init_ex failed\n");
goto err;
}
for (i = 0; i < packet_count; i++) {
hmac_ret = HMAC_Update(hmac_ctx, packet[i].data, 284);
if (!hmac_ret) {
fprintf(stderr, "HMAC_Update failed\n");
goto err;
}
}
hmac_ret = HMAC_Final(hmac_ctx, mac->byte, &md_len);
if (md_len != sizeof(mac->byte)) {
fprintf(stderr, "bad md_len %d != %zd\n", md_len, sizeof(mac->byte));
exit(1);
}
if (!hmac_ret) {
fprintf(stderr, "HMAC_Final failed\n");
goto err;
}
err:
HMAC_CTX_free(hmac_ctx);
return hmac_ret ? 0 : -1;
}
static int rpmb_file_seek(struct rpmb_dev_state* s, uint16_t addr) {
int ret;
int pos = addr * RPMB_PACKET_DATA_SIZE + sizeof(s->header);
ret = lseek(s->data_fd, pos, SEEK_SET);
if (ret != pos) {
fprintf(stderr, "rpmb_dev: seek to %d failed, got %d\n", pos, ret);
return -1;
}
return 0;
}
static uint16_t rpmb_dev_program_key(struct rpmb_dev_state* s) {
int ret;
if (s->header.key_programmed) {
return RPMB_RES_WRITE_FAILURE;
}
s->header.key = s->cmd[0].key_mac;
s->header.key_programmed = 1;
ret = lseek(s->data_fd, 0, SEEK_SET);
if (ret) {
fprintf(stderr, "rpmb_dev: Failed to seek rpmb data file\n");
return RPMB_RES_WRITE_FAILURE;
}
ret = write(s->data_fd, &s->header, sizeof(s->header));
if (ret != sizeof(s->header)) {
fprintf(stderr, "rpmb_dev: Failed to write rpmb key: %d, %s\n", ret,
strerror(errno));
return RPMB_RES_WRITE_FAILURE;
}
return RPMB_RES_OK;
}
static uint16_t rpmb_dev_get_counter(struct rpmb_dev_state* s) {
if (s->fail_next_get_counters > 0) {
if (verbose) {
fprintf(stderr,
"rpmb_dev: failing to get RPMB counter as requested by debug state\n");
}
s->fail_next_get_counters--;
return RPMB_RES_COUNT_FAILURE;
}
s->res[0].write_counter = rpmb_u32(s->header.write_counter);
return RPMB_RES_OK;
}
static uint16_t rpmb_dev_data_write(struct rpmb_dev_state* s) {
uint16_t addr = rpmb_get_u16(s->cmd[0].address);
uint16_t block_count = s->cmd_count;
uint32_t write_counter;
int ret;
if (s->fail_next_writes > 0 && !s->commit_failed_writes) {
if (verbose) {
fprintf(stderr,
"rpmb_dev: failing write as requested by debug state\n");
}
s->fail_next_writes--;
return RPMB_RES_WRITE_FAILURE;
}
if (s->header.write_counter == MAX_WRITE_COUNTER) {
if (verbose) {
fprintf(stderr, "rpmb_dev: Write counter expired\n");
}
return RPMB_RES_WRITE_FAILURE;
}
write_counter = rpmb_get_u32(s->cmd[0].write_counter);
if (s->header.write_counter != write_counter) {
if (verbose) {
fprintf(stderr,
"rpmb_dev: Invalid write counter %u. Expected: %u\n",
write_counter, s->header.write_counter);
}
return RPMB_RES_COUNT_FAILURE;
}
ret = rpmb_file_seek(s, addr);
if (ret) {
fprintf(stderr, "rpmb_dev: Failed to seek rpmb data file\n");
return RPMB_RES_WRITE_FAILURE;
}
for (int i = 0; i < block_count; i++) {
ret = write(s->data_fd, s->cmd[i].data, RPMB_PACKET_DATA_SIZE);
if (ret != RPMB_PACKET_DATA_SIZE) {
fprintf(stderr,
"rpmb_dev: Failed to write rpmb data file: %d, %s\n", ret,
strerror(errno));
return RPMB_RES_WRITE_FAILURE;
}
}
s->header.write_counter++;
if (s->fail_next_writes > 0) {
if (verbose) {
fprintf(stderr,
"rpmb_dev: Failing write after commit as requested by debug state\n");
}
s->fail_next_writes--;
return RPMB_RES_WRITE_FAILURE;
}
ret = lseek(s->data_fd, 0, SEEK_SET);
if (ret) {
fprintf(stderr, "rpmb_dev: Failed to seek rpmb data file\n");
return RPMB_RES_WRITE_FAILURE;
}
ret = write(s->data_fd, &s->header.write_counter,
sizeof(s->header.write_counter));
if (ret != sizeof(s->header.write_counter)) {
fprintf(stderr,
"rpmb_dev: Failed to write rpmb write counter: %d, %s\n", ret,
strerror(errno));
return RPMB_RES_WRITE_FAILURE;
}
s->res[0].write_counter = rpmb_u32(s->header.write_counter);
return RPMB_RES_OK;
}
static uint16_t rpmb_dev_data_read(struct rpmb_dev_state* s) {
uint16_t addr;
uint16_t block_count;
int ret;
if (s->fail_next_reads > 0) {
if (verbose) {
fprintf(stderr,
"rpmb_dev: failing read as requested by debug state\n");
}
s->fail_next_reads--;
return RPMB_RES_READ_FAILURE;
}
addr = rpmb_get_u16(s->cmd[0].address);
block_count = s->res_count;
rpmb_file_seek(s, addr);
for (int i = 0; i < block_count; i++) {
ret = read(s->data_fd, s->res[i].data, RPMB_PACKET_DATA_SIZE);
if (ret != 0 && ret != RPMB_PACKET_DATA_SIZE) {
fprintf(stderr, "rpmb_dev: Failed to read rpmb data file: %d, %s\n",
ret, strerror(errno));
return RPMB_RES_READ_FAILURE;
}
}
return RPMB_RES_OK;
}
struct rpmb_dev_cmd {
uint16_t (*func)(struct rpmb_dev_state* s);
uint16_t resp;
bool key_mac_is_key;
bool check_mac;
bool check_result_read;
bool check_key_programmed;
bool check_addr;
bool multi_packet_cmd;
bool multi_packet_res;
bool res_mac;
};
static struct rpmb_dev_cmd rpmb_dev_cmd_table[] = {
[RPMB_REQ_PROGRAM_KEY] =
{
.func = rpmb_dev_program_key,
.resp = RPMB_RESP_PROGRAM_KEY,
.key_mac_is_key = true,
.check_result_read = true,
},
[RPMB_REQ_GET_COUNTER] =
{
.func = rpmb_dev_get_counter,
.resp = RPMB_RESP_GET_COUNTER,
.check_key_programmed = true,
.res_mac = true,
},
[RPMB_REQ_DATA_WRITE] =
{
.func = rpmb_dev_data_write,
.resp = RPMB_RESP_DATA_WRITE,
.check_mac = true,
.check_result_read = true,
.check_key_programmed = true,
.check_addr = true,
.multi_packet_cmd = true,
.res_mac = true,
},
[RPMB_REQ_DATA_READ] =
{
.func = rpmb_dev_data_read,
.resp = RPMB_RESP_DATA_READ,
.check_key_programmed = true,
.check_addr = true,
.multi_packet_res = true,
.res_mac = true,
},
};
void rpmb_dev_process_cmd(struct rpmb_dev_state* s) {
assert(s->cmd_count > 0);
assert(s->res_count > 0);
uint16_t req_resp = rpmb_get_u16(s->cmd[0].req_resp);
uint16_t addr = rpmb_get_u16(s->cmd[0].address);
uint16_t sub_req;
uint16_t cmd_index = req_resp < countof(rpmb_dev_cmd_table) ? req_resp : 0;
struct rpmb_dev_cmd* cmd = &rpmb_dev_cmd_table[cmd_index];
uint16_t result = RPMB_RES_GENERAL_FAILURE;
struct rpmb_key mac;
uint16_t block_count = 0;
if (cmd->check_result_read) {
sub_req = rpmb_get_u16(s->cmd[s->cmd_count - 1].req_resp);
if (sub_req != RPMB_REQ_RESULT_READ) {
if (verbose) {
fprintf(stderr,
"rpmb_dev: Request %d, missing result read request, got %d, cmd_count %d\n",
req_resp, sub_req, s->cmd_count);
}
goto err;
}
assert(s->cmd_count > 1);
s->cmd_count--;
}
if (cmd->check_mac) {
if (rpmb_mac(s->header.key, s->cmd, s->cmd_count, &mac) != 0) {
fprintf(stderr, "rpmb_dev: failed to caclulate mac\n");
goto err;
}
} else if (cmd->key_mac_is_key) {
mac = s->cmd[s->cmd_count - 1].key_mac;
} else {
memset(mac.byte, 0, sizeof(mac.byte));
}
if (memcmp(&mac, s->cmd[s->cmd_count - 1].key_mac.byte, sizeof(mac))) {
if (verbose) {
fprintf(stderr, "rpmb_dev: Request %d, invalid MAC, cmd_count %d\n",
req_resp, s->cmd_count);
}
if (cmd->check_mac) {
result = RPMB_RES_AUTH_FAILURE;
}
goto err;
}
if (cmd->multi_packet_cmd) {
block_count = s->cmd_count;
}
if (cmd->multi_packet_res) {
block_count = s->res_count;
}
if (cmd->check_addr && (addr + block_count > s->header.max_block + 1)) {
if (verbose) {
fprintf(stderr,
"rpmb_dev: Request %d, invalid addr: 0x%x count 0x%x, Out of bounds. Max addr 0x%x\n",
req_resp, addr, block_count, s->header.max_block + 1);
}
result = RPMB_RES_ADDR_FAILURE;
goto err;
}
if (!cmd->check_addr && addr) {
if (verbose) {
fprintf(stderr, "rpmb_dev: Request %d, invalid addr: 0x%x != 0\n",
req_resp, addr);
}
goto err;
}
for (int i = 1; i < s->cmd_count; i++) {
sub_req = rpmb_get_u16(s->cmd[i].req_resp);
if (sub_req != req_resp) {
if (verbose) {
fprintf(stderr,
"rpmb_dev: Request %d, sub-request mismatch, %d, at %d\n",
req_resp, i, sub_req);
}
goto err;
}
}
if (!cmd->multi_packet_cmd && s->cmd_count != 1) {
if (verbose) {
fprintf(stderr,
"rpmb_dev: Request %d, bad cmd count %d, expected 1\n",
req_resp, s->cmd_count);
}
goto err;
}
if (!cmd->multi_packet_res && s->res_count != 1) {
if (verbose) {
fprintf(stderr,
"rpmb_dev: Request %d, bad res count %d, expected 1\n",
req_resp, s->res_count);
}
goto err;
}
if (cmd->check_key_programmed && !s->header.key_programmed) {
if (verbose) {
fprintf(stderr, "rpmb_dev: Request %d, key is not programmed\n",
req_resp);
}
s->res[0].result = rpmb_u16(RPMB_RES_NO_AUTH_KEY);
return;
}
if (!cmd->func) {
if (verbose) {
fprintf(stderr, "rpmb_dev: Unsupported request: %d\n", req_resp);
}
goto err;
}
result = cmd->func(s);
err:
if (s->header.write_counter == MAX_WRITE_COUNTER) {
result |= RPMB_RES_WRITE_COUNTER_EXPIRED;
}
for (int i = 0; i < s->res_count; i++) {
s->res[i].nonce = s->cmd[0].nonce;
s->res[i].address = rpmb_u16(addr);
s->res[i].block_count = rpmb_u16(block_count);
s->res[i].result = rpmb_u16(result);
s->res[i].req_resp = rpmb_u16(cmd->resp);
}
if (cmd->res_mac) {
rpmb_mac(s->header.key, s->res, s->res_count,
&s->res[s->res_count - 1].key_mac);
}
}