/* * 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. */ #define LOG_TAG "rpmb_mock" #include "rpmb_protocol.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* verbose is an int for getopt */ static 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 #define MAX_WRITE_COUNTER (0xffffffff) struct rpmb_data_header { uint32_t write_counter; uint16_t max_block; uint8_t pad1; uint8_t key_programmed; struct rpmb_key key; uint8_t pad[512 - 4 - 2 - 1 - 1 - sizeof(struct rpmb_key)]; }; #define MAX_PACKET_COUNT (8) struct rpmb_dev_state { struct rpmb_data_header header; struct rpmb_packet cmd[MAX_PACKET_COUNT]; struct rpmb_packet res[MAX_PACKET_COUNT]; uint16_t cmd_count; uint16_t res_count; int data_fd; }; /* 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) { ALOGE("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) { ALOGE("HMAC_Update failed\n"); goto err; } } hmac_ret = HMAC_Final(hmac_ctx, mac->byte, &md_len); if (md_len != sizeof(mac->byte)) { ALOGE("bad md_len %d != %zd\n", md_len, sizeof(mac->byte)); exit(1); } if (!hmac_ret) { ALOGE("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) { ALOGE("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) { ALOGE("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)) { ALOGE("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) { 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->header.write_counter == MAX_WRITE_COUNTER) { if (verbose) { ALOGE("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) { ALOGE("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) { ALOGE("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) { ALOGE("rpmb_dev: Failed to write rpmb data file: %d, %s\n", ret, strerror(errno)); return RPMB_RES_WRITE_FAILURE; } } s->header.write_counter++; ret = lseek(s->data_fd, 0, SEEK_SET); if (ret) { ALOGE("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)) { ALOGE("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; 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) { ALOGE("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, }, }; #define countof(arr) (sizeof(arr) / sizeof(arr[0])) static 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) { ALOGE("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) { ALOGE("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) { ALOGE("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) { ALOGE("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) { ALOGE("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) { ALOGE("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) { ALOGE("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) { ALOGE("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) { ALOGE("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) { ALOGE("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); } } /* * Receives data until one of the following is true: * - The buffer is full (return will be len) * - The connection closed (return > 0, < len) * - An error occurred (return will be the negative error code from recv) */ ssize_t recv_until(int sock, void* dest_in, size_t len) { size_t bytes_recvd = 0; char* dest = dest_in; while (bytes_recvd < len) { ssize_t ret = recv(sock, dest, len - bytes_recvd, 0); if (ret < 0) { return ret; } dest += ret; bytes_recvd += ret; if (ret == 0) { break; } } return bytes_recvd; } /* * Handles an incoming connection to the rpmb daemon. * Returns 0 if the client disconnects without violating the protocol. * Returns a negative value if we terminated the connection abnormally. * * Arguments: * conn_sock - an fd to send/recv on * s - an initialized rpmb device */ int handle_conn(struct rpmb_dev_state* s, int conn_sock) { int ret; while (true) { memset(s->res, 0, sizeof(s->res)); ret = recv_until(conn_sock, &s->res_count, sizeof(s->res_count)); /* * Disconnected while not in the middle of anything. */ if (ret <= 0) { return 0; } if (s->res_count > MAX_PACKET_COUNT) { ALOGE("rpmb_dev: Receive count too large: %d\n", s->res_count); return -1; } if (s->res_count <= 0) { ALOGE("rpmb_dev: Receive count too small: %d\n", s->res_count); return -1; } ret = recv_until(conn_sock, &s->cmd_count, sizeof(s->cmd_count)); if (ret != sizeof(s->cmd_count)) { ALOGE("rpmb_dev: Failed to read cmd_count"); return -1; } if (s->cmd_count == 0) { ALOGE("rpmb_dev: Must contain at least one command\n"); return -1; } if (s->cmd_count > MAX_PACKET_COUNT) { ALOGE("rpmb_dev: Command count is too large\n"); return -1; } size_t cmd_size = s->cmd_count * sizeof(s->cmd[0]); ret = recv_until(conn_sock, s->cmd, cmd_size); if (ret != (int)cmd_size) { ALOGE("rpmb_dev: Failed to read command: " "cmd_size: %zu ret: %d, %s\n", cmd_size, ret, strerror(errno)); return -1; } rpmb_dev_process_cmd(s); size_t resp_size = sizeof(s->res[0]) * s->res_count; ret = send(conn_sock, s->res, resp_size, 0); if (ret != (int)resp_size) { ALOGE("rpmb_dev: Failed to send response: %d, %s\n", ret, strerror(errno)); return -1; } } } void usage(const char* argv0) { fprintf(stderr, "Usage: %s [-d|--dev] [--sock] \n", argv0); fprintf(stderr, "or: %s [-d|--dev] [--size ] [--key key]\n", argv0); } int main(int argc, char** argv) { struct rpmb_dev_state s; int ret; int cmdres_sock; struct sockaddr_un cmdres_sockaddr; const char* data_file_name = NULL; const char* socket_path = NULL; int open_flags; int init = false; struct option long_options[] = {{"size", required_argument, 0, 0}, {"key", required_argument, 0, 0}, {"sock", required_argument, 0, 0}, {"dev", required_argument, 0, 'd'}, {"init", no_argument, &init, true}, {"verbose", no_argument, &verbose, true}, {0, 0, 0, 0}}; memset(&s.header, 0, sizeof(s.header)); while (1) { int c; int option_index = 0; c = getopt_long(argc, argv, "d:", long_options, &option_index); if (c == -1) { break; } switch (c) { /* long args */ case 0: switch (option_index) { /* size */ case 0: s.header.max_block = atoi(optarg) - 1; break; /* key */ case 1: for (size_t i = 0; i < sizeof(s.header.key.byte); i++) { if (!optarg) { break; } s.header.key.byte[i] = strtol(optarg, &optarg, 16); s.header.key_programmed = 1; } break; /* sock */ case 2: socket_path = optarg; break; } break; /* dev */ case 'd': data_file_name = optarg; break; default: usage(argv[0]); return EXIT_FAILURE; } } /* * We always need a data file, and at exactly one of --init or --sock * must be specified. */ if (!data_file_name || (!init == !socket_path)) { usage(argv[0]); return EXIT_FAILURE; } /* * If the file is already initialized, exit early. */ if (init && !access(data_file_name, F_OK)) { return EXIT_SUCCESS; } open_flags = O_RDWR | O_SYNC; if (init) { open_flags |= O_CREAT | O_TRUNC; } s.data_fd = open(data_file_name, open_flags, S_IWUSR | S_IRUSR); if (s.data_fd < 0) { ALOGE("rpmb_dev: Failed to open rpmb data file, %s: %s\n", data_file_name, strerror(errno)); return EXIT_FAILURE; } if (init) { /* Create new rpmb data file */ if (s.header.max_block == 0) { s.header.max_block = 512 - 1; } ret = write(s.data_fd, &s.header, sizeof(s.header)); if (ret != sizeof(s.header)) { ALOGE("rpmb_dev: Failed to write rpmb data file: %d, %s\n", ret, strerror(errno)); return EXIT_FAILURE; } return EXIT_SUCCESS; } ret = read(s.data_fd, &s.header, sizeof(s.header)); if (ret != sizeof(s.header)) { ALOGE("rpmb_dev: Failed to read rpmb data file: %d, %s\n", ret, strerror(errno)); return EXIT_FAILURE; } cmdres_sock = socket(AF_UNIX, SOCK_STREAM, 0); if (cmdres_sock < 0) { ALOGE("rpmb_dev: Failed to create command/response socket: %s\n", strerror(errno)); return EXIT_FAILURE; } cmdres_sockaddr.sun_family = AF_UNIX; strncpy(cmdres_sockaddr.sun_path, socket_path, sizeof(cmdres_sockaddr.sun_path)); ret = bind(cmdres_sock, (struct sockaddr*)&cmdres_sockaddr, sizeof(struct sockaddr_un)); if (ret < 0) { ALOGE("rpmb_dev: Failed to bind command/response socket: %s: %s\n", socket_path, strerror(errno)); return EXIT_FAILURE; } ret = listen(cmdres_sock, 1); if (ret < 0) { ALOGE("rpmb_dev: Failed to listen on command/response socket: %s\n", strerror(errno)); return EXIT_FAILURE; } while (true) { int conn_sock = accept(cmdres_sock, NULL, NULL); if (conn_sock < 0) { ALOGE("rpmb_dev: Could not accept connection: %s\n", strerror(errno)); return EXIT_FAILURE; } ret = handle_conn(&s, conn_sock); close(conn_sock); if (ret) { ALOGE("rpmb_dev: Connection terminated: %d", ret); } } }