blob: 6c513ad145e685991f36f3f66f302170e6f15190 [file] [log] [blame]
/*
* tpm_ioctl -- ioctl utility for the TPM
*
* Authors: David Safford <safford@us.ibm.com>
* Stefan Berger <stefanb@us.ibm.com>
*
* (c) Copyright IBM Corporation 2014 - 2016.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the names of the IBM Corporation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* tool for using 'swtpm cuse' ioctls
*
* cuse_tpm_ioctl [ -c | -i | -s | -e | -l num | -C | -v ] devicepath
* -c get ptm capabilities
* -i do a hardware TPM_Init
* -s shutdown tpm_server_cuse
* -e get tpmEstablished bit
* -l set locality to num
* -h hash the given data
* -v store volatile data to file
* -C cancel an ongoing TPM command
*/
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stddef.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netdb.h>
#include <sys/param.h>
#include <signal.h>
#include <poll.h>
#include <swtpm/tpm_ioctl.h>
#include <libtpms/tpm_error.h>
#include "sys_dependencies.h"
#include "swtpm.h"
#define DEFAULT_TCP_PORT 6546
#define devtoh32(is_chardev, x) (is_chardev ? x : be32toh(x))
#define htodev32(is_chardev, x) (is_chardev ? x : htobe32(x))
#define devtoh64(is_chardev, x) (is_chardev ? x : be64toh(x))
#define htodev64(is_chardev, x) (is_chardev ? x : htobe64(x))
/* for OpenBSD */
#ifndef _IOC_NRSHIFT
# define _IOC_NRSHIFT 0
#endif
#ifndef _IOC_NRMASK
# define _IOC_NRMASK 255
#endif
/* poll timeout that takes into account a busy swtpm creating a key */
#define DEFAULT_POLL_TIMEOUT 10000 /* ms */
static unsigned long ioctl_to_cmd(unsigned long ioctlnum)
{
/* the ioctl number contains the command number - 1 */
return ((ioctlnum >> _IOC_NRSHIFT) & _IOC_NRMASK) + 1;
}
/*
* ctrlcmd - send a control command
*
* This function returns -1 on on error with errno indicating the error.
* In case an ioctl is used, it returns 0 on success; otherwise
* it returns the number of bytes received in the response.
*/
static int ctrlcmd(int fd, unsigned long cmd, void *msg, size_t msg_len_in,
size_t msg_len_out)
{
struct stat statbuf;
int n;
n = fstat(fd, &statbuf);
if (n < 0) {
fprintf(stderr, "fstat failed: %s\n", strerror(errno));
return -1;
}
if ((statbuf.st_mode & S_IFMT) == S_IFCHR) {
n = ioctl(fd, cmd, msg);
} else {
uint32_t cmd_no = htobe32(ioctl_to_cmd(cmd));
struct iovec iov[2] = {
{
.iov_base = &cmd_no,
.iov_len = sizeof(cmd_no),
}, {
.iov_base = msg,
.iov_len = msg_len_in,
},
};
n = writev(fd, iov, 2);
if (n > 0) {
if (msg_len_out > 0) {
struct pollfd fds = {
.fd = fd,
.events = POLLIN,
};
n = poll(&fds, 1, DEFAULT_POLL_TIMEOUT);
if (n == 1) {
n = read(fd, msg, msg_len_out);
} else if (n == 0) {
n = -1;
errno = ETIMEDOUT;
}
} else {
/* we read 0 bytes */
n = 0;
}
}
}
return n;
}
/*
* Do PTM_HASH_START, PTM_HASH_DATA, PTM_HASH_END on the
* data.
*/
static int do_hash_start_data_end(int fd, bool is_chardev, const char *input)
{
ptm_res res;
int n;
size_t idx;
ptm_hdata hdata;
size_t inputlen;
memset(&hdata, 0, sizeof(hdata));
if (!input) {
fprintf(stderr,
"Input buffer for hashing must not be NULL.\n");
return 1;
}
/* hash string given on command line */
n = ctrlcmd(fd, PTM_HASH_START, &res, 0, sizeof(res));
if (n < 0) {
fprintf(stderr,
"Could not execute ioctl PTM_HASH_START: "
"%s\n", strerror(errno));
return 1;
}
if (devtoh32(is_chardev, res) != 0) {
fprintf(stderr,
"TPM result from PTM_HASH_START: 0x%x\n",
devtoh32(is_chardev, res));
return 1;
}
inputlen = strlen(input);
if (inputlen == 1 && input[0] == '-') {
/* read data from stdin */
while (1) {
idx = 0;
int c = 0;
while (idx < sizeof(hdata.u.req.data)) {
c = fgetc(stdin);
if (c == EOF)
break;
hdata.u.req.data[idx] = (char)c;
idx++;
}
hdata.u.req.length = htodev32(is_chardev, idx);
n = ctrlcmd(fd, PTM_HASH_DATA, &hdata,
offsetof(ptm_hdata, u.req.data) + idx,
sizeof(hdata.u.resp));
res = devtoh32(is_chardev, hdata.u.resp.tpm_result);
if (n < 0 || res != 0 || c == EOF)
break;
}
} else {
idx = 0;
while (idx < inputlen) {
size_t tocopy = inputlen - idx;
if (tocopy > sizeof(hdata.u.req.data))
tocopy = sizeof(hdata.u.req.data);
hdata.u.req.length = htodev32(is_chardev, tocopy);
memcpy(hdata.u.req.data, &input[idx], tocopy);
idx += tocopy;
n = ctrlcmd(fd, PTM_HASH_DATA, &hdata,
offsetof(ptm_hdata, u.req.data) + tocopy,
sizeof(hdata.u.resp));
res = devtoh32(is_chardev, hdata.u.resp.tpm_result);
if (n < 0 || res != 0)
break;
}
}
if (n < 0) {
fprintf(stderr,
"Could not execute ioctl PTM_HASH_DATA: "
"%s\n", strerror(errno));
return 1;
}
if (res != 0) {
fprintf(stderr,
"TPM result from PTM_HASH_DATA: 0x%x\n", res);
return 1;
}
n = ctrlcmd(fd, PTM_HASH_END, &res, 0, sizeof(res));
if (n < 0) {
fprintf(stderr,
"Could not execute ioctl PTM_HASH_END: "
"%s\n", strerror(errno));
return 1;
}
if (devtoh32(is_chardev, res) != 0) {
fprintf(stderr,
"TPM result from PTM_HASH_END: 0x%x\n",
devtoh32(is_chardev, res));
return 1;
}
return 0;
}
static uint32_t get_blobtype(const char *blobname)
{
if (!blobname)
return 0;
if (!strcmp(blobname, "permanent"))
return PTM_BLOB_TYPE_PERMANENT;
if (!strcmp(blobname, "volatile"))
return PTM_BLOB_TYPE_VOLATILE;
if (!strcmp(blobname, "savestate"))
return PTM_BLOB_TYPE_SAVESTATE;
return 0;
}
/*
* do_save_state_blob: Get a state blob from the TPM and store it into the
* given file
* @fd: file descriptor to talk to the TPM
* @is_chardev: whether @fd is a character device using ioctl
* @blobtype: the name of the blobtype
* @filename: name of the file to store the blob into
*
*/
static int do_save_state_blob(int fd, bool is_chardev, const char *blobtype,
const char *filename, size_t buffersize)
{
int file_fd;
ptm_res res;
ptm_getstate pgs;
uint16_t offset;
ssize_t numbytes, remain = -1;
bool had_error;
int n;
uint32_t bt;
unsigned char *buffer = NULL;
uint32_t recvd_bytes;
memset(&pgs, 0, sizeof(pgs));
bt = get_blobtype(blobtype);
if (!bt) {
fprintf(stderr,
"Unknown TPM state type '%s'", blobtype);
return 1;
}
file_fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
if (file_fd < 0) {
fprintf(stderr,
"Could not open file '%s' for writing: %s\n",
filename, strerror(errno));
return 1;
}
if (!is_chardev)
buffersize = 4096;
had_error = false;
offset = 0;
while (true) {
/* fill out request every time since response may change it */
pgs.u.req.state_flags = htodev32(is_chardev, PTM_STATE_FLAG_DECRYPTED);
pgs.u.req.type = htodev32(is_chardev, bt);
pgs.u.req.offset = htodev32(is_chardev, offset);
n = ctrlcmd(fd, PTM_GET_STATEBLOB, &pgs, sizeof(pgs.u.req),
sizeof(pgs.u.resp));
if (n < 0) {
fprintf(stderr,
"Could not execute ioctl PTM_GET_STATEBLOB: "
"%s\n", strerror(errno));
had_error = true;
break;
}
res = devtoh32(is_chardev, pgs.u.resp.tpm_result);
if (res != 0 && (res & TPM_NON_FATAL) == 0) {
fprintf(stderr,
"TPM result from PTM_GET_STATEBLOB: 0x%x\n",
res);
had_error = true;
break;
}
if (remain == -1)
remain = devtoh32(is_chardev, pgs.u.resp.totlength);
if (!is_chardev) {
/* we receive a part of the chunk */
recvd_bytes = n - offsetof(ptm_getstate, u.resp.data);
if ((int32_t)recvd_bytes < 0) {
fprintf(stderr,
"Too few bytes in response");
had_error = true;
break;
}
} else {
recvd_bytes =
MIN(devtoh32(is_chardev, pgs.u.resp.length),
sizeof(pgs.u.resp.data));
}
numbytes = write(file_fd, pgs.u.resp.data, recvd_bytes);
if (numbytes < 0 || (uint32_t)numbytes != recvd_bytes) {
fprintf(stderr,
"Could not write to file '%s': %s\n",
filename, strerror(errno));
had_error = true;
break;
}
remain -= recvd_bytes;
/* done when the last byte was received */
if (offset + recvd_bytes >= devtoh32(is_chardev, pgs.u.resp.totlength))
break;
if (buffersize) {
/* continue with the read interface */
buffer = malloc(buffersize);
if (!buffer) {
fprintf(stderr,
"Could not allocate buffer with %zu bytes.",
buffersize);
had_error = true;
break;
}
while (true) {
/* read from TPM */
n = read(fd, buffer, buffersize);
if (n < 0) {
fprintf(stderr,
"Could not read from TPM: %s\n",
strerror(errno));
had_error = true;
break;
}
remain -= n;
numbytes = write(file_fd, buffer, n);
if (numbytes < 0) {
fprintf(stderr,
"Could not write to file '%s': %s\n",
filename, strerror(errno));
had_error = true;
break;
}
if (remain <= 0)
break;
}
break;
} else {
offset += devtoh32(is_chardev, pgs.u.resp.length);
}
}
close(file_fd);
free(buffer);
if (had_error)
return 1;
if (remain != 0) {
fprintf(stderr, "Unexpected number of remaining bytes: %zd\n", remain);
return 1;
}
return 0;
}
/*
* do_load_state_blob: Load a TPM state blob from a file and load it into the
* TPM
* @fd: file descriptor to talk to the TPM
* @is_chardev: whether @fd is a character device
* @blobtype: the name of the blobtype
* @filename: name of the file to store the blob into
* @buffersize: the size of the buffer to use via write() interface
*/
static int do_load_state_blob(int fd, bool is_chardev, const char *blobtype,
const char *filename,
size_t buffersize)
{
int file_fd;
ptm_res res;
ptm_setstate pss;
ssize_t numbytes;
bool had_error;
int n;
uint32_t bt;
unsigned char *buffer = NULL;
struct stat statbuf;
bt = get_blobtype(blobtype);
if (!bt) {
fprintf(stderr,
"Unknown TPM state type '%s'", blobtype);
return 1;
}
if (!filename) {
fprintf(stderr,
"Filename parameter must not be NULL\n");
return 1;
}
file_fd = open(filename, O_RDONLY);
if (file_fd < 0) {
fprintf(stderr,
"Could not open file '%s' for reading: %s\n",
filename, strerror(errno));
return 1;
}
if (!is_chardev) {
n = fstat(file_fd, &statbuf);
if (n < 0) {
fprintf(stderr,
"Could not stat file '%s': %s\n",
filename, strerror(errno));
close(file_fd);
return 1;
}
buffersize = statbuf.st_size;
}
had_error = false;
if (!buffersize) {
/* use only the ioctl interface for the transfer */
while (true) {
size_t returnsize;
/* fill out request every time since response may change it */
pss.u.req.state_flags = htodev32(is_chardev, 0);
pss.u.req.type = htodev32(is_chardev, bt);
numbytes = read(file_fd, pss.u.req.data, sizeof(pss.u.req.data));
if (numbytes < 0) {
fprintf(stderr,
"Could not read from file '%s': %s\n",
filename, strerror(errno));
had_error = true;
break;
}
pss.u.req.length = htodev32(is_chardev, (uint32_t)numbytes);
/* the returnsize is zero on all intermediate packets */
returnsize = ((size_t)numbytes < sizeof(pss.u.req.data))
? sizeof(pss) : 0;
n = ctrlcmd(fd, PTM_SET_STATEBLOB, &pss,
offsetof(ptm_setstate, u.req.data) + numbytes,
returnsize);
if (n < 0) {
fprintf(stderr,
"Could not execute ioctl PTM_SET_STATEBLOB: "
"%s\n", strerror(errno));
had_error = true;
break;
}
res = devtoh32(is_chardev, pss.u.resp.tpm_result);
if (res != 0) {
fprintf(stderr,
"TPM result from PTM_SET_STATEBLOB: 0x%x\n",
res);
had_error = true;
break;
}
if ((size_t)numbytes < sizeof(pss.u.req.data))
break;
}
} else {
buffer = malloc(buffersize);
if (!buffer) {
fprintf(stderr,
"Could not allocate buffer with %zu bytes.",
buffersize);
had_error = true;
goto cleanup;
}
memset(&pss, 0, sizeof(pss));
pss.u.req.state_flags = htodev32(is_chardev, 0);
pss.u.req.type = htodev32(is_chardev, bt);
/* will use write interface */
if (is_chardev) {
pss.u.req.length = htodev32(is_chardev, 0);
} else {
pss.u.req.length = htodev32(is_chardev, buffersize);
}
n = ctrlcmd(fd, PTM_SET_STATEBLOB, &pss,
offsetof(ptm_setstate, u.req.data) + 0,
0);
if (n < 0) {
fprintf(stderr,
"Could not execute ioctl PTM_SET_STATEBLOB: "
"%s\n", strerror(errno));
had_error = 1;
goto cleanup;
}
res = devtoh32(is_chardev, pss.u.resp.tpm_result);
if (res != 0) {
fprintf(stderr,
"TPM result from PTM_SET_STATEBLOB: 0x%x\n",
res);
had_error = 1;
goto cleanup;
}
while (true) {
n = read(file_fd, buffer, buffersize);
if (n < 0) {
fprintf(stderr, "Could not read from file: %s\n",
strerror(errno));
had_error = 1;
goto cleanup;
}
if (n != write(fd, buffer, n)) {
fprintf(stderr, "Could not write to file: %s\n",
strerror(errno));
had_error = 1;
goto cleanup;
}
if (!is_chardev) {
n = read(fd, &pss.u.resp, sizeof(pss.u.resp));
if (n < 0) {
fprintf(stderr,
"Error reading response: %s\n",
strerror(errno));
had_error = 1;
goto cleanup;
}
if (n != sizeof(pss.u.resp)) {
fprintf(stderr,
"Did not get enough response bytes "
"from PTM_SET_STATE_BLOB: %d\n", n);
had_error = 1;
goto cleanup;
}
res = devtoh32(is_chardev, pss.u.resp.tpm_result);
if (res != 0) {
fprintf(stderr,
"TPM result from PTM_SET_STATEBLOB: 0x%x\n",
res);
had_error = 1;
goto cleanup;
}
break;
}
if ((size_t)n < buffersize) {
/* close transfer with the ioctl() */
pss.u.req.state_flags = htodev32(is_chardev, 0);
pss.u.req.type = htodev32(is_chardev, bt);
/* end the transfer */
pss.u.req.length = htodev32(is_chardev, 0);
n = ctrlcmd(fd, PTM_SET_STATEBLOB, &pss,
offsetof(ptm_setstate, u.req.data) + 0,
sizeof(pss.u.resp));
if (n < 0) {
fprintf(stderr,
"Could not execute ioctl PTM_SET_STATEBLOB: "
"%s\n", strerror(errno));
had_error = 1;
goto cleanup;
}
res = devtoh32(is_chardev, pss.u.resp.tpm_result);
if (res != 0) {
fprintf(stderr,
"TPM result from PTM_SET_STATEBLOB: 0x%x\n",
res);
had_error = 1;
goto cleanup;
}
break;
}
}
}
cleanup:
close(file_fd);
free(buffer);
if (had_error)
return 1;
return 0;
}
static int change_fd_flags(int fd, int flags_to_clear, int flags_to_set) {
int n;
int orig_flags = fcntl(fd, F_GETFL, 0);
if (orig_flags == -1) {
fprintf(stderr, "fcntl(F_GETFL) failed: %s\n", strerror(errno));
return -1;
} else {
int flags = (orig_flags & ~flags_to_clear) | flags_to_set;
n = fcntl(fd, F_SETFL, flags);
if (n == -1) {
fprintf(stderr, "fcntl(F_SETFL) failed: %s\n", strerror(errno));
return -1;
}
}
return orig_flags;
}
/* Create a connection by setting the given file descriptor to non-blocking.
* If the function returns successfully, the file descriptor will be blocking
* again.
*/
static int connect_nonblock(int fd, const struct sockaddr *addr,
socklen_t addrlen, int allowed_errno,
const char *socktype)
{
int n, sockerr;
socklen_t optlen = sizeof(sockerr);
#if !defined(__CYGWIN__)
int orig_flags = change_fd_flags(fd, 0, O_NONBLOCK);
#else
int orig_flags = change_fd_flags(fd, 0, 0);
#endif
n = connect(fd, addr, addrlen);
/* n < 0: it must fail with EAGAIN (Unix socket) and then we have to poll
* and get SO_ERROR
* n = 0: connection is established
*/
if (n < 0 && errno != allowed_errno) {
fprintf(stderr, "Connect failed: %s\n", strerror(errno));
return -1;
} else if (n < 0) {
struct pollfd pollfd = {
.fd = fd,
.events = POLLOUT,
};
n = poll(&pollfd, 1, DEFAULT_POLL_TIMEOUT);
/* Unix socket may return POLLHUP on error */
if (n != 1 || (pollfd.revents & ~POLLOUT) != 0) {
fprintf(stderr, "Could not connect using %s socket.\n", socktype);
return -1;
}
n = getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &optlen);
if (n < 0) {
fprintf(stderr, "getsockopt(SO_ERROR) failed: %s\n",
strerror(errno));
return -1;
}
if (sockerr != 0) {
fprintf(stderr, "Could not connect using %s socket.\n", socktype);
return -1;
}
}
return change_fd_flags(fd, ~0, orig_flags);
}
static int open_connection(const char *devname, char *tcp_hostname,
unsigned short tcp_port, const char *unix_path)
{
int fd = -1;
if (devname) {
fd = open(devname, O_RDWR);
if (fd < 0) {
fprintf(stderr, "Unable to open device '%s'.\n", devname);
}
} else if (tcp_hostname) {
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *ais = NULL, *ai;
char portstr[10];
snprintf(portstr, sizeof(portstr), "%u", tcp_port);
int err = getaddrinfo(tcp_hostname, portstr, &hints, &ais);
if (err != 0) {
fprintf(stderr, "getaddrinfo failed on host '%s': %s\n",
tcp_hostname, gai_strerror(err));
return -1;
}
for (ai = ais; ai != NULL; ai = ai->ai_next) {
fd = socket(ai->ai_family, ai->ai_socktype, 0);
if (fd < 0)
continue;
if (connect(fd,
(struct sockaddr *)ai->ai_addr, ai->ai_addrlen) == 0)
break;
close(fd);
fd = -1;
}
freeaddrinfo(ais);
if (fd < 0) {
fprintf(stderr, "Could not connect to host '%s' on port '%u' "
"using TCP socket: %s\n", tcp_hostname, tcp_port,
strerror(errno));
}
} else if (unix_path) {
struct sockaddr_un addr;
size_t unix_path_len = strlen(unix_path) + 1;
if (unix_path_len > sizeof(addr.sun_path)) {
fprintf(stderr, "Socket path is too long.\n");
return -1;
}
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd > 0) {
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, unix_path, unix_path_len);
if (connect_nonblock(fd, (struct sockaddr*)&addr,
sizeof(addr), EAGAIN, "UnixIO") < 0) {
close(fd);
fd = -1;
}
}
}
return fd;
}
static int parse_tcp_optarg(char *optarg, char **tcp_hostname,
unsigned int *tcp_port)
{
char *pos = strrchr(optarg, ':');
int n;
*tcp_port = DEFAULT_TCP_PORT;
if (!pos) {
/* <server> */
*tcp_hostname = strdup(optarg);
if (*tcp_hostname == NULL) {
fprintf(stderr, "Out of memory.\n");
return -1;
}
return 0;
} else if (pos == optarg) {
if (strlen(&pos[1]) != 0) {
/* :<port> (not just ':') */
n = sscanf(&pos[1], "%u", tcp_port);
if (n != 1) {
fprintf(stderr, "Invalid port '%s'\n", &pos[1]);
return -1;
}
if (*tcp_port >= 65536) {
fprintf(stderr, "Port '%s' outside valid range.\n",
&optarg[1]);
return -1;
}
}
*tcp_hostname = strdup("127.0.0.1");
if (*tcp_hostname == NULL) {
fprintf(stderr, "Out of memory.\n");
return -1;
}
} else {
/* <server>:<port> */
n = sscanf(&pos[1], "%u", tcp_port);
if (n != 1) {
fprintf(stderr, "Invalid port '%s'\n", &pos[1]);
return -1;
}
if (*tcp_port >= 65536) {
fprintf(stderr, "Port '%s' outside valid range.\n",
&optarg[1]);
return -1;
}
*tcp_hostname = strndup(optarg, pos - optarg);
if (*tcp_hostname == NULL) {
fprintf(stderr, "Out of memory.\n");
return -1;
}
}
return 0;
}
static void versioninfo(void)
{
fprintf(stdout,
"TPM emulator control tool version %d.%d.%d, Copyright (c) 2015 IBM Corp.\n"
,SWTPM_VER_MAJOR, SWTPM_VER_MINOR, SWTPM_VER_MICRO);
}
static void usage(const char *prgname)
{
versioninfo();
fprintf(stdout,
"\n"
"Usage: %s command <device path>\n"
"\n"
"The following commands are supported:\n"
"--tpm-device <device> : use the given device; default is /dev/tpm0\n"
"--tcp [<host>]:[<prt>]: connect to TPM on given host and port;\n"
" default host is 127.0.0.1, default port is %u\n"
"--unix <path> : connect to TPM using UnixIO socket\n"
"-c : get ptm capabilities\n"
"-i : do a hardware TPM_Init; if volatile state is found,\n"
" it will resume the TPM with it and delete it\n"
" afterwards\n"
"--stop : stop the TPM without exiting\n"
"-s : shutdown the TPM; stops and exists\n"
"-e : get the tpmEstablished bit\n"
"-r <loc> : reset the tpmEstablished bit; use the given locality\n"
"-v : store the TPM's volatile data\n"
"-C : cancel an ongoing TPM command\n"
"-l <loc> : set the locality to the given number; valid\n"
" localities are 0-4\n"
"-h <data> : hash the given data; if data is '-' then data are\n"
" read from stdin\n"
"--save <type> <file> : store the TPM state blob of given type in a file;\n"
" type may be one of volatile, permanent, or savestate\n"
"--load <type> <file> : load the TPM state blob of given type from a file;\n"
" type may be one of volatile, permanent, or savestate\n"
"-g : get configuration flags indicating which keys are in\n"
" use\n"
"-b <buffersize> : set the buffer size of the TPM and get its current\n"
" size; get minimum and maximum supported sizes\n"
"--info <flags> : get TPM implementation specific information;\n"
" flags must be an integer value\n"
"--lock-storage <n> : lock the storage after it was unlocked; retry\n"
" n times with 10ms delay in between\n"
"--version : display version and exit\n"
"--help : display help screen and exit\n"
"\n"
, prgname, DEFAULT_TCP_PORT);
}
int main(int argc, char *argv[])
{
int fd = -1, n;
ptm_est est;
ptm_reset_est reset_est;
ptm_loc loc;
ptm_cap cap;
ptm_res res;
ptm_init init;
ptm_getconfig cfg;
ptm_setbuffersize psbs;
ptm_getinfo pgi;
ptm_lockstorage pls;
char *tmp;
size_t buffersize = 0;
static struct option long_options[] = {
{"tpm-device", required_argument, NULL, 'D'},
{"tcp", required_argument, NULL, 'T'},
{"unix", required_argument, NULL, 'U'},
{"c", no_argument, NULL, 'c'},
{"i", no_argument, NULL, 'i'},
{"stop", no_argument, NULL, 't'},
{"s", no_argument, NULL, 's'},
{"e", no_argument, NULL, 'e'},
{"r", required_argument, NULL, 'r'},
{"v", no_argument, NULL, 'v'},
{"C", no_argument, NULL, 'C'},
{"l", required_argument, NULL, 'l'},
{"h", required_argument, NULL, 'h'},
{"g", no_argument, NULL, 'g'},
{"b", required_argument, NULL, 'b'},
{"save", required_argument, NULL, 'S'},
{"load", required_argument, NULL, 'L'},
{"version", no_argument, NULL, 'V'},
{"info", required_argument, NULL, 'I'},
{"lock-storage", required_argument, NULL, 'o'},
{"help", no_argument, NULL, 'H'},
{NULL, 0, NULL, 0},
};
int opt, option_index = 0;
const char *command = NULL, *pcommand = NULL;
const char *blobtype = NULL, *blobfile = NULL, *hashdata = NULL;
const char *tpm_device = NULL, *unix_path = NULL;
char *tcp_hostname = NULL;
unsigned int locality = 0;
unsigned int tpmbuffersize = 0;
unsigned int tcp_port = 0;
unsigned int retries;
bool is_chardev;
unsigned long int info_flags = 0;
char *endptr = NULL;
int ret = EXIT_FAILURE;
#if defined __NetBSD__
while ((opt = getopt_long(argc, argv, "D:T:U:citser:vCl:h:gb:S:L:VI:H",
long_options, &option_index)) != -1) {
#else
while ((opt = getopt_long_only(argc, argv, "", long_options,
&option_index)) != -1) {
#endif
switch (opt) {
case 'D':
tpm_device = optarg;
break;
case 'T':
free(tcp_hostname);
if (parse_tcp_optarg(optarg, &tcp_hostname, &tcp_port) < 0)
return EXIT_FAILURE;
break;
case 'U':
unix_path = optarg;
break;
case 'c':
case 'i':
case 't':
case 's':
case 'e':
case 'v':
case 'C':
case 'g':
command = argv[optind - 1];
break;
case 'h':
command = argv[optind - 2];
hashdata = argv[optind - 1];
break;
case 'r':
case 'l':
command = argv[optind - 2];
if (sscanf(argv[optind - 1], "%u", &locality) != 1) {
fprintf(stderr, "Could not get locality number from %s.\n",
argv[optind - 1]);
goto exit;
}
if (locality > 4) {
fprintf(stderr, "Locality outside valid range of [0..4].\n");
goto exit;
}
break;
case 'S':
if (optind == argc ||
!strncmp(argv[optind], "-", 1) ||
!strncmp(argv[optind], "--", 2)) {
fprintf(stderr, "Missing filename argument for --save option\n");
goto exit;
}
command = argv[optind - 2];
blobtype = argv[optind - 1];
blobfile = argv[optind];
optind++;
break;
case 'L':
if (optind == argc ||
!strncmp(argv[optind], "-", 1) ||
!strncmp(argv[optind], "--", 2)) {
fprintf(stderr, "Missing filename argument for --load option\n");
goto exit;
}
command = argv[optind - 2];
blobtype = argv[optind - 1];
blobfile = argv[optind];
optind++;
break;
case 'b':
command = argv[optind - 2];
if (sscanf(argv[optind - 1], "%u", &tpmbuffersize) != 1) {
fprintf(stderr, "Could not get buffersize from %s.\n",
argv[optind - 1]);
goto exit;
}
break;
case 'I':
command = argv[optind - 2];
errno = 0;
info_flags = strtoul(argv[optind - 1], &endptr, 0);
if (errno || endptr[0] != '\0') {
fprintf(stderr, "Cannot parse info flags.\n");
goto exit;
}
break;
case 'o':
command = argv[optind - 2];
if (sscanf(argv[optind - 1], "%u", &retries) != 1) {
fprintf(stderr, "Could not parse lock retries from %s.\n",
argv[optind - 1]);
goto exit;
}
break;
case 'V':
versioninfo();
ret = EXIT_SUCCESS;
goto exit;
case 'H':
usage(argv[0]);
ret = EXIT_SUCCESS;
goto exit;
}
if (!pcommand) {
pcommand = command;
} else {
if (command != pcommand) {
fprintf(stderr, "Only one command may be given.\n");
goto exit;
}
}
}
if (!command) {
fprintf(stderr, "No valid command.\n");
goto exit;
}
if (!tpm_device && !tcp_hostname && !unix_path) {
if (optind == argc) {
fprintf(stderr, "Error: Missing device name.\n");
goto exit;
}
tpm_device = argv[optind];
}
is_chardev = (tpm_device != NULL);
if (is_chardev) {
tmp = getenv("SWTPM_IOCTL_BUFFERSIZE");
if (tmp) {
if (sscanf(tmp, "%zu", &buffersize) != 1 || buffersize < 1)
buffersize = 1;
}
} else {
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
fprintf(stderr, "Could not install signal handler for SIGPIPE.");
goto exit;
}
}
fd = open_connection(tpm_device, tcp_hostname, tcp_port, unix_path);
if (fd < 0) {
goto exit;
}
if (!strcmp(command, "-c")) {
n = ctrlcmd(fd, PTM_GET_CAPABILITY, &cap, 0, sizeof(cap));
if (n < 0) {
fprintf(stderr,
"Could not execute PTM_GET_CAPABILITY: "
"%s\n", strerror(errno));
goto exit;
}
/* no tpm_result here */
printf("ptm capability is 0x%" PRIx64 "\n",
(uint64_t)devtoh64(is_chardev, cap));
} else if (!strcmp(command, "-i")) {
init.u.req.init_flags = htodev32(is_chardev,
PTM_INIT_FLAG_DELETE_VOLATILE);
n = ctrlcmd(fd, PTM_INIT, &init, sizeof(init.u.req),
sizeof(init.u.resp));
if (n < 0) {
fprintf(stderr,
"Could not execute PTM_INIT: "
"%s\n", strerror(errno));
goto exit;
}
res = devtoh32(is_chardev, init.u.resp.tpm_result);
if (res != 0) {
fprintf(stderr,
"TPM result from PTM_INIT: 0x%x\n", res);
goto exit;
}
} else if (!strcmp(command, "-e")) {
n = ctrlcmd(fd, PTM_GET_TPMESTABLISHED, &est, 0, sizeof(est.u.resp));
if (n < 0) {
fprintf(stderr,
"Could not execute PTM_GET_ESTABLISHED: "
"%s\n", strerror(errno));
goto exit;
}
res = devtoh32(is_chardev, est.u.resp.tpm_result);
if (res != 0) {
fprintf(stderr,
"TPM result from PTM_GET_TPMESTABLISHED: 0x%x\n", res);
goto exit;
}
printf("tpmEstablished is %d\n", est.u.resp.bit);
} else if (!strcmp(command, "-r")) {
memset(&reset_est, 0, sizeof(reset_est));
reset_est.u.req.loc = locality;
n = ctrlcmd(fd, PTM_RESET_TPMESTABLISHED,
&reset_est, sizeof(reset_est.u.req),
sizeof(reset_est.u.resp));
if (n < 0) {
fprintf(stderr,
"Could not execute PTM_RESET_ESTABLISHED: "
"%s\n", strerror(errno));
goto exit;
}
res = devtoh32(is_chardev, reset_est.u.resp.tpm_result);
if (res != 0) {
fprintf(stderr,
"TPM result from PTM_RESET_TPMESTABLISHED: 0x%x\n", res);
goto exit;
}
} else if (!strcmp(command, "-s")) {
n = ctrlcmd(fd, PTM_SHUTDOWN, &res, 0, sizeof(res));
if (n < 0) {
fprintf(stderr,
"Could not execute PTM_SHUTDOWN: "
"%s\n", strerror(errno));
goto exit;
}
if (devtoh32(is_chardev, res) != 0) {
fprintf(stderr,
"TPM result from PTM_SHUTDOWN: 0x%x\n",
devtoh32(is_chardev, res));
goto exit;
}
} else if (!strcmp(command, "--stop")) {
n = ctrlcmd(fd, PTM_STOP, &res, 0, sizeof(res));
if (n < 0) {
fprintf(stderr,
"Could not execute PTM_STOP: "
"%s\n", strerror(errno));
goto exit;
}
if (devtoh32(is_chardev, res) != 0) {
fprintf(stderr,
"TPM result from PTM_STOP: 0x%x\n",
devtoh32(is_chardev, res));
goto exit;
}
} else if (!strcmp(command, "-l")) {
loc.u.req.loc = locality;
n = ctrlcmd(fd, PTM_SET_LOCALITY, &loc, sizeof(loc.u.req),
sizeof(loc.u.resp));
if (n < 0) {
fprintf(stderr,
"Could not execute PTM_SET_LOCALITY: "
"%s\n", strerror(errno));
goto exit;
}
res = devtoh32(is_chardev, loc.u.resp.tpm_result);
if (res != 0) {
fprintf(stderr,
"TPM result from PTM_SET_LOCALITY: 0x%x\n", res);
goto exit;
}
} else if (!strcmp(command, "-h")) {
if (do_hash_start_data_end(fd, is_chardev, hashdata)) {
goto exit;
}
} else if (!strcmp(command, "-C")) {
n = ctrlcmd(fd, PTM_CANCEL_TPM_CMD, &res, 0, sizeof(res));
if (n < 0) {
fprintf(stderr,
"Could not execute PTM_CANCEL_TPM_CMD: "
"%s\n", strerror(errno));
goto exit;
}
if (devtoh32(is_chardev, res) != 0) {
fprintf(stderr,
"TPM result from PTM_CANCEL_TPM_CMD: 0x%x\n",
devtoh32(is_chardev, res));
goto exit;
}
} else if (!strcmp(command, "-v")) {
n = ctrlcmd(fd, PTM_STORE_VOLATILE, &res, 0, sizeof(res));
if (n < 0) {
fprintf(stderr,
"Could not execute PTM_STORE_VOLATILE: "
"%s\n", strerror(errno));
goto exit;
}
if (devtoh32(is_chardev, res) != 0) {
fprintf(stderr,
"TPM result from PTM_STORE_VOLATILE: 0x%x\n",
devtoh32(is_chardev, res));
goto exit;
}
} else if (!strcmp(command, "--save")) {
if (do_save_state_blob(fd, is_chardev, blobtype, blobfile, buffersize))
goto exit;
} else if (!strcmp(command, "--load")) {
if (do_load_state_blob(fd, is_chardev, blobtype, blobfile, buffersize))
goto exit;
} else if (!strcmp(command, "-g")) {
n = ctrlcmd(fd, PTM_GET_CONFIG, &cfg, 0, sizeof(cfg));
if (n < 0) {
fprintf(stderr,
"Could not execute PTM_GET_CONFIG: "
"%s\n", strerror(errno));
goto exit;
}
res = devtoh32(is_chardev, cfg.u.resp.tpm_result);
if (res != 0) {
fprintf(stderr,
"TPM result from PTM_GET_CONFIG: 0x%x\n", res);
goto exit;
}
printf("ptm configuration flags: 0x%x\n",
devtoh32(is_chardev, cfg.u.resp.flags));
} else if (!strcmp(command, "-b")) {
memset(&psbs, 0, sizeof(psbs));
psbs.u.req.buffersize = htodev32(is_chardev, tpmbuffersize);
n = ctrlcmd(fd, PTM_SET_BUFFERSIZE, &psbs, sizeof(psbs.u.req),
sizeof(psbs.u.resp));
if (n < 0) {
fprintf(stderr,
"Could not execute PTM_SET_BUFFERSIZE: "
"%s\n", strerror(errno));
goto exit;
}
res = devtoh32(is_chardev, psbs.u.resp.tpm_result);
if (res != 0) {
fprintf(stderr,
"TPM result from PTM_SET_BUFFERSIZE: 0x%x\n", res);
goto exit;
}
printf("TPM buffersize: %u\n"
"minimum size : %u\n"
"maximum size : %u\n",
devtoh32(is_chardev, psbs.u.resp.buffersize),
devtoh32(is_chardev, psbs.u.resp.minsize),
devtoh32(is_chardev, psbs.u.resp.maxsize));
} else if (!strcmp(command, "--info")) {
memset(&pgi, 0, sizeof(pgi));
pgi.u.req.flags = htodev64(is_chardev, info_flags);
pgi.u.req.offset = htodev64(is_chardev, 0);
n = ctrlcmd(fd, PTM_GET_INFO, &pgi,
sizeof(pgi.u.req), sizeof(pgi.u.resp));
if (n < 0) {
fprintf(stderr,
"Could not execute PTM_GET_INFO: %s\n",
strerror(errno));
goto exit;
}
res = devtoh32(is_chardev, pgi.u.resp.tpm_result);
if (res != 0) {
fprintf(stderr,
"TPM result from PTM_GET_INFO: 0x%x\n", res);
goto exit;
}
printf("%s\n", pgi.u.resp.buffer);
} else if (!strcmp(command, "--lock-storage")) {
memset(&pls, 0, sizeof(pls));
pls.u.req.retries = htodev32(is_chardev, 0);
n = ctrlcmd(fd, PTM_LOCK_STORAGE, &pls,
sizeof(pls.u.req), sizeof(pls.u.resp));
if (n < 0) {
fprintf(stderr,
"Could not execute PTM_LOCK_STORAGE: "
"%s\n", strerror(errno));
goto exit;
}
res = devtoh32(is_chardev, pls.u.resp.tpm_result);
if (devtoh32(is_chardev, res) != 0) {
fprintf(stderr,
"TPM result from PTM_LOCK_STORAGE: 0x%x\n",
devtoh32(is_chardev, res));
goto exit;
}
} else {
usage(argv[0]);
goto exit;
}
ret = EXIT_SUCCESS;
exit:
free(tcp_hostname);
if (fd >= 0)
close(fd);
return ret;
}