1075 lines
28 KiB
C
1075 lines
28 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2020 Micron Technology Inc. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
* nvme-rpmb.c - Implementation of NVMe RPMB support commands in Nvme
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <sys/socket.h>
|
|
#include <linux/if_alg.h>
|
|
#include <linux/socket.h>
|
|
#include <limits.h>
|
|
|
|
#include "common.h"
|
|
#include "nvme.h"
|
|
#include "libnvme.h"
|
|
#include "nvme-print.h"
|
|
|
|
#define CREATE_CMD
|
|
|
|
|
|
#ifndef AF_ALG
|
|
#define AF_ALG 38
|
|
#endif
|
|
#ifndef SOL_ALG
|
|
#define SOL_ALG 279
|
|
#endif
|
|
|
|
#define HMAC_SHA256_ALGO_NAME "hmac(sha256)"
|
|
#define MD5_HASH_ALGO_NAME "md5"
|
|
#define HMAC_SHA256_HASH_SIZE 32
|
|
#define MD5_HASH_HASH_SIZE 16
|
|
|
|
/*
|
|
* Utility function to create hash value of given data (with given key) using
|
|
* given hash algorithm; this function uses kernel crypto services
|
|
*/
|
|
unsigned char *create_hash(const char *algo,
|
|
int hash_size,
|
|
unsigned char *data,
|
|
int datalen,
|
|
unsigned char *key,
|
|
int keylen)
|
|
{
|
|
int error, infd, outfd = -1;
|
|
unsigned char *hash = NULL;
|
|
struct sockaddr_alg provider_sa = {
|
|
.salg_family = AF_ALG,
|
|
.salg_type = "hash",
|
|
.salg_name = { 0 }
|
|
};
|
|
|
|
/* copy algorithm name */
|
|
if (strlen(algo) > sizeof(provider_sa.salg_name)) {
|
|
fprintf(stderr, "%s: algorithm name overflow", __func__);
|
|
return hash;
|
|
}
|
|
memcpy(provider_sa.salg_name, algo, strlen(algo));
|
|
|
|
/* open netlink socket connection to algorigm provider and bind */
|
|
infd = socket(AF_ALG, SOCK_SEQPACKET, 0);
|
|
if (infd < 0) {
|
|
perror("socket");
|
|
return hash;
|
|
}
|
|
error = bind(infd, (struct sockaddr *)&provider_sa, sizeof(provider_sa));
|
|
if (error < 0) {
|
|
perror("bind");
|
|
goto out_close_infd;
|
|
}
|
|
|
|
/* if algorithm requires key, set it first - empty keys not accepted !*/
|
|
if (key != NULL && keylen > 0) {
|
|
error = setsockopt(infd, SOL_ALG, ALG_SET_KEY, key, keylen);
|
|
if (error < 0) {
|
|
perror("setsockopt");
|
|
goto out_close_infd;
|
|
}
|
|
}
|
|
|
|
/* now send data to hash */
|
|
outfd = accept(infd, NULL, 0);
|
|
if (outfd < 0) {
|
|
perror("accept");
|
|
goto out_close_infd;
|
|
}
|
|
error = send(outfd, data, datalen, 0);
|
|
if (error < 0) {
|
|
perror("send");
|
|
goto out_close_outfd;
|
|
}
|
|
|
|
/* read computed hash */
|
|
hash = (unsigned char *)calloc(hash_size, 1);
|
|
if (hash == NULL) {
|
|
perror("calloc");
|
|
goto out_close_outfd;
|
|
}
|
|
|
|
error = read(outfd, hash, hash_size);
|
|
if (error != hash_size) {
|
|
perror("read");
|
|
free(hash);
|
|
hash = NULL;
|
|
}
|
|
out_close_outfd:
|
|
close(outfd);
|
|
out_close_infd:
|
|
close(infd);
|
|
|
|
return hash;
|
|
}
|
|
|
|
/* Function that computes hmac-sha256 hash of given data and key pair. Returns
|
|
* byte stream (non-null terminated) upon success, NULL otherwise.
|
|
*/
|
|
unsigned char *
|
|
hmac_sha256(unsigned char *data, int datalen, unsigned char *key, int keylen)
|
|
{
|
|
return create_hash(HMAC_SHA256_ALGO_NAME,
|
|
HMAC_SHA256_HASH_SIZE,
|
|
data,
|
|
datalen,
|
|
key,
|
|
keylen);
|
|
}
|
|
|
|
/* Function that computes md5 of given buffer - md5 hash is used as nonce
|
|
* Returns byte stream (non-null terminated) upon success, NULL otherwise.
|
|
*/
|
|
unsigned char *
|
|
rpmb_md5(unsigned char *data, int datalen)
|
|
{
|
|
return create_hash(MD5_HASH_ALGO_NAME,
|
|
MD5_HASH_HASH_SIZE,
|
|
data,
|
|
datalen,
|
|
NULL,
|
|
0);
|
|
}
|
|
|
|
/* Read data from given file into buffer and return its length */
|
|
static int read_file(const char *file, unsigned char **data, unsigned int *len)
|
|
{
|
|
struct stat sb;
|
|
size_t size;
|
|
unsigned char *buf = NULL;
|
|
int fd;
|
|
int err = -EINVAL;
|
|
|
|
if (file == NULL) return err;
|
|
|
|
if ((fd = open(file, O_RDONLY)) < 0) {
|
|
fprintf(stderr, "Failed to open %s: %s\n", file, strerror(errno));
|
|
return fd;
|
|
}
|
|
|
|
err = fstat(fd, &sb);
|
|
if (err < 0) {
|
|
perror("fstat");
|
|
goto out;
|
|
}
|
|
|
|
size = sb.st_size;
|
|
if (posix_memalign((void **)&buf, getpagesize(), size)) {
|
|
fprintf(stderr, "No memory for reading file :%s\n", file);
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
err = read(fd, buf, size);
|
|
if (err < 0) {
|
|
err = -errno;
|
|
fprintf(stderr, "Failed to read data from file"
|
|
" %s with %s\n", file, strerror(errno));
|
|
free(buf);
|
|
goto out;
|
|
}
|
|
*data = buf;
|
|
*len = err;
|
|
err = 0;
|
|
out:
|
|
close(fd);
|
|
return err;
|
|
}
|
|
|
|
/* Write given buffer data to specified file */
|
|
static void write_file(unsigned char *data, size_t len, const char *dir,
|
|
const char *file, const char *msg)
|
|
{
|
|
char temp_folder[PATH_MAX] = { 0 };
|
|
_cleanup_file_ FILE *fp = NULL;
|
|
|
|
if (dir != NULL)
|
|
sprintf(temp_folder, "%s/%s", dir, file);
|
|
else
|
|
sprintf(temp_folder, "./%s", file);
|
|
|
|
if ((fp = fopen(temp_folder, "ab+")) != NULL) {
|
|
if (fwrite(data, 1, len, fp) != len) {
|
|
fprintf(stderr, "Failed to write %s data to %s\n",
|
|
msg ? msg : "", temp_folder);
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Failed to open %s file to write %s\n",
|
|
temp_folder, msg ? msg : "");
|
|
}
|
|
}
|
|
|
|
/* Various definitions used in RPMB related support */
|
|
enum rpmb_request_type {
|
|
RPMB_REQ_AUTH_KEY_PROGRAM = 0x0001,
|
|
RPMB_REQ_READ_WRITE_CNTR = 0x0002,
|
|
RPMB_REQ_AUTH_DATA_WRITE = 0x0003,
|
|
RPMB_REQ_AUTH_DATA_READ = 0x0004,
|
|
RPMB_REQ_READ_RESULT = 0x0005,
|
|
RPMB_REQ_AUTH_DCB_WRITE = 0x0006,
|
|
RPMB_REQ_AUTH_DCB_READ = 0x0007
|
|
};
|
|
|
|
enum rpmb_response_type {
|
|
RPMB_RSP_AUTH_KEY_PROGRAM = (RPMB_REQ_AUTH_KEY_PROGRAM << 8),
|
|
RPMB_RSP_READ_WRITE_CNTR = (RPMB_REQ_READ_WRITE_CNTR << 8),
|
|
RPMB_RSP_AUTH_DATA_WRITE = (RPMB_REQ_AUTH_DATA_WRITE << 8),
|
|
RPMB_RSP_AUTH_DATA_READ = (RPMB_REQ_AUTH_DATA_READ << 8),
|
|
RPMB_RSP_READ_RESULT = (RPMB_REQ_READ_RESULT << 8),
|
|
RPMB_RSP_AUTH_DCB_WRITE = (RPMB_REQ_AUTH_DCB_WRITE << 8),
|
|
RPMB_RSP_AUTH_DCB_READ = (RPMB_REQ_AUTH_DCB_READ << 8)
|
|
};
|
|
|
|
/* RPMB data frame structure */
|
|
#pragma pack(1)
|
|
struct rpmb_data_frame_t {
|
|
unsigned char pad[191];
|
|
unsigned char mac[32];
|
|
unsigned char target; /* 0-6, should match with NSSF with SS, SR */
|
|
unsigned char nonce[16];
|
|
unsigned int write_counter;
|
|
unsigned int address;
|
|
unsigned int sectors;
|
|
unsigned short result;
|
|
unsigned short type; /* req or response */
|
|
unsigned char data[0]; /* in sector count times */
|
|
};
|
|
#pragma pack()
|
|
|
|
struct rpmb_config_block_t {
|
|
unsigned char bp_enable;
|
|
unsigned char bp_lock;
|
|
unsigned char rsvd[510];
|
|
};
|
|
|
|
#define RPMB_DATA_FRAME_SIZE 256
|
|
#define RPMB_NVME_SECP 0xEA
|
|
#define RPMB_NVME_SPSP 0x0001
|
|
|
|
static int send_rpmb_req(int fd, unsigned char tgt, int size,
|
|
struct rpmb_data_frame_t *req)
|
|
{
|
|
struct nvme_security_send_args args = {
|
|
.args_size = sizeof(args),
|
|
.fd = fd,
|
|
.nsid = 0,
|
|
.nssf = tgt,
|
|
.spsp0 = RPMB_NVME_SPSP,
|
|
.spsp1 = 0,
|
|
.secp = RPMB_NVME_SECP,
|
|
.tl = 0,
|
|
.data_len = size,
|
|
.data = (void *)req,
|
|
.timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
|
|
.result = NULL,
|
|
};
|
|
|
|
return nvme_security_send(&args);
|
|
}
|
|
|
|
static int recv_rpmb_rsp(int fd, int tgt, int size,
|
|
struct rpmb_data_frame_t *rsp)
|
|
{
|
|
|
|
struct nvme_security_receive_args args = {
|
|
.args_size = sizeof(args),
|
|
.fd = fd,
|
|
.nsid = 0,
|
|
.nssf = tgt,
|
|
.spsp0 = RPMB_NVME_SPSP,
|
|
.spsp1 = 0,
|
|
.secp = RPMB_NVME_SECP,
|
|
.al = 0,
|
|
.data_len = size,
|
|
.data = (void *)rsp,
|
|
.timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
|
|
.result = NULL,
|
|
};
|
|
|
|
return nvme_security_receive(&args);
|
|
}
|
|
|
|
/* Initialize nonce value in rpmb request frame */
|
|
static void rpmb_nonce_init(struct rpmb_data_frame_t *req)
|
|
{
|
|
int num = rand();
|
|
unsigned char *hash = rpmb_md5((unsigned char *)&num, sizeof(num));
|
|
if (hash) memcpy(req->nonce, hash, sizeof(req->nonce));
|
|
}
|
|
|
|
/* Read key from a given key buffer or key file */
|
|
static unsigned char *read_rpmb_key(char *keystr, char *keyfile, unsigned int *keysize)
|
|
{
|
|
unsigned char *keybuf = NULL;
|
|
int err;
|
|
|
|
if (keystr == NULL) {
|
|
if (keyfile != NULL) {
|
|
err = read_file(keyfile, &keybuf, keysize);
|
|
if (err < 0)
|
|
return NULL;
|
|
}
|
|
} else if ((keybuf = (unsigned char *)malloc(strlen(keystr))) != NULL) {
|
|
*keysize = strlen(keystr);
|
|
memcpy(keybuf, keystr, *keysize);
|
|
}
|
|
|
|
return keybuf;
|
|
}
|
|
|
|
/* Initialize RPMB request frame with given values */
|
|
static struct rpmb_data_frame_t *
|
|
rpmb_request_init(unsigned int req_size,
|
|
unsigned short type,
|
|
unsigned char target,
|
|
unsigned char nonce,
|
|
unsigned int addr,
|
|
unsigned int sectors,
|
|
unsigned char *data,
|
|
unsigned short data_offset,
|
|
unsigned int data_size)
|
|
{
|
|
struct rpmb_data_frame_t *req = NULL;
|
|
|
|
if ((req = (struct rpmb_data_frame_t *)calloc(req_size, 1)) == NULL) {
|
|
fprintf(stderr, "Memory allocation failed for request 0x%04x\n",
|
|
type);
|
|
return req;
|
|
}
|
|
|
|
req->type = type;
|
|
req->target = target;
|
|
req->address = addr;
|
|
req->sectors = sectors;
|
|
|
|
if (nonce) rpmb_nonce_init(req);
|
|
if (data) memcpy((unsigned char *)req + data_offset, data, data_size);
|
|
|
|
return req;
|
|
}
|
|
|
|
/* Process rpmb response and print appropriate error message */
|
|
static int check_rpmb_response( struct rpmb_data_frame_t *req,
|
|
struct rpmb_data_frame_t *rsp,
|
|
char *msg)
|
|
{
|
|
const char *rpmb_result_string [] = {
|
|
"Operation successful",
|
|
"General failure",
|
|
"Authentication (MAC) failure",
|
|
"Counter failure (not matching/incrementing failure)",
|
|
"Address failure (out of range or wrong alignment)",
|
|
"Write (data/counter/result) failure",
|
|
"Read (data/counter/result) failure",
|
|
"Authentication key not yet programmed",
|
|
"Invalid device configuration block",
|
|
"Unknown error"
|
|
};
|
|
|
|
/* check error status before comparing nonce and mac */
|
|
if (rsp->result != 0) {
|
|
if (rsp->type != ((req->type << 8) & 0xFF00)) {
|
|
fprintf(stderr, "%s ! non-matching response 0x%04x for"
|
|
" 0x%04x\n", msg, rsp->type, req->type);
|
|
} else if ((rsp->result & 0x80) == 0x80) {
|
|
fprintf(stderr, "%s ! Expired write-counter !\n", msg);
|
|
} else if (rsp->result) {
|
|
fprintf(stderr, "%s ! %s\n", msg,
|
|
rpmb_result_string[rsp->result & 0x7F]);
|
|
} else if (memcmp(req->nonce, rsp->nonce, 16)) {
|
|
fprintf(stderr, "%s ! non-matching nonce\n", msg);
|
|
} else if (memcmp(req->mac, rsp->mac, 32)) {
|
|
fprintf(stderr, "%s ! non-matching MAC\n", msg);
|
|
} else if ((req->write_counter + 1) != rsp->write_counter) {
|
|
fprintf(stderr, "%s ! out-of-sync write-counters\n", msg);
|
|
}
|
|
}
|
|
|
|
return (int)(rsp->result);
|
|
}
|
|
|
|
/* send an initialized rpmb request to the controller and read its response
|
|
* expected response size give in 'rsp_size'. returns response buffer upon
|
|
* successful completion (caller must free), NULL otherwise
|
|
*/
|
|
static struct rpmb_data_frame_t *
|
|
rpmb_read_request(int fd,
|
|
struct rpmb_data_frame_t *req,
|
|
int req_size,
|
|
int rsp_size)
|
|
{
|
|
struct rpmb_data_frame_t *rsp = NULL;
|
|
unsigned char msg[1024] = { 0 };
|
|
int error;
|
|
|
|
sprintf((char *)msg, "RPMB request 0x%04x to target 0x%x",
|
|
req->type, req->target);
|
|
|
|
error = send_rpmb_req(fd, req->target, req_size, req);
|
|
if (error != 0) {
|
|
fprintf(stderr, "%s failed with error = 0x%x\n",
|
|
msg, error);
|
|
goto error_out;
|
|
}
|
|
|
|
/* read the result back */
|
|
rsp = (struct rpmb_data_frame_t *)calloc(rsp_size, 1);
|
|
if (rsp == NULL) {
|
|
fprintf(stderr, "memory alloc failed for %s\n", msg);
|
|
goto error_out;
|
|
}
|
|
|
|
/* Read result of previous request */
|
|
error = recv_rpmb_rsp(fd, req->target, rsp_size, rsp);
|
|
if (error) {
|
|
fprintf(stderr, "error 0x%x receiving response for %s\n",
|
|
error, msg);
|
|
goto error_out;
|
|
}
|
|
|
|
/* validate response buffer - match target, nonce, and mac */
|
|
error = check_rpmb_response(req, rsp, (char *)msg);
|
|
if (error == 0) return rsp;
|
|
|
|
error_out:
|
|
free(rsp);
|
|
return NULL;
|
|
}
|
|
|
|
/* read current write counter value from controller */
|
|
static int rpmb_read_write_counter(int fd,
|
|
unsigned char target,
|
|
unsigned int *counter)
|
|
{
|
|
int error = -1;
|
|
int req_size = sizeof(struct rpmb_data_frame_t);
|
|
struct rpmb_data_frame_t *req = NULL;
|
|
struct rpmb_data_frame_t *rsp = NULL;
|
|
|
|
req = rpmb_request_init(req_size, RPMB_REQ_READ_WRITE_CNTR,
|
|
target, 1, 0, 0, NULL, 0, 0);
|
|
if (req == NULL) goto out;
|
|
if ((rsp = rpmb_read_request(fd, req, req_size, req_size)) == NULL) {
|
|
goto out;
|
|
}
|
|
*counter = rsp->write_counter;
|
|
error = 0;
|
|
|
|
out:
|
|
free(req);
|
|
free(rsp);
|
|
return error;
|
|
}
|
|
|
|
/* Read current device configuration block into specified buffer. It also returns
|
|
* current write counter value returned as part of response, in case of error it
|
|
* returns 0
|
|
*/
|
|
static unsigned int rpmb_read_config_block(int fd, unsigned char **config_buf)
|
|
{
|
|
int req_size = sizeof(struct rpmb_data_frame_t);
|
|
int cfg_size = sizeof(struct rpmb_config_block_t);
|
|
int rsp_size = req_size + cfg_size;
|
|
|
|
struct rpmb_data_frame_t *req = NULL;
|
|
struct rpmb_data_frame_t *rsp = NULL;
|
|
struct rpmb_config_block_t *cfg = NULL;
|
|
unsigned int retval = 0;
|
|
|
|
/* initialize request with nonce, no data on input */
|
|
req = rpmb_request_init(req_size, RPMB_REQ_AUTH_DCB_READ, 0, 1, 0, 1,
|
|
0, 0, 0);
|
|
if (!req)
|
|
return 0;
|
|
if ((rsp = rpmb_read_request(fd, req, req_size, rsp_size)) == NULL)
|
|
{
|
|
free(req);
|
|
return 0;
|
|
}
|
|
|
|
/* copy configuration data to be sent back to caller */
|
|
cfg = (struct rpmb_config_block_t *)calloc(cfg_size, 1);
|
|
if (cfg == NULL) {
|
|
fprintf(stderr, "failed to allocate RPMB config buffer\n");
|
|
goto out;
|
|
}
|
|
|
|
memcpy(cfg, rsp->data, cfg_size);
|
|
*config_buf = (unsigned char *)cfg;
|
|
cfg = NULL;
|
|
retval = rsp->write_counter;
|
|
out:
|
|
free(req);
|
|
free(rsp);
|
|
return retval;
|
|
}
|
|
|
|
|
|
static int rpmb_auth_data_read(int fd, unsigned char target,
|
|
unsigned int offset,
|
|
unsigned char **msg_buf,
|
|
int msg_size, int acc_size)
|
|
{
|
|
struct rpmb_data_frame_t *req = NULL;
|
|
struct rpmb_data_frame_t *rsp = NULL;
|
|
int req_size = sizeof(struct rpmb_data_frame_t);
|
|
int chunk_size = (acc_size < msg_size) ? acc_size : msg_size;
|
|
int xfer = chunk_size;
|
|
unsigned char *bufp = (unsigned char *)malloc(msg_size * 512);
|
|
unsigned char *tbufp = bufp;
|
|
int data_size, rsp_size;
|
|
int error = -1;
|
|
|
|
if (bufp == NULL) {
|
|
fprintf(stderr, "Failed to allocated memory for read-data req\n");
|
|
goto out;
|
|
}
|
|
|
|
while (xfer > 0) {
|
|
rsp_size = req_size + xfer * 512;
|
|
req = rpmb_request_init(req_size, RPMB_REQ_AUTH_DATA_READ,
|
|
target, 1, offset, xfer, 0, 0, 0);
|
|
if (req == NULL)
|
|
break;
|
|
if ((rsp = rpmb_read_request(fd, req, req_size, rsp_size)) == NULL)
|
|
{
|
|
fprintf(stderr, "read_request failed\n");
|
|
free(req);
|
|
break;
|
|
}
|
|
|
|
data_size = rsp->sectors * 512;
|
|
memcpy(tbufp, rsp->data, data_size);
|
|
offset += rsp->sectors;
|
|
tbufp += data_size;
|
|
if (offset + chunk_size > msg_size)
|
|
xfer = msg_size - offset;
|
|
else
|
|
xfer = chunk_size;
|
|
free(req);
|
|
free(rsp);
|
|
}
|
|
|
|
*msg_buf = bufp;
|
|
error = offset;
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
/* Implementation of programming authentication key to given RPMB target */
|
|
static int rpmb_program_auth_key(int fd, unsigned char target,
|
|
unsigned char *key_buf, int key_size)
|
|
{
|
|
int req_size = sizeof(struct rpmb_data_frame_t);
|
|
int rsp_size = sizeof(struct rpmb_data_frame_t);
|
|
|
|
struct rpmb_data_frame_t *req = NULL;
|
|
struct rpmb_data_frame_t *rsp = NULL;
|
|
|
|
int err = -ENOMEM;
|
|
|
|
req = rpmb_request_init(req_size, RPMB_REQ_AUTH_KEY_PROGRAM, target,
|
|
0, 0, 0, key_buf, (223 - key_size), key_size);
|
|
if (req == NULL) {
|
|
fprintf(stderr, "failed to allocate request buffer memory\n");
|
|
goto out;
|
|
}
|
|
|
|
/* send the request and get response */
|
|
err = send_rpmb_req(fd, req->target, req_size, req);
|
|
if (err) {
|
|
fprintf(stderr, "RPMB request 0x%04x for 0x%x, err: %d\n", req->type, req->target,
|
|
err);
|
|
goto out;
|
|
}
|
|
|
|
/* send the request to get the result and then request to get the response */
|
|
rsp = (struct rpmb_data_frame_t *)calloc(rsp_size, 1);
|
|
if (!rsp) {
|
|
fprintf(stderr, "failed to allocate response buffer memory\n");
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
rsp->target = req->target;
|
|
rsp->type = RPMB_REQ_READ_RESULT;
|
|
err = send_rpmb_req(fd, req->target, rsp_size, rsp);
|
|
if (err || rsp->result) {
|
|
fprintf(stderr, "Program auth key read result 0x%x, error = 0x%x\n", rsp->result,
|
|
err);
|
|
goto out;
|
|
}
|
|
|
|
/* reuse response buffer */
|
|
memset(rsp, 0, rsp_size);
|
|
err = recv_rpmb_rsp(fd, req->target, rsp_size, rsp);
|
|
if (err != 0) {
|
|
err = check_rpmb_response(req, rsp, "Failed to Program Key");
|
|
}
|
|
out:
|
|
free(req);
|
|
free(rsp);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/* Implementation of RPMB authenticated data write command; this function
|
|
* transfers msg_size bytes from msg_buf to controller 'addr'. Returns
|
|
* number of bytes actually written to, otherwise negetive error code
|
|
* on failures.
|
|
*/
|
|
static int auth_data_write_chunk(int fd, unsigned char tgt, unsigned int addr,
|
|
unsigned char *msg_buf, int msg_size,
|
|
unsigned char *keybuf, int keysize)
|
|
{
|
|
int req_size = sizeof(struct rpmb_data_frame_t) + msg_size;
|
|
int rsp_size = sizeof(struct rpmb_data_frame_t);
|
|
|
|
struct rpmb_data_frame_t *req = NULL;
|
|
struct rpmb_data_frame_t *rsp = NULL;
|
|
|
|
unsigned int write_cntr = 0;
|
|
unsigned char *mac = NULL;
|
|
int error = -ENOMEM;
|
|
|
|
/* get current write counter and copy to the request */
|
|
error = rpmb_read_write_counter(fd, tgt, &write_cntr);
|
|
if (error != 0) {
|
|
fprintf(stderr, "Failed to read write counter for write-data\n");
|
|
goto out;
|
|
}
|
|
|
|
req = rpmb_request_init(req_size, RPMB_REQ_AUTH_DATA_WRITE, tgt, 0,
|
|
addr, (msg_size / 512), msg_buf,
|
|
offsetof(struct rpmb_data_frame_t, data), msg_size);
|
|
if (req == NULL) {
|
|
fprintf(stderr, "Memory alloc failed for write-data command\n");
|
|
goto out;
|
|
}
|
|
|
|
req->write_counter = write_cntr;
|
|
|
|
/* compute HMAC hash */
|
|
mac = hmac_sha256(((unsigned char *)req + 223), req_size - 223,
|
|
keybuf, keysize);
|
|
if (mac == NULL) {
|
|
fprintf(stderr, "failed to compute HMAC-SHA256\n");
|
|
error = -1;
|
|
goto out;
|
|
}
|
|
|
|
memcpy(req->mac, mac, 32);
|
|
|
|
/* send the request and get response */
|
|
error = send_rpmb_req(fd, tgt, req_size, req);
|
|
if (error != 0) {
|
|
fprintf(stderr, "RPMB request 0x%04x for 0x%x, error: %d\n",
|
|
req->type, tgt, error);
|
|
goto out;
|
|
}
|
|
|
|
/* send the request to get the result and then request to get the response */
|
|
rsp = (struct rpmb_data_frame_t *)calloc(rsp_size, 1);
|
|
rsp->target = req->target;
|
|
rsp->type = RPMB_REQ_READ_RESULT;
|
|
error = send_rpmb_req(fd, tgt, rsp_size, rsp);
|
|
if (error != 0 || rsp->result != 0) {
|
|
fprintf(stderr, "Write-data read result 0x%x, error = 0x%x\n",
|
|
rsp->result, error);
|
|
goto out;
|
|
}
|
|
|
|
/* Read final response */
|
|
memset(rsp, 0, rsp_size);
|
|
error = recv_rpmb_rsp(fd, tgt, rsp_size, rsp);
|
|
if (error != 0)
|
|
fprintf(stderr, "Auth data write recv error = 0x%x\n", error);
|
|
else
|
|
error = check_rpmb_response(req, rsp, "Failed to write-data");
|
|
out:
|
|
free(req);
|
|
free(rsp);
|
|
free(mac);
|
|
|
|
return error;
|
|
}
|
|
|
|
/* send the request and get response */
|
|
static int rpmb_auth_data_write(int fd, unsigned char target,
|
|
unsigned int addr, int acc_size,
|
|
unsigned char *msg_buf, int msg_size,
|
|
unsigned char *keybuf, int keysize)
|
|
{
|
|
int chunk_size = acc_size < msg_size ? acc_size : msg_size;
|
|
int xfer = chunk_size;
|
|
int offset = 0;
|
|
|
|
while (xfer > 0 ) {
|
|
if (auth_data_write_chunk(fd, target, (addr + offset / 512),
|
|
msg_buf + offset, xfer,
|
|
keybuf, keysize) != 0)
|
|
{
|
|
/* error writing chunk data */
|
|
break;
|
|
}
|
|
|
|
offset += xfer;
|
|
if (offset + chunk_size > msg_size)
|
|
xfer = msg_size - offset;
|
|
else
|
|
xfer = chunk_size;
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* writes given config_block buffer to the drive target 0 */
|
|
static int rpmb_write_config_block(int fd, unsigned char *cfg_buf,
|
|
unsigned char *keybuf, int keysize)
|
|
{
|
|
int cfg_size = sizeof(struct rpmb_config_block_t);
|
|
int rsp_size = sizeof(struct rpmb_data_frame_t);
|
|
int req_size = rsp_size + cfg_size;
|
|
|
|
struct rpmb_data_frame_t *req = NULL;
|
|
struct rpmb_data_frame_t *rsp = NULL;
|
|
unsigned char *cfg_buf_read = NULL, *mac = NULL;
|
|
unsigned int write_cntr = 0;
|
|
int error = -ENOMEM;
|
|
|
|
/* initialize request */
|
|
req = rpmb_request_init(req_size, RPMB_REQ_AUTH_DCB_WRITE, 0, 0, 0, 1,
|
|
cfg_buf, offsetof(struct rpmb_data_frame_t, data),
|
|
cfg_size);
|
|
if (req == NULL) {
|
|
fprintf(stderr, "failed to allocate rpmb request buffer\n");
|
|
goto out;
|
|
}
|
|
|
|
/* read config block write_counter from controller */
|
|
write_cntr = rpmb_read_config_block(fd, &cfg_buf_read);
|
|
if (cfg_buf_read == NULL) {
|
|
fprintf(stderr, "failed to read config block write counter\n");
|
|
error = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
free(cfg_buf_read);
|
|
req->write_counter = write_cntr;
|
|
mac = hmac_sha256(((unsigned char *)req + 223), req_size - 223,
|
|
keybuf, keysize);
|
|
if (mac == NULL) {
|
|
fprintf(stderr, "failed to compute hmac-sha256 hash\n");
|
|
error = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
memcpy(req->mac, mac, sizeof(req->mac));
|
|
|
|
error = send_rpmb_req(fd, 0, req_size, req);
|
|
if (error != 0) {
|
|
fprintf(stderr, "Write-config RPMB request, error = 0x%x\n",
|
|
error);
|
|
goto out;
|
|
}
|
|
|
|
/* get response */
|
|
rsp = (struct rpmb_data_frame_t *)calloc(rsp_size, 1);
|
|
if (rsp == NULL) {
|
|
fprintf(stderr, "failed to allocate response buffer memory\n");
|
|
error = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/* get result first */
|
|
memset(rsp, 0, rsp_size);
|
|
rsp->target = req->target;
|
|
rsp->type = RPMB_REQ_READ_RESULT;
|
|
/* get the response and validate */
|
|
error = recv_rpmb_rsp(fd, req->target, rsp_size, rsp);
|
|
if (error != 0) {
|
|
fprintf(stderr,"Failed getting write-config response\
|
|
error = 0x%x\n", error);
|
|
goto out;
|
|
}
|
|
error = check_rpmb_response(req, rsp,
|
|
"Failed to retrieve write-config response");
|
|
out:
|
|
free(req);
|
|
free(rsp);
|
|
free(mac);
|
|
|
|
return error;
|
|
}
|
|
|
|
static bool invalid_xfer_size(int blocks, unsigned int bpsz)
|
|
{
|
|
return ((blocks <= 0) ||
|
|
(blocks * 512) > ((bpsz + 1) * 128 * 1024));
|
|
}
|
|
|
|
/* Handling rpmb sub-command */
|
|
int rpmb_cmd_option(int argc, char **argv, struct command *cmd, struct plugin *plugin)
|
|
{
|
|
const char *desc = "Run RPMB command on the supporting controller";
|
|
const char *msg = "data to be written on write-data or write-config commands";
|
|
const char *mfile = "data file for read/write-data, read/write-config options";
|
|
const char *kfile = "key file that has authentication key to be used";
|
|
const char *target = "RPMB target - numerical value of 0 to 6, default 0";
|
|
const char *address = "Sector offset to read from or write to for an RPMB target, default 0";
|
|
const char *blocks = "Number of 512 blocks to read or write";
|
|
const char *key = "key to be used for authentication";
|
|
const char *opt = "RPMB action - info, program-key, read-counter, write-data, " \
|
|
"read-data, write-config and read-config";
|
|
|
|
struct config {
|
|
char *cmd;
|
|
char *key;
|
|
char *msg;
|
|
char *keyfile;
|
|
char *msgfile;
|
|
int opt;
|
|
int address;
|
|
int blocks;
|
|
char target;
|
|
};
|
|
|
|
struct config cfg = {
|
|
.cmd = "info",
|
|
.key = NULL,
|
|
.msg = NULL,
|
|
.msgfile = NULL,
|
|
.keyfile = NULL,
|
|
.opt = 0,
|
|
.address = 0,
|
|
.blocks = 0,
|
|
.target = 0,
|
|
};
|
|
|
|
OPT_ARGS(opts) = {
|
|
OPT_STRING("cmd", 'c', "command", &cfg.cmd, opt),
|
|
OPT_STRING("msgfile", 'f', "FILE", &cfg.msgfile, mfile),
|
|
OPT_STRING("keyfile", 'g', "FILE", &cfg.keyfile, kfile),
|
|
OPT_STRING("key", 'k', "key", &cfg.key, key),
|
|
OPT_STRING("msg", 'd', "data", &cfg.msg, msg),
|
|
OPT_UINT("address", 'o', &cfg.address, address),
|
|
OPT_UINT("blocks", 'b', &cfg.blocks, blocks),
|
|
OPT_UINT("target", 't', &cfg.target, target),
|
|
OPT_END()
|
|
};
|
|
|
|
unsigned int write_cntr = 0;
|
|
unsigned char *key_buf = NULL;
|
|
unsigned char *msg_buf = NULL;
|
|
unsigned int msg_size = 0;
|
|
unsigned int key_size = 0;
|
|
struct nvme_id_ctrl ctrl;
|
|
struct nvme_dev *dev;
|
|
int err = -1;
|
|
|
|
union ctrl_rpmbs_reg {
|
|
struct {
|
|
unsigned int num_targets:3;
|
|
unsigned int auth_method:3;
|
|
unsigned int reserved:10;
|
|
unsigned int total_size:8; /* 128K units */
|
|
unsigned int access_size:8; /* in 512 byte count */
|
|
};
|
|
unsigned int rpmbs;
|
|
} regs;
|
|
|
|
if ((err = parse_and_open(&dev, argc, argv, desc, opts)))
|
|
return err;
|
|
|
|
/* before parsing commands, check if controller supports any RPMB targets */
|
|
err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
|
|
if (err)
|
|
goto out;
|
|
|
|
regs.rpmbs = le32_to_cpu(ctrl.rpmbs);
|
|
if (regs.num_targets == 0) {
|
|
fprintf(stderr, "No RPMB targets are supported by the drive\n");
|
|
goto out;
|
|
}
|
|
|
|
/* parse and validate options; default print rpmb support info */
|
|
if (cfg.cmd == 0 || strcmp(cfg.cmd, "info") == 0) {
|
|
nvme_show_id_ctrl_rpmbs(regs.rpmbs, 0);
|
|
goto out;
|
|
}
|
|
|
|
if (strcmp(cfg.cmd, "program-key") == 0)
|
|
cfg.opt = RPMB_REQ_AUTH_KEY_PROGRAM;
|
|
else if (strcmp(cfg.cmd, "read-counter") == 0)
|
|
cfg.opt = RPMB_REQ_READ_WRITE_CNTR;
|
|
else if (strcmp(cfg.cmd, "write-data") == 0)
|
|
cfg.opt = RPMB_REQ_AUTH_DATA_WRITE;
|
|
else if (strcmp(cfg.cmd, "read-data") == 0)
|
|
cfg.opt = RPMB_REQ_AUTH_DATA_READ;
|
|
else if (strcmp(cfg.cmd, "write-config") == 0)
|
|
cfg.opt = RPMB_REQ_AUTH_DCB_WRITE;
|
|
else if (strcmp(cfg.cmd, "read-config") == 0)
|
|
cfg.opt = RPMB_REQ_AUTH_DCB_READ;
|
|
else {
|
|
fprintf(stderr, "Invalid option %s for rpmb command\n", cfg.cmd);
|
|
goto out;
|
|
}
|
|
|
|
/* input file/data processing */
|
|
if (cfg.opt == RPMB_REQ_AUTH_DCB_WRITE ||
|
|
cfg.opt == RPMB_REQ_AUTH_DATA_WRITE ||
|
|
cfg.opt == RPMB_REQ_AUTH_KEY_PROGRAM)
|
|
{
|
|
key_buf = read_rpmb_key(cfg.key, cfg.keyfile, &key_size);
|
|
if (key_buf == NULL) {
|
|
fprintf(stderr, "Failed to read key\n");
|
|
goto out;
|
|
}
|
|
|
|
if (key_size > 223 || key_size <= 0) {
|
|
fprintf(stderr, "Invalid key size %d, valid input 1 to 223\n",
|
|
key_size);
|
|
goto out;
|
|
}
|
|
|
|
if (cfg.opt == RPMB_REQ_AUTH_DCB_WRITE ||
|
|
cfg.opt == RPMB_REQ_AUTH_DATA_WRITE) {
|
|
if (cfg.msg != NULL) {
|
|
msg_size = strlen(cfg.msg);
|
|
msg_buf = (unsigned char *)malloc(msg_size);
|
|
memcpy(msg_buf, cfg.msg, msg_size);
|
|
} else {
|
|
err = read_file(cfg.msgfile, &msg_buf, &msg_size);
|
|
if (err || msg_size <= 0) {
|
|
fprintf(stderr, "Failed to read file %s\n",
|
|
cfg.msgfile);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (cfg.opt) {
|
|
case RPMB_REQ_READ_WRITE_CNTR:
|
|
err = rpmb_read_write_counter(dev_fd(dev), cfg.target,
|
|
&write_cntr);
|
|
if (err == 0)
|
|
printf("Write Counter is: %u\n", write_cntr);
|
|
break;
|
|
|
|
case RPMB_REQ_AUTH_DCB_READ:
|
|
write_cntr = rpmb_read_config_block(dev_fd(dev),
|
|
&msg_buf);
|
|
if (msg_buf == NULL) {
|
|
fprintf(stderr, "failed read config blk\n");
|
|
goto out;
|
|
}
|
|
|
|
/* no output file is given, print the data on stdout */
|
|
if (cfg.msgfile == 0) {
|
|
struct rpmb_config_block_t *cfg =
|
|
(struct rpmb_config_block_t *)msg_buf;
|
|
printf("Boot Partition Protection is %s\n",
|
|
((cfg->bp_enable & 0x1) ? "Enabled" : "Disabled"));
|
|
printf("Boot Partition 1 is %s\n",
|
|
((cfg->bp_lock & 0x2) ? "Locked" : "Unlocked"));
|
|
printf("Boot Partition 0 is %s\n",
|
|
((cfg->bp_lock & 0x1) ? "Locked" : "Unlocked"));
|
|
} else {
|
|
printf("Saving received config data to %s file\n", cfg.msgfile);
|
|
write_file(msg_buf, sizeof(struct rpmb_config_block_t), NULL,
|
|
cfg.msgfile, NULL);
|
|
}
|
|
err = (write_cntr == 0);
|
|
break;
|
|
|
|
case RPMB_REQ_AUTH_DATA_READ:
|
|
/* check if requested data is beyond what target supports */
|
|
msg_size = cfg.blocks * 512;
|
|
if (invalid_xfer_size(cfg.blocks, regs.total_size)) {
|
|
fprintf(stderr, "invalid transfer size %d \n",
|
|
msg_size);
|
|
break;
|
|
}
|
|
err = rpmb_auth_data_read(dev_fd(dev), cfg.target,
|
|
cfg.address, &msg_buf,
|
|
cfg.blocks,
|
|
(regs.access_size + 1));
|
|
if (err > 0 && msg_buf != NULL) {
|
|
printf("Writing %d bytes to file %s\n",
|
|
err * 512, cfg.msgfile);
|
|
write_file(msg_buf, err * 512, NULL,
|
|
cfg.msgfile, NULL);
|
|
}
|
|
break;
|
|
|
|
case RPMB_REQ_AUTH_DATA_WRITE:
|
|
if (invalid_xfer_size(cfg.blocks, regs.total_size) ||
|
|
(cfg.blocks * 512) > msg_size) {
|
|
fprintf(stderr, "invalid transfer size %d\n",
|
|
cfg.blocks * 512);
|
|
break;
|
|
} else if ((cfg.blocks * 512) < msg_size) {
|
|
msg_size = cfg.blocks * 512;
|
|
}
|
|
err = rpmb_auth_data_write(dev_fd(dev), cfg.target,
|
|
cfg.address,
|
|
((regs.access_size + 1) * 512),
|
|
msg_buf, msg_size,
|
|
key_buf, key_size);
|
|
|
|
/* print whatever extent of data written to target */
|
|
printf("Written %d sectors out of %d @target(%d):0x%x\n",
|
|
err/512, msg_size/512, cfg.target, cfg.address);
|
|
break;
|
|
|
|
case RPMB_REQ_AUTH_DCB_WRITE:
|
|
err = rpmb_write_config_block(dev_fd(dev), msg_buf,
|
|
key_buf, key_size);
|
|
break;
|
|
|
|
case RPMB_REQ_AUTH_KEY_PROGRAM:
|
|
err = rpmb_program_auth_key(dev_fd(dev), cfg.target,
|
|
key_buf, key_size);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
out:
|
|
/* release memory */
|
|
free(key_buf);
|
|
free(msg_buf);
|
|
|
|
/* close device */
|
|
dev_close(dev);
|
|
|
|
return err;
|
|
}
|