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(a)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;
}
--
1.8.3.1