This is evmctl-client program which can connect to evmctl daemon and
request for file signing.
For example one could use it as follows.
# make sure /var/run/evmctl is present.
$ mkdir -p /var/run/evmctl
# start daemon. Use V2 signature with sha256 hash.
$ evmctl daemonize -x -a sha256
# Sign a file
$ evmctl-client ima_sign --key /etc/keys/privkey_evm.pem /tmp/data.txt
# verify signature of file
$ evmctl ima_verify /tmp/data.txt -k /etc/keys/x509_evm.der
Similar stuff could be done for signing using a key which is on crypto card.
- Make sure /var/run/evmctl exists
mkdir -p /var/run/evmctl
- Start daemon
evmctl daemonize -x -a sha256 --engine_id pkcs11 --engine_so
/usr/lib64/openssl/engines/engine_pkcs11.so --engine_module opensc-pkcs11.so
- Set pin fort the smart card.
evmctl-client set_pin <card-pin>
- Sign file
evmctl-client ima_sign --key slot_1-id_bb82253c8ab1337a74b95a6c68da4a658859c71a
/tmp/data.txt
Or
evmctl-client ima_sign --key id_bb82253c8ab74b95a6c68da4a658859c71a --token "OpenSC
Card (Fedora Signing CA)" --pkcs11_module opensc-pkcs11.so /tmp/data.txt
- Verify file
evmctl ima_verify /tmp/data.txt --key /root/keys/fedora-signer-x509-cert.der
Signed-off-by: Vivek Goyal <vgoyal(a)redhat.com>
---
src/Makefile.am | 7 +-
src/client.c | 697 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 703 insertions(+), 1 deletion(-)
create mode 100644 src/client.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 472479f..45d5ea8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,11 +1,16 @@
-bin_PROGRAMS = evmctl
+bin_PROGRAMS = evmctl evmctl-client
evmctl_SOURCES = evmctl.c
evmctl_CPPFLAGS = $(OPENSSL_CFLAGS)
evmctl_LDFLAGS = $(LDFLAGS_READLINE)
evmctl_LDADD = $(OPENSSL_LIBS) -lkeyutils -lp11
+evmctl_client_SOURCES = client.c
+evmctl_client_CPPFLAGS = $(OPENSSL_CFLAGS)
+evmctl_client_LDFLAGS = $(LDFLAGS_READLINE)
+evmctl_client_LDADD = $(OPENSSL_LIBS) -lkeyutils -lp11
+
INCLUDES = -I$(top_srcdir) -include config.h
DISTCLEANFILES = @DISTCLEANFILES@
diff --git a/src/client.c b/src/client.c
new file mode 100644
index 0000000..92bafa3
--- /dev/null
+++ b/src/client.c
@@ -0,0 +1,697 @@
+/*
+ * evm-utils - IMA/EVM support utilities
+ *
+ * Copyright (C) 2013 Red Hat Inc.
+ *
+ * Authors:
+ * Vivek Goyal <vgoyal(a)redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ * File: client.c
+ * IMA/EVM client program
+ */
+
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+#include <syslog.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <libp11.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/prctl.h>
+#include <poll.h>
+
+#include "daemon.h"
+
+#define USE_FPRINTF
+
+#ifdef DEBUG
+#define log_debug(fmt, args...) do_log(LOG_DEBUG, "%s:%d " fmt, __func__ ,
__LINE__ , ##args)
+#else
+#define log_debug(fmt, args...)
+#define log_debug_dump(p, len)
+#endif
+
+#define log_info(fmt, args...) do_log(LOG_INFO, fmt, ##args)
+#define log_warn(fmt, args...) do_log(LOG_WARNING, fmt, ##args)
+#define log_notice(fmt, args...) do_log(LOG_NOTICE, fmt, ##args)
+#define log_err(fmt, args...) do_log(LOG_ERR, fmt, ##args)
+#define log_errno(fmt, args...) do_log(LOG_ERR, fmt ": errno: %s (%d)\n",
##args, strerror(errno), errno)
+
+
+struct client_info {
+ char *key;
+ char *token_label;
+ bool memlock;
+ char *file;
+ bool detached;
+ char *pkcs11_module;
+};
+
+struct command {
+ char *name;
+ int (*func)(struct command *cmd, struct client_info *ci);
+ int cmd;
+ char *arg;
+ char *msg; /* extra info message */
+};
+
+static int verbose = LOG_INFO - 1;
+static int g_argc;
+static char **g_argv;
+
+struct command cmds[];
+static void print_usage(struct command *cmd);
+
+static void do_log(int level, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+#ifdef USE_FPRINTF
+ vfprintf(stderr, fmt, ap);
+#else
+ vsyslog(level, fmt, ap);
+#endif
+ va_end(ap);
+}
+
+static int token_label_to_slot_id(struct client_info *ci, char *tk_label)
+{
+ int rc = -1;
+ PKCS11_CTX *ctx;
+ PKCS11_SLOT *slots, *slot, *temp_slots;
+ unsigned int nslots, temp_nslots;
+
+ if (ci->pkcs11_module == NULL) {
+ log_err("No pkcs11_module specified\n");
+ return -1;
+ }
+
+ ctx = PKCS11_CTX_new();
+
+ /* load pkcs #11 module */
+ rc = PKCS11_CTX_load(ctx, ci->pkcs11_module);
+ if (rc) {
+ log_err("Loading pkcs11 engine failed: %s\n",
+ ERR_reason_error_string(ERR_get_error()));
+ rc = -1;
+ goto nolib;
+ }
+
+ /* get information on all slots */
+ rc = PKCS11_enumerate_slots(ctx, &slots, &nslots);
+ if (rc < 0) {
+ log_err("No slots available\n");
+ rc = -1;
+ goto noslots;
+ }
+
+ /* search for slot with given label */
+ temp_slots = slots;
+ temp_nslots = nslots;
+
+ while (1) {
+ /* get first slot with a token */
+ slot = PKCS11_find_token(ctx, temp_slots, temp_nslots);
+ if (!slot || !slot->token) {
+ log_err("No token available with given label:%s\n",
+ tk_label);
+ rc = -1;
+ goto notoken;
+ }
+
+ if (!strcmp(slot->token->label, tk_label)) {
+ rc = PKCS11_get_slotid_from_slot(slot);
+ break;
+ }
+
+ temp_slots = slot + 1;
+ temp_nslots -= temp_slots - slots;
+ }
+
+ log_info("token label=%s slot_id=%u\n", tk_label, rc);
+
+notoken:
+ PKCS11_release_all_slots(ctx, slots, nslots);
+noslots:
+ PKCS11_CTX_unload(ctx);
+nolib:
+ PKCS11_CTX_free(ctx);
+ return rc;
+}
+
+/*
+ * Parse a token determine slot and prefix slot info to supplied key in a
+ * format openssl likes. New key memmory is allocated and returned in
+ * newkey which should be freed by caller
+ */
+static int parse_token_label(struct client_info *ci,
+ char *tk_label, char *key, char **newkey)
+{
+ char *p;
+ char *modified_key;
+ int slot_id;
+ char slot_str[32];
+
+ /*
+ * If user specified token label, convert that into slot and
+ * prefix it to key in a format understood by openssl
+ */
+ if (tk_label == NULL)
+ return 0;
+
+ slot_id = token_label_to_slot_id(ci, tk_label);
+ if (slot_id < 0)
+ return 1;
+
+ if (!strncmp(key, "id_", 3) || !strncmp(key, "label_", 6)) {
+ sprintf(slot_str, "slot_%d-", slot_id);
+ } else
+ sprintf(slot_str, "%d:", slot_id);
+
+ modified_key = malloc(strlen(slot_str) + strlen(key) + 1);
+ if (modified_key == NULL) {
+ log_err("Memory allocation failed\n");
+ return 1;
+ }
+
+ strcpy(modified_key, slot_str);
+ p = modified_key;
+ p += strlen(slot_str);
+ strcpy(p, key);
+ *newkey = modified_key;
+ log_info("Using key:%s\n", modified_key);
+ return 0;
+}
+
+static int check_response(int sd, char **srvmsg)
+{
+ ssize_t n;
+ struct msghdr msg;
+ struct iovec iov;
+ char buffer[1024];
+
+ evmctld_msghdr *em;
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+
+ memset(&msg, '\0', sizeof(msg));
+ memset(buffer, '\0', sizeof(buffer));
+
+ iov.iov_base = buffer;
+ iov.iov_len = 1023;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ n = recvmsg(sd, &msg, 0);
+ if (n < 0) {
+ fprintf(stderr, "evmctl-client: could not get response from "
+ "server: %m\n");
+ exit(1);
+ }
+
+ em = (evmctld_msghdr *)buffer;
+
+ if (em->version != EVMCTLD_VERSION) {
+ fprintf(stderr, "evmctl-client: got version %d, "
+ "expected version %d\n", em->version, EVMCTLD_VERSION);
+ exit(1);
+ }
+
+ if (em->command != CMD_RESPONSE) {
+ fprintf(stderr, "evmctl-client: got unexpected response: %d\n",
+ em->command);
+ exit(1);
+ }
+
+ evmctld_cmd_response *resp = (evmctld_cmd_response *)((uint8_t *)em +
+ offsetof(evmctld_msghdr, size) +
+ sizeof(em->size));
+
+ if (resp->rc == 0)
+ return 0;
+
+ *srvmsg = strdup((char *)resp->errmsg);
+ return resp->rc;
+}
+
+static void set_pin(int sd, char *pin)
+{
+ struct msghdr msg;
+ struct iovec iov[1];
+ evmctld_msghdr em;
+
+ uint32_t size0 = evmctld_string_size(pin);
+
+ em.version = EVMCTLD_VERSION;
+ em.command = CMD_SET_PIN;
+ em.size = size0;
+ iov[0].iov_base = &em;
+ iov[0].iov_len = sizeof (em);
+
+ memset(&msg, '\0', sizeof(msg));
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ ssize_t n;
+ n = sendmsg(sd, &msg, 0);
+ if (n < 0) {
+ fprintf(stderr, "evmctl-client: set pin: sendmsg failed: "
+ "%m\n");
+ exit(1);
+ }
+
+ uint8_t *buffer = NULL;
+ buffer = calloc(1, size0);
+ if (!buffer) {
+ fprintf(stderr, "evmctl-client: could not allocate memory: "
+ "%m\n");
+ exit(1);
+ }
+
+ evmctld_string *ep = (evmctld_string *)buffer;
+ evmctld_string_set(ep, pin);
+ iov[0].iov_base = ep;
+ iov[0].iov_len = size0;
+
+ n = sendmsg(sd, &msg, 0);
+ if (n < 0) {
+ fprintf(stderr, "evmctl-client: set pin: sendmsg failed: "
+ "%m\n");
+ exit(1);
+ }
+
+ char *srvmsg = NULL;
+ int rc = check_response(sd, &srvmsg);
+ if (rc < 0) {
+ fprintf(stderr, "evmctl-client: %s\n",
+ srvmsg);
+ exit(1);
+ }
+
+ free(buffer);
+}
+
+static int connect_to_server(void)
+{
+ int rc = access(SOCKPATH, R_OK);
+ if (rc != 0) {
+ fprintf(stderr, "evmctl-client: could not connect to server: "
+ "%m\n");
+ exit(1);
+ }
+
+ struct sockaddr_un addr_un = {
+ .sun_family = AF_UNIX,
+ .sun_path = SOCKPATH,
+ };
+
+ int sd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sd < 0) {
+ fprintf(stderr, "pesign-client: could not open socket: %m\n");
+ exit(1);
+ }
+
+ socklen_t len = strlen(addr_un.sun_path) +
+ sizeof(addr_un.sun_family);
+
+ rc = connect(sd, (struct sockaddr *)&addr_un, len);
+ if (rc < 0) {
+ fprintf(stderr, "pesign-client: could not connect to daemon: "
+ "%m\n");
+ exit(1);
+ }
+
+ return sd;
+}
+
+/*
+ * Concanate two input strings to a destination. Also allocate memory for
+ * destination which needs to be freed by caller
+ */
+static char *strcat_alloc(char *src1, char *src2)
+{
+ int len;
+ char *dest;
+
+ len = strlen(src1) + strlen(src2) + 1;
+ dest = calloc(len, 1);
+ if (!dest) {
+ fprintf(stderr, "evmctl-client: failed to allocate memory.\n");
+ exit(1);
+ }
+
+ strcpy(dest, src1);
+ strcat(dest, src2);
+
+ return dest;
+}
+
+static int sign_ima(int sd, struct client_info *ci)
+{
+ struct msghdr msg;
+ int max_nr_strings = 4;
+ struct iovec iov[max_nr_strings]; /* Key, file, token, memlock str */
+ evmctld_msghdr em;
+ uint32_t total_size = 0;
+ int nr_strings = 0, len, idx;
+ char *strings[max_nr_strings], *temp;
+
+ if (!ci->key) {
+ fprintf(stderr, "evmctl-client: No key specified.\n");
+ exit(1);
+ }
+
+ memset(strings, 0, max_nr_strings * sizeof(char *));
+
+ /* key to use for signing */
+ nr_strings++;
+ idx = 0;
+ len = sizeof(evmctld_string) + strlen("--key ") + strlen(ci->key) + 1;
+ strings[idx] = calloc(len, 1);
+ if (!strings[idx]) {
+ fprintf(stderr, "evmctl-client: failed to allocate memory.\n");
+ exit(1);
+ }
+
+ temp = strcat_alloc("--key ", ci->key);
+ total_size += len;
+ evmctld_string_set((evmctld_string *)strings[idx], temp);
+ free(temp);
+
+ /* memlock info */
+ if (ci->memlock) {
+ nr_strings++;
+ idx++;
+ len = sizeof(evmctld_string) + strlen("--memlock") + 1;
+ strings[idx] = calloc(len, 1);
+ if (!strings[idx]) {
+ fprintf(stderr, "evmctl-client: failed to allocate"
+ " memory.\n");
+ exit(1);
+ }
+
+ total_size += len;
+ evmctld_string_set((evmctld_string *)strings[idx],
+ "--memlock");
+ }
+
+ /* File to be signed */
+ nr_strings++;
+ idx++;
+ len = sizeof(evmctld_string) + strlen(ci->file) + 1;
+ strings[idx] = calloc(len, 1);
+ if (!strings[idx]) {
+ fprintf(stderr, "evmctl-client: failed to allocate memory.\n");
+ exit(1);
+ }
+ evmctld_string_set((evmctld_string *)strings[idx], ci->file);
+ total_size += len;
+
+
+ em.version = EVMCTLD_VERSION;
+ if (ci->detached == true)
+ em.command = CMD_IMA_SIGN_DETACHED;
+ else
+ em.command = CMD_IMA_SIGN_ATTACHED;
+ em.size = total_size;
+ iov[0].iov_base = &em;
+ iov[0].iov_len = sizeof (em);
+
+ memset(&msg, '\0', sizeof(msg));
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ ssize_t n;
+ n = sendmsg(sd, &msg, 0);
+ if (n < 0) {
+ fprintf(stderr, "evmctl-client: set pin: sendmsg failed: "
+ "%m\n");
+ exit(1);
+ }
+
+ for (idx = 0; idx < nr_strings; idx++) {
+ iov[idx].iov_base = strings[idx];
+ iov[idx].iov_len = ((evmctld_string *)strings[idx])->size +
+ sizeof(evmctld_string);
+ }
+
+ msg.msg_iov = iov;
+ msg.msg_iovlen = nr_strings;
+
+ n = sendmsg(sd, &msg, 0);
+ if (n < 0) {
+ fprintf(stderr, "evmctl-client: set pin: sendmsg failed: "
+ "%m\n");
+ exit(1);
+ }
+
+ char *srvmsg = NULL;
+ int rc = check_response(sd, &srvmsg);
+ if (rc < 0) {
+ fprintf(stderr, "evmctl-client: %s\n",
+ srvmsg);
+ exit(1);
+ }
+
+ /* Free strings array */
+ for (idx = 0; idx < max_nr_strings; idx++)
+ free(strings[idx]);
+
+ return 0;
+}
+
+static int cmd_ima_sign(struct command *cmd, struct client_info *ci)
+{
+ char *file = g_argv[optind++];
+ int sd = -1, rc = 0;
+
+ if (!file) {
+ log_err("Parameters missing\n");
+ print_usage(cmd);
+ return -1;
+ }
+
+ ci->file = file;
+
+ if (!ci->key) {
+ log_err("Key missing\n");
+ print_usage(cmd);
+ return -1;
+ }
+
+ /* If a token is specified, convert it to slot id */
+ if (ci->token_label) {
+ char *temp = strdup(ci->key);
+ if (!temp) {
+ log_err("Memory allocation failed\n");
+ return -ENOMEM;
+ }
+ rc = parse_token_label(ci, ci->token_label, temp, &ci->key);
+ free(temp);
+ if (rc) {
+ log_err("Parsing token label failed\n");
+ return rc;
+ }
+ }
+ sd = connect_to_server();
+
+ return sign_ima(sd, ci);
+
+}
+
+static int cmd_set_pin(struct command *cmd, struct client_info *ci)
+{
+ char *pin = g_argv[optind++];
+ int sd = -1;
+
+ if (!pin) {
+ log_err("Parameters missing\n");
+ print_usage(cmd);
+ return -1;
+ }
+
+ sd = connect_to_server();
+ set_pin(sd, pin);
+ return 0;
+}
+
+static void print_usage(struct command *cmd)
+{
+ printf("usage: %s %s\n", cmd->name, cmd->arg ? cmd->arg :
"");
+}
+
+static void print_full_usage(struct command *cmd)
+{
+ if (cmd->name)
+ printf("usage: %s %s\n", cmd->name, cmd->arg ? cmd->arg :
"");
+ if (cmd->msg)
+ printf("%s", cmd->msg);
+}
+
+static int print_command_usage(struct command *cmds, char *command)
+{
+ struct command *cmd;
+
+ for (cmd = cmds; cmd->name; cmd++) {
+ if (strcmp(cmd->name, command) == 0) {
+ print_full_usage(cmd);
+ return 0;
+ }
+ }
+ printf("invalid command: %s\n", command);
+ return -1;
+}
+
+static void print_all_usage(struct command *cmds)
+{
+ struct command *cmd;
+
+ printf("commands:\n");
+
+ for (cmd = cmds; cmd->name; cmd++) {
+ if (cmd->arg)
+ printf(" %s %s\n", cmd->name, cmd->arg);
+ else if (cmd->msg)
+ printf(" %s", cmd->msg);
+ }
+}
+
+static int
+call_command(struct command *cmds, char *command, struct client_info *ci)
+{
+ struct command *cmd;
+
+ for (cmd = cmds; cmd->name; cmd++) {
+ if (strcasecmp(cmd->name, command) == 0)
+ return cmd->func(cmd, ci);
+ }
+ printf("Invalid command: %s\n", command);
+ return -1;
+}
+
+static int cmd_help(struct command *cmd, struct client_info *ci)
+{
+ if (!g_argv[optind]) {
+ print_usage(cmd);
+ return 0;
+ } else
+ return print_command_usage(cmds, g_argv[optind]);
+}
+
+static void usage(void)
+{
+ printf("Usage: evmctl [-v] <command> [OPTIONS]\n");
+
+ print_all_usage(cmds);
+
+ printf(
+ "\n"
+ " -k, --key path to signing key (default keys are
/etc/keys/{privkey,pubkey}_evm.pem)\n"
+ " -l, --memlock run executable file locked in memory.\n"
+ " -t, --token token label to use\n"
+ " -f, --sigfile Generate detached signature.\n"
+ " -c, --pkcs11_module Specify pkcs11 module to use. Needed if token label is
used.\n"
+ " -v increase verbosity level\n"
+ " -h, --help display this help and exit\n"
+ "\n");
+}
+
+struct command cmds[] = {
+ {"help", cmd_help, 0, "<command>"},
+ {"set_pin", cmd_set_pin, 0, "pin", "Set evmctld engine
pin.\n"},
+ {"ima_sign", cmd_ima_sign, 0, "--key key [--token token_label]
[--sigfile] [--memlock] file", "Make file content signature.\n"},
+ {0, 0, 0, NULL}
+};
+
+static struct option opts[] = {
+ {"help", 0, 0, 'h'},
+ {"sigfile", 0, 0, 'f'},
+ {"key", 1, 0, 'k'},
+ {"memlock", 0, 0, 'l'},
+ {"token", 1, 0, 't'},
+ {"pkcs11_module", 1, 0, 'c'},
+ {}
+
+};
+
+int main(int argc, char *argv[])
+{
+ int err = 0, c, lind;
+ struct client_info client_info, *ci;
+
+
+ g_argv = argv;
+ g_argc = argc;
+
+ ci = &client_info;
+ memset(ci, 0, sizeof(struct client_info));
+
+ while (1) {
+ c = getopt_long(argc, argv, "hfk:lt:", opts, &lind);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ usage();
+ exit(0);
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'f':
+ ci->detached = true;
+ break;
+ case 'k':
+ ci->key = optarg;
+ break;
+ case 'l':
+ ci->memlock = true;
+ break;
+ case 't':
+ ci->token_label = optarg;
+ break;
+ case 'c':
+ ci->pkcs11_module = optarg;
+ break;
+ case '?':
+ exit(1);
+ break;
+ default:
+ log_err("getopt() returned: %d (%c)\n", c, c);
+ }
+ }
+
+ if (argv[optind] == NULL)
+ usage();
+ else
+ err = call_command(cmds, argv[optind++], ci);
+
+ if (err)
+ log_err("evmctl-client exited with error %d\n, err");
+
+ return err;
+}
--
1.8.3.1