Hi,
This is an RFC patch series to get early feedback on stuff I am working on.
This series does few things.
- Adds an extra structure to ima signature (security.ima) which will signal the elf loader that this executable needs to be locked. This will be useful for secureboot where signed /sbin/kexec needs to run memory locked.
I have posted RFC kernel patches on Fedora kernel mailing list.
https://lists.fedoraproject.org/pipermail/kernel/2013-September/004432.html
kexec-tools patches are posted here.
https://lists.fedoraproject.org/pipermail/kernel/2013-September/004469.html
- Add a functionality to import signatures signed externally. (Patch 2) - Add functionality to allow signing using external crypto card. (Patch 3) - Add a functionality to create a daemon which cilents can connect to and request file signing (Patch 4 and Patch 5).
All the signing enhancements I need so that various build servers can make use of it to sign /sbin/kexec and bzImage using appropriate keys.
This is still a work in progress and code is very raw. I wanted to get the code out to get early feedback.
Thanks Vivek
Vivek Goyal (5): evmctl: Allow adding a memlock information in security.ima evmctl: Allow importing external signature evmctl: Allow signing using external crypto engine evmctl-allow-launching-daemon evmctl-client: A simple client to request signing from evmctl daemon
configure.ac | 1 + src/Makefile.am | 9 +- src/client.c | 697 +++++++++++++++++++++++++++++++++ src/daemon.h | 83 ++++ src/evmctl.c | 1166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 1934 insertions(+), 22 deletions(-) create mode 100644 src/client.c create mode 100644 src/daemon.h
Signed executables need to run locked in memory otherwise it might happen that they can be swapped out and then there is a possiblity that these can be attacked by directly writing to swap.
So add a memlock structure in security.ima xattr. Kernel will parse it and memlock the executable file if signature verification was successful.
Currently this will happen only for elf binaries.
Signed-off-by: Vivek Goyal vgoyal@redhat.com --- src/evmctl.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 4 deletions(-)
diff --git a/src/evmctl.c b/src/evmctl.c index aa61338..e24b9ed 100644 --- a/src/evmctl.c +++ b/src/evmctl.c @@ -45,6 +45,7 @@ #include <attr/xattr.h> #include <dirent.h> #include <ctype.h> +#include <stdbool.h>
#include <openssl/sha.h> #include <openssl/rsa.h> @@ -165,6 +166,14 @@ struct signature_v2_hdr { uint8_t sig[0]; /* signature payload */ } __attribute__ ((packed));
+/* memlocking info header */ +#define MEMLOCK_MAGIC_STR "MEMLOCK" +struct memlock_hdr { + uint8_t magic_str[8]; /* magic to detect memlock hdr presence */ + uint8_t version; /* memlock info hdr version */ + uint8_t memlock_file; /* If set, run executable locked in memory */ +} __attribute__ ((packed)); +
/* * Hash algorithm OIDs plus ASN.1 DER wrappings [RFC4880 sec 5.2.2]. @@ -258,6 +267,7 @@ static char *uuid_str; static int x509; static int user_sig_type; static char *keyfile; +static bool memlock = false;
typedef int (*sign_hash_fn_t)(const char *algo, const unsigned char *hash, int size, const char *keyfile, unsigned char *sig);
@@ -1021,12 +1031,23 @@ static int cmd_hash_ima(struct command *cmd) return hash_ima(file); }
+static int add_memlock_info(unsigned char *ptr) +{ + struct memlock_hdr *memlock_hdr = (struct memlock_hdr *)ptr; + strcpy((char *)memlock_hdr->magic_str, MEMLOCK_MAGIC_STR); + + memlock_hdr->version = 1; + memlock_hdr->memlock_file = 1; + + return sizeof(struct memlock_hdr); +} + static int sign_ima(const char *file, const char *key) { unsigned char hash[64]; unsigned char sig[1024] = "\x03"; char magic[] = "This Is A Crypto Signed Module"; - int len, err; + int len, err, memlock_len = 0;
len = calc_hash(file, hash); if (len <= 1) @@ -1049,6 +1070,11 @@ static int sign_ima(const char *file, const char *key) return 0; }
+ if (memlock) { + memlock_len = add_memlock_info(sig + len); + len += memlock_len; + } + if (sigfile) bin2file(file, "sig", sig, len);
@@ -1262,11 +1288,27 @@ static int get_hash_algo_from_sig(unsigned char *sig) return -1; }
+static int get_digsig_len(const unsigned char *sig) +{ + uint16_t sz; + + if (sig[0] == 1) { + sz = *((uint16_t *)(sig + sizeof(struct signature_hdr))); + sz = __be16_to_cpu(sz); + return sizeof(struct signature_hdr) + 2 + (sz >> 3); + } else if (sig[0] == 2 ) { + sz = ((struct signature_v2_hdr *)sig)->sig_size; + return sizeof(struct signature_v2_hdr) + __be16_to_cpu(sz); + } + + return -EBADMSG; +} + static int verify_ima(const char *file) { unsigned char hash[64]; unsigned char sig[1024]; - int len, hashlen; + int len, hashlen, digsiglen; int sig_hash_algo; char *key;
@@ -1322,7 +1364,13 @@ static int verify_ima(const char *file) "/etc/keys/x509_evm.der" : "/etc/keys/pubkey_evm.pem";
- return verify_hash(hash, hashlen, sig + 1, len - 1, key); + digsiglen = get_digsig_len(sig + 1); + if (digsiglen < 0) { + log_err("Bad digital signature"); + return -1; + } + + return verify_hash(hash, hashlen, sig + 1, digsiglen, key); }
static int cmd_verify_ima(struct command *cmd) @@ -1629,6 +1677,7 @@ static void usage(void) " -p, --pass password for encrypted signing key\n" " -u, --uuid use file system UUID in HMAC calculation (EVM v2)\n" " -n print result to stdout instead of setting xattr\n" + " -l, --memlock run executable file locked in memory.\n" " -v increase verbosity level\n" " -h, --help display this help and exit\n" "\n"); @@ -1659,6 +1708,7 @@ static struct option opts[] = { {"uuid", 2, 0, 'u'}, {"x509", 0, 0, 'x'}, {"key", 1, 0, 'k'}, + {"memlock", 0, 0, 'l'}, {}
}; @@ -1674,7 +1724,7 @@ int main(int argc, char *argv[]) verify_hash = verify_hash_v1;
while (1) { - c = getopt_long(argc, argv, "hvnsda:p:fu::xk:", opts, &lind); + c = getopt_long(argc, argv, "hvnsda:p:fu::xk:l", opts, &lind); if (c == -1) break;
@@ -1724,6 +1774,9 @@ int main(int argc, char *argv[]) case 'k': keyfile = optarg; break; + case 'l': + memlock = true; + break; case '?': exit(1); break;
It is possible that one uses external cryptographic engine to create RSA PKCS1.5 signature of a file. Import these signatures and wrap it with IMA specific metadata to convert it into IMA signature.
For example, one could do following.
$ echo "Hello World" > /tmp/data.txt $ openssl dgst -sha256 -sign signing_key.priv.pem -out /tmp/data.sig /tmp/data.txt $ evmctl ima_sig_import -x -a sha256 -k signing_key.x509.der /tmp/data.sig /tmp/data.txt
# Verify signature $ evmctl ima_verify -k signing_key.x509.der /tmp/data.txt
Signed-off-by: Vivek Goyal vgoyal@redhat.com --- src/evmctl.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+)
diff --git a/src/evmctl.c b/src/evmctl.c index e24b9ed..3679e68 100644 --- a/src/evmctl.c +++ b/src/evmctl.c @@ -1133,6 +1133,89 @@ static int cmd_sign_evm(struct command *cmd) return sign_evm(file, key); }
+static int ima_sig_import_v2(const char *hashalgo, const char *keyfile, const char *rawsigfile, const char *file) +{ + unsigned char *rawsig; + int rawsigsz, len, ret = 0, memlock_len; + unsigned char sig[1024] = "\x03"; + char name[20]; + struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)(sig + 1); + RSA *key; + + rawsig = file2bin(rawsigfile, NULL, &rawsigsz); + if (!rawsig) { + log_err("Unable to read file %s\n", rawsigfile); + return 1; + } + + key = read_pub_key(keyfile); + if (!key) { + free(rawsig); + return 1; + } + + hdr->version = 2; + hdr->hash_algo = get_hash_algo(hashalgo); + + calc_keyid_v2(&hdr->keyid, name, key); + memcpy(hdr->sig, rawsig, rawsigsz); + + len = rawsigsz; + hdr->sig_size = __cpu_to_be16(len); + len += sizeof(*hdr); + log_info("evm/ima signature: %d bytes\n", len); + if (sigdump || verbose >= LOG_INFO) + dump(sig + 1, len); + + /* Add ima header */ + len++; + if (memlock) { + memlock_len = add_memlock_info(sig + len); + len += memlock_len; + } + + if (sigfile) + bin2file(file, "sig", sig, len); + + if (xattr) { + ret = setxattr(file, "security.ima", sig, len, 0); + if (ret < 0) { + log_err("setxattr failed: %s\n", file); + goto out; + } + } + +out: + RSA_free(key); + free(rawsig); + return ret; +} + +static int cmd_ima_sig_import(struct command *cmd) +{ + char *rawsigfile, *file, *key; + + rawsigfile = g_argv[optind++]; + file = g_argv[optind++]; + + if (!file || !rawsigfile) { + log_err("Parameters missing\n"); + print_usage(cmd); + return -1; + } + + key = keyfile ? : x509 ? + "/etc/keys/x509_evm.der" : + "/etc/keys/pubkey_evm.pem"; + + if (x509) + return ima_sig_import_v2(hash_algo, key, rawsigfile, file); + else { + log_err("Signature version 1 import not supported.\n"); + return 1; + } +} + static int verify_hash_v1(const unsigned char *hash, int size, unsigned char *sig, int siglen, const char *keyfile) { int err, len; @@ -1694,6 +1777,7 @@ struct command cmds[] = { #ifdef DEBUG {"hmac", cmd_hmac_evm, 0, "[--imahash | --imasig ] file", "Sign file metadata with HMAC using symmetric key (for testing purpose).\n"}, #endif + {"ima_sig_import", cmd_ima_sig_import, 0, "[--x509] [-a hashalgo] [-k pubkey] signature file", "Make IMA signature from externally signed raw signature blob.\n"}, {0, 0, 0, NULL} };
evmctl uses openssl API for signing a file. One might want to use a external crypto engine for signing. For example, I am trying to use a smartcard for signing. Openssl provides engine API to deal with external crypto.
This patch adds support where one can specify external engine to load and then sign a file using that.
For example, I am doing this on my machine.
#Sign a file evmctl ima_sign -e pkcs11 -x -v --key slot_1-id_bb82253c8ab1337a74b95a6c68da4a658859c71a /tmp/data.txt -p <enter-pin> --engine_so /usr/lib64/openssl/engines/engine_pkcs11.so --engine_module opensc-pkcs11.so
Or
evmctl ima_sign -v -x --key id_bb82253c8ab1337a74b95a6c68da4a658859c71a /tmp/data.txt -p <enter-pin> -e pkcs11 --engine_so /usr/lib64/openssl/engines/engine_pkcs11.so --engine_module opensc-pkcs11.so -t "OpenSC Card (Fedora Signing CA)" --pkcs11_module opensc-pkcs11.so
# Verify signature evmctl ima_verify /tmp/data.txt -k signer-x509-cert.der
Signed-off-by: Vivek Goyal vgoyal@redhat.com --- configure.ac | 1 + src/Makefile.am | 2 +- src/evmctl.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 280 insertions(+), 6 deletions(-)
diff --git a/configure.ac b/configure.ac index 5decc4f..137c88a 100644 --- a/configure.ac +++ b/configure.ac @@ -27,6 +27,7 @@ AC_HEADER_STDC PKG_CHECK_MODULES(OPENSSL, [ openssl >= 0.9.8 ]) AC_SUBST(OPENSSL_CFLAGS) AC_SUBST(OPENSSL_LIBS) +PKG_CHECK_MODULES(OPENSC,libp11) AC_CHECK_HEADER(unistd.h) AC_CHECK_HEADERS(openssl/conf.h)
diff --git a/src/Makefile.am b/src/Makefile.am index 6779baf..472479f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,7 +4,7 @@ bin_PROGRAMS = evmctl evmctl_SOURCES = evmctl.c evmctl_CPPFLAGS = $(OPENSSL_CFLAGS) evmctl_LDFLAGS = $(LDFLAGS_READLINE) -evmctl_LDADD = $(OPENSSL_LIBS) -lkeyutils +evmctl_LDADD = $(OPENSSL_LIBS) -lkeyutils -lp11
INCLUDES = -I$(top_srcdir) -include config.h
diff --git a/src/evmctl.c b/src/evmctl.c index 3679e68..2205d1e 100644 --- a/src/evmctl.c +++ b/src/evmctl.c @@ -46,6 +46,7 @@ #include <dirent.h> #include <ctype.h> #include <stdbool.h> +#include <libp11.h>
#include <openssl/sha.h> #include <openssl/rsa.h> @@ -260,7 +261,7 @@ static int digest; static int digsig; static const char *hash_algo = "sha1"; static int user_hash_algo; -static char *keypass; +static char *keypass = NULL; static int sigfile; static int modsig; static char *uuid_str; @@ -268,6 +269,12 @@ static int x509; static int user_sig_type; static char *keyfile; static bool memlock = false; +static char *engine_id = NULL; +static char *engine_so = NULL; +static char *engine_module = NULL; +static ENGINE *engine = NULL; +static char *token_label = NULL; +static char *pkcs11_module = NULL;
typedef int (*sign_hash_fn_t)(const char *algo, const unsigned char *hash, int size, const char *keyfile, unsigned char *sig);
@@ -475,11 +482,34 @@ static void calc_keyid_v2(uint32_t *keyid, char *str, RSA *key) free(pkey); }
+static RSA *read_priv_key_from_engine(ENGINE *e, const char *keyfile) +{ + RSA *key; + EVP_PKEY *evp_key = NULL; + + evp_key = ENGINE_load_private_key(e, keyfile, NULL, NULL); + if (!evp_key) { + log_err("ENGINE_load_private_key: key=%s failed\n", keyfile); + return NULL; + } + + key = EVP_PKEY_get1_RSA(evp_key); + if (!key) { + log_err("Getting RSA key from EVP_PKEY failed\n"); + return NULL; + } + + return key; +} + static RSA *read_priv_key(const char *keyfile) { FILE *fp; RSA *key;
+ if (engine_id != NULL) + return read_priv_key_from_engine(engine, keyfile); + fp = fopen(keyfile, "r"); if (!fp) { log_err("Unable to open keyfile %s\n", keyfile); @@ -992,6 +1022,205 @@ static int calc_hash(const char *file, uint8_t *hash) return mdlen; }
+static int token_label_to_slot_id(char *tk_label) +{ + int rc = -1; + PKCS11_CTX *ctx; + PKCS11_SLOT *slots, *slot, *temp_slots; + unsigned int nslots, temp_nslots; + + if (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, 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", + token_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(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(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 void unload_engine(ENGINE *e) +{ + /* Free up functional reference */ + if (e != NULL) + ENGINE_finish(e); + + ENGINE_cleanup(); +} + +static ENGINE *load_engine(void) +{ + ENGINE *e; + + if (engine_id == NULL) { + log_err("Provide an engine_id\n"); + return NULL; + } + + if (engine_so == NULL) { + log_err("Provide engine shared library (engine_so)\n"); + return NULL; + } + + if (engine_module == NULL) { + log_err("Provide engine module (engine_module)\n"); + return NULL; + } + + ENGINE_load_dynamic(); + + /* TODO: Allow using built-in engines */ + e = ENGINE_by_id("dynamic"); + if (e == NULL) { + log_err("ENGINE_by_id(dynamic) failed\n"); + return NULL; + } + + if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", engine_so, 0)) { + log_err("ENGINE_ctrl_cmd_string(SO_PATH,%s) failed\n", + engine_so); + goto out; + } + + if (!ENGINE_ctrl_cmd_string(e, "ID", engine_id, 0)) { + log_err("ENGINE_ctrl_cmd_string(ID,%s) failed\n", + engine_id); + goto out; + } + + if (!ENGINE_ctrl_cmd_string(e, "LIST_ADD", "1", 0)) { + log_err("ENGINE_ctrl_cmd_string(LIST_ADD,1) failed\n"); + goto out; + } + + if (!ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) { + log_err("ENGINE_ctrl_cmd_string(LOAD) failed\n"); + goto out; + } + + if (!ENGINE_ctrl_cmd_string(e, "MODULE_PATH", engine_module, 0)) { + log_err("ENGINE_ctrl_cmd_string(MODULE_PATH,%s) failed\n", + engine_module); + goto out; + } + + log_info("Using engine with engine_id: %s\n", ENGINE_get_id(e)); + + ENGINE_set_default(e, ENGINE_METHOD_ALL); + + if (!ENGINE_init(e)) { + log_err("ENGINE_init(e) failed\n"); + goto out; + } + + /* Engine initialized. Free up structural reference */ + ENGINE_free(e); + + /* Use keypass as PIN to login into the card */ + if (keypass != NULL) + ENGINE_ctrl_cmd_string(e, "PIN", keypass, 0); + + + ENGINE_set_default(e, ENGINE_METHOD_RSA); + return e; + +out: + if (e) + ENGINE_free(e); + return NULL; +} + static int hash_ima(const char *file) { unsigned char hash[65] = "\x01"; /* MAX hash size + 1 */ @@ -1761,6 +1990,11 @@ static void usage(void) " -u, --uuid use file system UUID in HMAC calculation (EVM v2)\n" " -n print result to stdout instead of setting xattr\n" " -l, --memlock run executable file locked in memory.\n" + " -e, --engine_id Specify engine id to use for signing (pkcs11)\n" + " -g, --engine_so Specify engine library/shared object file\n" + " -b --engine_module Specify engine module file path\n" + " -c, --pkcs11_module Specify pkcs11 module file path\n" + " -t, --token token label to use\n" " -v increase verbosity level\n" " -h, --help display this help and exit\n" "\n"); @@ -1793,6 +2027,11 @@ static struct option opts[] = { {"x509", 0, 0, 'x'}, {"key", 1, 0, 'k'}, {"memlock", 0, 0, 'l'}, + {"engine_id", 1, 0, 'e'}, + {"engine_so", 1, 0, 'g'}, + {"engine_module", 1, 0, 'b'}, + {"pkcs11_module", 1, 0, 'c'}, + {"token", 1, 0, 't'}, {}
}; @@ -1808,7 +2047,8 @@ int main(int argc, char *argv[]) verify_hash = verify_hash_v1;
while (1) { - c = getopt_long(argc, argv, "hvnsda:p:fu::xk:l", opts, &lind); + c = getopt_long(argc, argv, "hvnsda:p:fu::xk:le:t:c:g:b:", + opts, &lind); if (c == -1) break;
@@ -1861,6 +2101,21 @@ int main(int argc, char *argv[]) case 'l': memlock = true; break; + case 'e': + engine_id = optarg; + break; + case 'g': + engine_so = optarg; + break; + case 'b': + engine_module = optarg; + break; + case 't': + token_label = optarg; + break; + case 'c': + pkcs11_module = optarg; + break; case '?': exit(1); break; @@ -1872,10 +2127,26 @@ int main(int argc, char *argv[]) OpenSSL_add_all_algorithms(); ERR_load_crypto_strings();
- if (argv[optind] == NULL) + if (argv[optind] == NULL) { usage(); - else - err = call_command(cmds, argv[optind++]); + goto out; + } + + if (token_label) { + char *temp_key = strdup(keyfile); + err = parse_token_label(token_label, temp_key, &keyfile); + free(temp_key); + if (err) + goto out; + } + + if (engine_id) { + engine = load_engine(); + if (engine == NULL) + goto out; + } + + err = call_command(cmds, argv[optind++]);
if (err) { unsigned long error; @@ -1889,8 +2160,10 @@ int main(int argc, char *argv[]) } }
+out: ERR_free_strings(); EVP_cleanup(); + unload_engine(engine);
return err; }
Launch evmctl as daemon so that clients can connect to it and request file signing. This can be useful when signing needs to happen using an crypto card and client does not know the pin of the card. Machine owner can setup evmctl daemon, set pin and later clients can request file signing without knowing pin.
I have taken lot of daemon and client code from pesign project (written by peter jones).
Signed-off-by: Vivek Goyal vgoyal@redhat.com --- src/daemon.h | 83 +++++++ src/evmctl.c | 776 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 829 insertions(+), 30 deletions(-) create mode 100644 src/daemon.h
diff --git a/src/daemon.h b/src/daemon.h new file mode 100644 index 0000000..0a29ae6 --- /dev/null +++ b/src/daemon.h @@ -0,0 +1,83 @@ +/* + * Copyright 2013 Red Hat, 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; version 2 of the License. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + * Author(s): Vivek Goyal vgoyal@redhat.com + */ +#ifndef DAEMON_H +#define DAEMON_H 1 + +/* Daemon related stuff */ +#define SOCKPATH "/var/run/evmctl/socket" +#define PIDFILE "/var/run/evmctld.pid" +#define EVMCTLD_VERSION 0xb4d052dc + +extern int daemonize(void); + +typedef struct { + uint32_t version; + uint32_t command; + uint32_t size; +} evmctld_msghdr; + +typedef struct { + uint32_t rc; + uint8_t errmsg[]; +} evmctld_cmd_response; + +typedef struct { + uint32_t size; + uint8_t value[]; +} evmctld_string; + +typedef enum { + CMD_KILL_DAEMON, + CMD_SET_PIN, + CMD_IMA_SIGN_ATTACHED, + CMD_IMA_SIGN_DETACHED, + CMD_RESPONSE, + CMD_LIST_END +} evmctld_cmd; + +static inline uint32_t +__attribute__ ((unused)) +evmctld_string_size(char *buffer) +{ + evmctld_string *s; + return sizeof(s->size) + (buffer ? strlen(buffer) : 0) + 1; +} + +static inline void +__attribute__ ((unused)) +evmctld_string_set(evmctld_string *str, char *value) +{ + str->size = (value ? strlen(value) : 0) + 1; + if (value) + strcpy((char *)str->value, value); + else + str->value[0] = '\0'; +} + +static inline evmctld_string * +__attribute__ ((unused)) +evmctld_string_next(evmctld_string *str) +{ + char *buffer = (char *)str; + buffer += sizeof(str->size) + str->size; + return (evmctld_string *)buffer; +} + + +#endif /* DAEMON_H */ diff --git a/src/evmctl.c b/src/evmctl.c index 2205d1e..6638410 100644 --- a/src/evmctl.c +++ b/src/evmctl.c @@ -26,6 +26,7 @@ * IMA/EVM control program */
+#define _GNU_SOURCE #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> @@ -48,6 +49,11 @@ #include <stdbool.h> #include <libp11.h>
+#include <sys/socket.h> +#include <sys/un.h> +#include <sys/prctl.h> +#include <poll.h> + #include <openssl/sha.h> #include <openssl/rsa.h> #include <openssl/pem.h> @@ -57,15 +63,9 @@ #include <openssl/err.h> #include <openssl/x509.h>
-#define USE_FPRINTF +#include "daemon.h"
-#ifdef USE_FPRINTF -#define do_log(level, fmt, args...) ({ if (level <= verbose) fprintf(stderr, fmt, ##args); }) -#define do_log_dump(level, p, len) ({ if (level <= verbose) do_dump(stderr, p, len); }) -#else -#define do_log(level, fmt, args...) syslog(level, fmt, ##args) -#define do_log_dump(p, len) -#endif +#define USE_FPRINTF
#ifdef DEBUG #define log_debug(fmt, args...) do_log(LOG_DEBUG, "%s:%d " fmt, __func__ , __LINE__ , ##args) @@ -77,6 +77,8 @@
#define log_dump(p, len) do_log_dump(LOG_INFO, p, len) #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)
@@ -91,6 +93,10 @@ #define FS_IOC32_GETFLAGS _IOR('f', 1, int) #define FS_IOC32_SETFLAGS _IOW('f', 2, int)
+static int should_exit = 0; +static int socket_desc; +static bool daemon_mode = false; + struct h_misc { unsigned long ino; uint32_t generation; @@ -252,6 +258,14 @@ struct command { char *msg; /* extra info message */ };
+/* When in daemon mode, this keep info about client context */ +struct client_context { + bool memlock; /* Client wants to memlock file */ + char *token_label; + char *key; + char *file; +}; + static int verbose = LOG_INFO - 1; static int g_argc; static char **g_argv; @@ -287,6 +301,26 @@ static verify_hash_fn_t verify_hash; 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); + if (daemon_mode == true) { + vsyslog(level, fmt, ap); + va_end(ap); + return; + } + +#ifdef USE_FPRINTF + if (level <= verbose) + vfprintf(stderr, fmt, ap); +#else + vsyslog(level, fmt, ap); +#endif + va_end(ap); +} + static void do_dump(FILE *fp, const void *ptr, int len) { int i; @@ -297,8 +331,26 @@ static void do_dump(FILE *fp, const void *ptr, int len) fprintf(fp, "\n"); }
+static void do_log_dump(int level, const void *p, int len) +{ +#ifdef USE_FPRINTF + if (level > verbose) + return; + + /* May be we can dump to syslog in daemon mode ? */ + if (daemon_mode == true) + return; + + do_dump(stderr, p, len); +#endif +} + static void dump(const void *ptr, int len) { + /* May be we can dump to syslog in daemon mode ? */ + if (daemon_mode == true) + return; + do_dump(stdout, ptr, len); }
@@ -1133,10 +1185,10 @@ static int parse_token_label(char *tk_label, char *key, char **newkey) static void unload_engine(ENGINE *e) { /* Free up functional reference */ - if (e != NULL) + if (e != NULL) { ENGINE_finish(e); - - ENGINE_cleanup(); + ENGINE_cleanup(); + } }
static ENGINE *load_engine(void) @@ -1271,7 +1323,8 @@ static int add_memlock_info(unsigned char *ptr) return sizeof(struct memlock_hdr); }
-static int sign_ima(const char *file, const char *key) +static int sign_ima(const char *file, const char *key, + struct client_context *cc) { unsigned char hash[64]; unsigned char sig[1024] = "\x03"; @@ -1299,7 +1352,7 @@ static int sign_ima(const char *file, const char *key) return 0; }
- if (memlock) { + if (memlock || (cc && cc->memlock)) { memlock_len = add_memlock_info(sig + len); len += memlock_len; } @@ -1321,6 +1374,7 @@ static int sign_ima(const char *file, const char *key) static int cmd_sign_ima(struct command *cmd) { char *key, *file = g_argv[optind++]; + int rc;
if (!file) { log_err("Parameters missing\n"); @@ -1330,7 +1384,23 @@ static int cmd_sign_ima(struct command *cmd)
key = keyfile ? : "/etc/keys/privkey_evm.pem";
- return sign_ima(file, key); + if (token_label) { + char *temp_key = strdup(keyfile); + rc = parse_token_label(token_label, temp_key, &keyfile); + free(temp_key); + if (rc) + return -1; + } + + if (engine_id) { + engine = load_engine(); + if (engine == NULL) { + log_err("Failed to load engine\n"); + return -1; + } + } + + return sign_ima(file, key, NULL);
}
@@ -1348,7 +1418,7 @@ static int cmd_sign_evm(struct command *cmd) key = keyfile ? : "/etc/keys/privkey_evm.pem";
if (digsig) { - err = sign_ima(file, key); + err = sign_ima(file, key, NULL); if (err) return err; } @@ -1420,6 +1490,665 @@ out: return ret; }
+static void write_pid_file(int pid) +{ + int fd = open(PIDFILE, O_WRONLY|O_CREAT|O_TRUNC, 0644); + if (fd < 0) { +err: + fprintf(stderr, "couldn't open pidfile: %m\n"); + exit(1); + } + char *pidstr = NULL; + int rc = asprintf(&pidstr, "%d\n", pid); + if (rc < 0) + goto err; + + rc = write(fd, pidstr, strlen(pidstr)+1); + if (rc < 0) + goto err; + + free(pidstr); + close(fd); +} + +static void check_socket(void) +{ + errno = 0; + int rc = access(SOCKPATH, R_OK); + if (rc == 0) { + struct sockaddr_un addr_un = { + .sun_family = AF_UNIX, + .sun_path = SOCKPATH, + }; + + int sd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (sd < 0) { + fprintf(stderr, "unable to create socket: %m"); + 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) { + close(sd); + unlink(SOCKPATH); + return; + } + + struct sockaddr_un remote; + socklen_t size = sizeof(remote); + rc = getpeername(sd, &remote, &size); + if (rc < 0) { + close(sd); + return; + } else { + fprintf(stderr, "already running"); + exit(1); + } + } else { + /* It could be something other than EEXIST, but it really + * doesn't matter since the daemon isn't running. Blindly + * remove it. */ + unlink(SOCKPATH); + } +} + +static void daemon_setup_std_descriptors(void) +{ + int rc; + + /* Handle stdio, stdout and stderr */ + int fd = open("/dev/zero", O_RDONLY); + if (fd < 0) { + log_err("could not open /dev/zero: %m"); + exit(1); + } + close(STDIN_FILENO); + + rc = dup2(fd, STDIN_FILENO); + if (rc < 0) { + log_err("could not set up standard input: %m"); + exit(1); + } + close(fd); + + fd = open("/dev/null", O_WRONLY); + if (fd < 0) { + log_err("could not open /dev/null: %m"); + exit(1); + } + close(STDOUT_FILENO); + + rc = dup2(fd, STDOUT_FILENO); + if (rc < 0) { + log_err("could not set up standard output: %m"); + exit(1); + } + close(STDERR_FILENO); + + rc = dup2(fd, STDERR_FILENO); + if (rc < 0) { + log_err("could not set up standard error: %m"); + exit(1); + } + close(fd); +} + +static void quit_handler(int signal) +{ + should_exit = 1; +} + +static int set_up_socket(void) +{ + int sd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sd < 0) { + log_err("unable to create socket: %m"); + exit(1); + } + + int one = 1; + int rc = setsockopt(sd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); + if (rc < 0) { + log_err("unable to set socket options: %m"); + exit(1); + } + + struct sockaddr_un addr_un = { + .sun_family = AF_UNIX, + .sun_path = SOCKPATH, + }; + + rc = bind(sd, &addr_un, sizeof(addr_un)); + if (rc < 0) { + log_err("unable to bind to "%s": %m", addr_un.sun_path); + exit(1); + } + rc = chmod(SOCKPATH, 0660); + if (rc < 0) { + log_err("could not set permissions for "%s": %m", SOCKPATH); + exit(1); + } + + rc = listen(sd, 5); + if (rc < 0) { + log_err("unable to listen on socket: %m"); + exit(1); + } + + socket_desc = sd; + return 0; +} + +static void send_response(struct pollfd *pollfd, int rc, char *response) +{ + struct msghdr msg; + struct iovec iov; + ssize_t n; + int msglen = response ? strlen(response) + 1 : 0; + + iov.iov_len = sizeof(evmctld_msghdr) + sizeof(evmctld_cmd_response) + + msglen; + + void *buffer = calloc(1, iov.iov_len); + if (!buffer) { + log_err("could not allocate memory: %m"); + exit(1); + } + + iov.iov_base = buffer; + + evmctld_msghdr *em = buffer; + evmctld_cmd_response *resp = (evmctld_cmd_response *)((uint8_t *)em + + offsetof(evmctld_msghdr, size) + + sizeof (em->size)); + + em->version = EVMCTLD_VERSION; + em->command = CMD_RESPONSE; + em->size = sizeof(resp->rc) + msglen; + + memset(&msg, '\0', sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + resp->rc = rc; + + if (response) + memcpy(resp->errmsg, response, msglen); + + n = sendmsg(pollfd->fd, &msg, 0); + if (n < 0) + log_warn("could not send response to client: %m"); + + free(buffer); +} + +static void handle_kill_daemon(struct pollfd *pollfd, socklen_t size) +{ + should_exit = 1; +} + +static void free_client_context(struct client_context *cc) +{ + if (cc == NULL) + return; + + if (cc->key) + free(cc->key); + if (cc->token_label) + free(cc->token_label); + free(cc); +} + +static void handle_ima_sign(struct pollfd *pollfd, socklen_t size) +{ + struct msghdr msg; + struct iovec iov; + ssize_t n; + evmctld_string *ekey = NULL, *etoken_label = NULL, *efile = NULL, *etemp; + bool ememlock = false; + char *key, *temp; + int rc; + struct client_context *cc; + + /* Allocate client context */ + cc = calloc(sizeof(struct client_context), 1); + if (!cc) + goto oom; + + char *buffer = malloc(size); + if (!buffer) { +oom: + log_err("unable to allocate memory: %m"); + exit(1); + } + + memset(&msg, '\0', sizeof(msg)); + + iov.iov_base = buffer; + iov.iov_len = size; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + n = recvmsg(pollfd->fd, &msg, MSG_WAITALL); + + etemp = (evmctld_string *)buffer; + if (n < sizeof(etemp->size)) { +malformed: + log_err("sign: invalid data\n"); + log_err("possible exploit attempt. closing.\n"); + close(pollfd->fd); + return; + } + + n -= sizeof(etemp->size); + if (n < etemp->size) + goto malformed; + n -= etemp->size; + + if (etemp->value[etemp->size - 1] != '\0') + goto malformed; + + ekey = etemp; + log_info("sign: key=%s.\n", (char *)ekey->value); + +next_string: + etemp = evmctld_string_next(etemp); + n -= sizeof(etemp->size); + if (n < etemp->size) + goto malformed; + n -= etemp->size; + + if (etemp->value[etemp->size - 1] != '\0') + goto malformed; + + log_info("sign: etemp->value=%s", (char *)etemp->value); + + if (etemp->size > 15 && + !strncmp((char *)etemp->value, "--token_label ", 14)) { + if (etoken_label != NULL) + goto malformed; + etoken_label = etemp; + temp = (char *)etemp->value + strlen("--token_label "); + cc->token_label = strdup(temp); + if (!cc->token_label) + goto oom; + } else if (etemp->size > 9 && + !strncmp((char *)etemp->value, "--memlock", 9)) { + if (ememlock != false) + goto malformed; + ememlock = true; + cc->memlock = true; + } else { + efile = etemp; + cc->file = strdup((char *)efile->value); + if (!cc->file) + goto oom; + } + + /* file to be signed is last string */ + if (!efile && n > 0) + goto next_string; + + if (n != 0) + goto malformed; + + if (!efile) { + log_info("Did not find efile\n"); + goto malformed; + } + + log_info("sign: key=%s efile=%s, memlock=%d token_label=%s\n", + ekey->value, efile->value, ememlock, + etoken_label ? etoken_label->value : NULL); + + if (sigfile) + log_notice("ima signing detached"); + else + log_notice("ima signing attached"); + + /* Strip --key from key */ + if (strncmp((char *)ekey->value, "--key ", 6)) { + /* key is not prefixed with --key. Bad message */ + goto malformed; + } + + key = (char *)ekey->value + strlen("--key "); + + /* Convert token label to slot and modify key accordingly */ + if (cc->token_label) { + rc = parse_token_label(cc->token_label, key, &cc->key); + if (rc) { + log_notice("Failed to parse token label"); + send_response(pollfd, rc, "Failed to parse token label"); + goto out; + } + } else { + cc->key = strdup(key); + if (!cc->key) + goto oom; + } + + rc = sign_ima((char *)cc->file, cc->key, cc); + if (!rc) { + log_notice("Signing successful"); + send_response(pollfd, rc, "Signing successful"); + } else { + log_notice("Signing Failed"); + send_response(pollfd, rc, "Signing Failed"); + } + +out: + free(buffer); + free_client_context(cc); +} + +static void handle_ima_sign_detached(struct pollfd *pollfd, socklen_t size) +{ + /* emulate passing -f */ + sigfile = 1; + xattr = 0; + handle_ima_sign(pollfd, size); +} + +static void handle_ima_sign_attached(struct pollfd *pollfd, socklen_t size) +{ + handle_ima_sign(pollfd, size); +} + +static void handle_set_pin(struct pollfd *pollfd, socklen_t size) +{ + struct msghdr msg; + struct iovec iov; + ssize_t n; + + char *buffer = malloc(size); + if (!buffer) { +oom: + log_err("unable to allocate memory: %m"); + exit(1); + } + + memset(&msg, '\0', sizeof(msg)); + + iov.iov_base = buffer; + iov.iov_len = size; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + n = recvmsg(pollfd->fd, &msg, MSG_WAITALL); + + evmctld_string *ep = (evmctld_string *)buffer; + if (n < sizeof(ep->size)) { +malformed: + log_err("set-pin: invalid data\n"); + log_err("possible exploit attempt. closing.\n"); + close(pollfd->fd); + return; + } + + n -= sizeof(ep->size); + if (n < ep->size) + goto malformed; + n -= ep->size; + + if (ep->value[ep->size - 1] != '\0') + goto malformed; + + if (n != 0) + goto malformed; + + log_notice("setting pin"); + + char *pin = (char *)ep->value; + if (!pin) + goto oom; + + if (engine) { + ENGINE_ctrl_cmd_string(engine, "PIN", pin, 0); + log_notice("set pin done."); + send_response(pollfd, 0, "set pin done"); + } else { + log_notice("No engine loaded. set pin failed.\n"); + send_response(pollfd, 1, "No engine loaded. set pin failed."); + } + + free(buffer); +} + +static void handle_invalid_input(evmctld_cmd cmd, struct pollfd *pollfd, + socklen_t size) +{ + log_err("got unexpected command 0x%x", cmd); + log_err("possible exploit attempt"); +} + +typedef void (*cmd_handler)(struct pollfd *pollfd, socklen_t size); + +typedef struct { + evmctld_cmd cmd; + cmd_handler func; +} cmd_table_t; + +cmd_table_t cmd_table[] = { + { CMD_KILL_DAEMON, handle_kill_daemon }, + { CMD_SET_PIN, handle_set_pin }, + { CMD_IMA_SIGN_ATTACHED, handle_ima_sign_attached }, + { CMD_IMA_SIGN_DETACHED, handle_ima_sign_detached }, + { CMD_RESPONSE, NULL }, + { CMD_LIST_END, NULL } + }; + +static int handle_event(struct pollfd *pollfd) +{ + struct msghdr msg; + struct iovec iov; + ssize_t n; + evmctld_msghdr em; + int i; + + msg.msg_name = NULL; + msg.msg_namelen = 0; + + iov.iov_base = &em; + iov.iov_len = sizeof(em); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char control[1024]; + msg.msg_controllen = 1024; + msg.msg_control = control; + + n = recvmsg(pollfd->fd, &msg, MSG_WAITALL); + if (n < 0) { + log_warn("recvmsg failed: %m"); + return n; + } + + /* if recvmsg returned 0, we're not going to get any valid data. */ + /* This *probably* means we were hung up on. */ + if (n == 0) + return n; + + if (n < sizeof (em)) { + log_err("got message with invalid size %zu", n); + log_err("possible exploit attempt. closing."); + close(pollfd->fd); + return -1; + } + + if (em.version != EVMCTLD_VERSION) { + log_err("got version %d, expected version %d", + em.version, EVMCTLD_VERSION); + log_err("possible exploit attempt. closing."); + close(pollfd->fd); + return -1; + } + + for (i = 0; cmd_table[i].cmd != CMD_LIST_END; i++) { + if (cmd_table[i].cmd == em.command) { + if (cmd_table[i].func == NULL) { + handle_invalid_input(em.command, pollfd, + em.size); + close(pollfd->fd); + } + cmd_table[i].func(pollfd, em.size); + return 0; + } + } + + handle_invalid_input(em.command, pollfd, em.size); + close(pollfd->fd); + return 0; +} + +static void do_shutdown(int nsockets, struct pollfd *pollfds) +{ + int i; + + unlink(SOCKPATH); + unlink(PIDFILE); + + log_notice("evmctld exiting (pid %d)", getpid()); + + for (i = 0; i < nsockets; i++) + close(pollfds[i].fd); + free(pollfds); +} + +static int handle_events(void) +{ + int rc; + int nsockets = 1; + int i, j; + + struct pollfd *pollfds = calloc(1, sizeof(struct pollfd)); + + if (!pollfds) { + log_err("could not allocate memory: %m"); + exit(1); + } + + pollfds[0].fd = socket_desc; + pollfds[0].events = POLLIN|POLLPRI|POLLHUP; + + while (1) { + if (should_exit != 0) { +shutdown: + do_shutdown(nsockets, pollfds); + return 0; + } + rc = ppoll(pollfds, nsockets, NULL, NULL); + if (should_exit != 0) + goto shutdown; + if (rc < 0) { + log_warn("ppoll: %m"); + continue; + } + + if (pollfds[0].revents & POLLIN) { + nsockets++; + struct pollfd *newpollfds = realloc(pollfds, + nsockets * sizeof(struct pollfd)); + + if (!newpollfds) { + log_err("could not allocate memory: %m"); + exit(1); + } + pollfds = newpollfds; + + struct sockaddr_un remote; + socklen_t len = sizeof(remote); + pollfds[nsockets-1].fd = accept(pollfds[0].fd, &remote, + &len); + pollfds[nsockets-1].events = POLLIN|POLLPRI|POLLHUP; + pollfds[nsockets-1].revents = pollfds[0].revents; + } + + for (i = 1; i < nsockets; i++) { + if (pollfds[i].revents & (POLLHUP|POLLNVAL)) { + close(pollfds[i].fd); + if (i == nsockets-1) { + nsockets--; + continue; + } + for (j = i; j < nsockets - 1; j++) { + pollfds[j].fd = pollfds[j+1].fd; + pollfds[j].events = + pollfds[j].events; + pollfds[j].revents = + pollfds[j].revents; + } + nsockets--; + i--; + continue; + } + + if (pollfds[i].revents & (POLLIN|POLLPRI)) + handle_event(&pollfds[i]); + } + } + return 0; +} + +static int cmd_daemonize(struct command *cmd) +{ + pid_t pid; + int rc; + + if (getuid() != 0) { + fprintf(stderr, "evmctl daemon must be started as root"); + exit(1); + } + + check_socket(); + + openlog("evmctld", LOG_PID, LOG_DAEMON); + + if ((pid = fork())) { + sleep(2); + return 0; + } + + daemon_mode = true; + + write_pid_file(getpid()); + + /* Load engine */ + if (engine_id) { + engine = load_engine(); + if (engine == NULL) { + log_err("Failed to load engine\n"); + exit(1); + } + } + + log_notice("evmctld starting(pid %d)", getpid()); + + /* Handle stdio, stdout and stderr */ + daemon_setup_std_descriptors(); + prctl(PR_SET_NAME, "evmctld", 0, 0, 0); + setsid(); + + struct sigaction sa = { + .sa_handler = quit_handler, + }; + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + chdir("/"); + + set_up_socket(); + + /* TODO: Handling related to password functions */ + + rc = handle_events(); + return 0; +} + static int cmd_ima_sig_import(struct command *cmd) { char *rawsigfile, *file, *key; @@ -1895,7 +2624,7 @@ static int cmd_hmac_evm(struct command *cmd) key = keyfile ? : "/etc/keys/privkey_evm.pem";
if (digsig) { - err = sign_ima(file, key); + err = sign_ima(file, key, NULL); if (err) return err; } @@ -2012,6 +2741,7 @@ struct command cmds[] = { {"hmac", cmd_hmac_evm, 0, "[--imahash | --imasig ] file", "Sign file metadata with HMAC using symmetric key (for testing purpose).\n"}, #endif {"ima_sig_import", cmd_ima_sig_import, 0, "[--x509] [-a hashalgo] [-k pubkey] signature file", "Make IMA signature from externally signed raw signature blob.\n"}, + {"daemonize", cmd_daemonize, 0, "", "Start evmctl daemon for signing\n"}, {0, 0, 0, NULL} };
@@ -2132,20 +2862,6 @@ int main(int argc, char *argv[]) goto out; }
- if (token_label) { - char *temp_key = strdup(keyfile); - err = parse_token_label(token_label, temp_key, &keyfile); - free(temp_key); - if (err) - goto out; - } - - if (engine_id) { - engine = load_engine(); - if (engine == NULL) - goto out; - } - err = call_command(cmds, argv[optind++]);
if (err) {
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@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@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; +}
On 06/09/13 22:38, Vivek Goyal wrote:
Hi,
This is an RFC patch series to get early feedback on stuff I am working on.
This series does few things.
Adds an extra structure to ima signature (security.ima) which will signal the elf loader that this executable needs to be locked. This will be useful for secureboot where signed /sbin/kexec needs to run memory locked.
I have posted RFC kernel patches on Fedora kernel mailing list.
https://lists.fedoraproject.org/pipermail/kernel/2013-September/004432.html
kexec-tools patches are posted here.
https://lists.fedoraproject.org/pipermail/kernel/2013-September/004469.html
Add a functionality to import signatures signed externally. (Patch 2)
Add functionality to allow signing using external crypto card. (Patch 3)
Add a functionality to create a daemon which cilents can connect to and request file signing (Patch 4 and Patch 5).
All the signing enhancements I need so that various build servers can make use of it to sign /sbin/kexec and bzImage using appropriate keys.
This is still a work in progress and code is very raw. I wanted to get the code out to get early feedback.
Thanks Vivek
Vivek Goyal (5): evmctl: Allow adding a memlock information in security.ima evmctl: Allow importing external signature evmctl: Allow signing using external crypto engine evmctl-allow-launching-daemon evmctl-client: A simple client to request signing from evmctl daemon
configure.ac | 1 + src/Makefile.am | 9 +- src/client.c | 697 +++++++++++++++++++++++++++++++++ src/daemon.h | 83 ++++ src/evmctl.c | 1166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 1934 insertions(+), 22 deletions(-) create mode 100644 src/client.c create mode 100644 src/daemon.h
Hi Vivek,
I am looking into patches..
It would be great if you could share your tree somewhere so that it would simplify pulling your code.
- Dmitry
kernel@lists.fedoraproject.org