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@xxxxxxxxxx> --- 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@xxxxxxxxxx> + * + * 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 _______________________________________________ kernel mailing list kernel@xxxxxxxxxxxxxxxxxxxxxxx https://admin.fedoraproject.org/mailman/listinfo/kernel