[PATCH ima-evm-utils] Improve memory handling for private keys and passwords

Vitaly Chikunov
2021-08-12T21:21:58Z

After CRYPTO_secure_malloc_init OpenSSL will store private keys in
secure heap. This facility is only available since OpenSSL_1_1_0-pre1
and enabled for 'ima_sign', 'sign', 'sign_hash', and 'hmac'.

setvbuf(3) _IONBF is used to hopefully avoid private key and password
being stored inside of stdio buffers.

Unfortunately, secure heap is not used for the passwords (`-p') due to
absence of its support in the older OpenSSL where ima-evem-utils still
should work, thus simple cleansing of password memory is used where
possible to shorten its lifespan.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
---
Perhaps, it will conflict with Bruno's patch, we should decide which
one goes first if this will be accepted.

src/evmctl.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++---
src/libimaevm.c | 14 ++++++++-
2 files changed, 92 insertions(+), 5 deletions(-)

diff --git a/src/evmctl.c b/src/evmctl.c
index a8065bb..3275464 100644
--- a/src/evmctl.c
+++ b/src/evmctl.c
@@ -59,6 +59,7 @@
#include <assert.h>

#include <openssl/asn1.h>
+#include <openssl/crypto.h>
#include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/hmac.h>
@@ -165,6 +166,23 @@ struct tpm_bank_info {
static char *pcrfile[MAX_PCRFILE];
static unsigned npcrfile;

+static void init_openssl_secure_memory(void)
+{
+#if OPENSSL_VERSION_NUMBER > 0x10100000
+ /*
+ * man CRYPTO_secure_malloc_init for explanation of the values.
+ * 8192 is chosen to be big enough, so that any single key data could fit.
+ */
+# define SECURE_HEAP_SIZE 8192
+# define SECURE_HEAP_MINSIZE 64
+ /*
+ * Enable secure storage when working with private keys.
+ * This facility is available since OpenSSL_1_1_0-pre1.
+ */
+ CRYPTO_secure_malloc_init(SECURE_HEAP_SIZE, SECURE_HEAP_MINSIZE);
+#endif
+}
+
static int bin2file(const char *file, const char *ext, const unsigned char *data, int len)
{
FILE *fp;
@@ -221,6 +239,8 @@ static unsigned char *file2bin(const char *file, const char *ext, int *size)
fclose(fp);
return NULL;
}
+ /* Disable buffering because it's used to read private HMAC key too. */
+ setvbuf(fp, NULL, _IONBF, 0);
if (fread(data, len, 1, fp) != 1) {
log_err("Failed to fread %zu bytes: %s\n", len, name);
fclose(fp);
@@ -723,6 +743,7 @@ static int sign_ima_file(const char *file)

static int cmd_sign_ima(struct command *cmd)
{
+ init_openssl_secure_memory();
return do_cmd(cmd, sign_ima_file);
}

@@ -737,6 +758,7 @@ static int cmd_sign_hash(struct command *cmd)
unsigned char sig[MAX_SIGNATURE_SIZE] = "\x03";
int siglen;

+ init_openssl_secure_memory();
key = imaevm_params.keyfile ? : "/etc/keys/privkey_evm.pem";

/* support reading hash (eg. output of shasum) */
@@ -796,6 +818,7 @@ static int sign_evm_path(const char *file)

static int cmd_sign_evm(struct command *cmd)
{
+ init_openssl_secure_memory();
return do_cmd(cmd, sign_evm_path);
}

@@ -1104,6 +1127,7 @@ static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *h

if (keylen > sizeof(evmkey)) {
log_err("key is too long: %d\n", keylen);
+ OPENSSL_cleanse(key, keylen);
goto out;
}

@@ -1111,6 +1135,7 @@ static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *h
memcpy(evmkey, key, keylen);
if (keylen < sizeof(evmkey))
memset(evmkey + keylen, 0, sizeof(evmkey) - keylen);
+ OPENSSL_cleanse(key, keylen);

if (lstat(file, &st)) {
log_err("Failed to stat: %s\n", file);
@@ -1148,6 +1173,7 @@ static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *h
}

err = !HMAC_Init_ex(pctx, evmkey, sizeof(evmkey), md, NULL);
+ OPENSSL_cleanse(evmkey, sizeof(evmkey));
if (err) {
log_err("HMAC_Init() failed\n");
goto out;
@@ -1259,6 +1285,7 @@ static int cmd_hmac_evm(struct command *cmd)
const char *key, *file = g_argv[optind++];
int err;

+ init_openssl_secure_memory();
if (!file) {
log_err("Parameters missing\n");
print_usage(cmd);
@@ -2592,6 +2619,32 @@ static struct option opts[] = {

};

+/*
+ * Copy password from optarg into malloc'd memory, so it could be
+ * freed in the same way as a result of get_password.
+ */
+static char *optarg_password(char *optarg)
+{
+ size_t len;
+ char *keypass;
+
+ if (!optarg)
+ return NULL;
+ len = strlen(optarg);
+ keypass = malloc(len + 1);
+ if (keypass)
+ memcpy(keypass, optarg, len + 1);
+ else
+ perror("malloc");
+ /*
+ * This memset does not add real security, just increases
+ * the chance of password being obscured in ps output.
+ */
+ memset(optarg, 'X', len);
+ return keypass;
+}
+
+/* Read password from TTY. */
static char *get_password(void)
{
struct termios flags, tmp_flags;
@@ -2614,6 +2667,7 @@ static char *get_password(void)
free(password);
return NULL;
}
+ setvbuf(stdin, NULL, _IONBF, 0);

printf("PEM password: ");
pwd = fgets(password, passlen, stdin);
@@ -2621,8 +2675,10 @@ static char *get_password(void)
/* restore terminal */
if (tcsetattr(fileno(stdin), TCSANOW, &flags) != 0) {
perror("tcsetattr");
- free(password);
- return NULL;
+ /*
+ * Password is already here, so there is no point
+ * to stop working on this petty error.
+ */
}

return pwd;
@@ -2634,6 +2690,7 @@ int main(int argc, char *argv[])
ENGINE *eng = NULL;
unsigned long keyid;
char *eptr;
+ char *keypass = NULL;

#if !(OPENSSL_VERSION_NUMBER < 0x10100000)
OPENSSL_init_crypto(
@@ -2673,10 +2730,19 @@ int main(int argc, char *argv[])
imaevm_params.hash_algo = optarg;
break;
case 'p':
+ if (keypass) {
+ OPENSSL_cleanse(keypass, strlen(keypass));
+ free(keypass);
+ }
if (optarg)
- imaevm_params.keypass = optarg;
+ keypass = optarg_password(optarg);
else
- imaevm_params.keypass = get_password();
+ keypass = get_password();
+ if (!keypass) {
+ log_err("Cannot read password\n");
+ goto quit;
+ }
+ imaevm_params.keypass = keypass;
break;
case 'f':
sigfile = 1;
@@ -2833,6 +2899,15 @@ int main(int argc, char *argv[])
err = 125;
}

+quit:
+ if (keypass) {
+ OPENSSL_cleanse(keypass, strlen(keypass));
+ free(keypass);
+ }
+#if OPENSSL_VERSION_NUMBER > 0x10100000
+ CRYPTO_secure_malloc_done();
+#endif
+
if (eng) {
ENGINE_finish(eng);
ENGINE_free(eng);
diff --git a/src/libimaevm.c b/src/libimaevm.c
index 19f1041..7bf3ba9 100644
--- a/src/libimaevm.c
+++ b/src/libimaevm.c
@@ -726,6 +726,10 @@ static int read_keyid_from_cert(uint32_t *keyid_be, const char *certfile, int tr
log_err("Cannot open %s: %s\n", certfile, strerror(errno));
return -1;
}
+
+ /* There could be private key, thus disable buffering. */
+ setvbuf(fp, NULL, _IONBF, 0);
+
if (!PEM_read_X509(fp, &x, NULL, NULL)) {
if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) {
ERR_clear_error();
@@ -799,6 +803,7 @@ static EVP_PKEY *read_priv_pkey(const char *keyfile, const char *keypass)
log_err("Failed to open keyfile: %s\n", keyfile);
return NULL;
}
+ setvbuf(fp, NULL, _IONBF, 0);
pkey = PEM_read_PrivateKey(fp, NULL, NULL, (void *)keypass);
if (!pkey) {
log_err("Failed to PEM_read_PrivateKey key file: %s\n",
@@ -1021,8 +1026,15 @@ err:

int sign_hash(const char *hashalgo, const unsigned char *hash, int size, const char *keyfile, const char *keypass, unsigned char *sig)
{
- if (keypass)
+ if (keypass && keypass != imaevm_params.keypass) {
+ /*
+ * Cannot cleanse previous imaevm_params.keypass value, due to
+ * it's being const, but can warn user.
+ */
+ if (imaevm_params.keypass)
+ log_err("Overwriting non-empty imaevm_params.keypass\n");
imaevm_params.keypass = keypass;
+ }

return imaevm_params.x509 ?
sign_hash_v2(hashalgo, hash, size, keyfile, sig) :
--
2.29.3