Subject: [PATCH] [FEAT SEC1800] zkey: Add properties file handling routines
From: Philipp Rudo <prudo@linux.ibm.com>

Summary:     zkey: Add support of protected key crypto for dm-crypt.
Description: Support the usage of protected key crypto for dm-crypt disks in
             plain format by providing a tool to manage a key repository
             allowing to associate secure keys with disk partitions or logical
             volumes.
Upstream-ID: 340da73bb7f06a9fc2aecfe4e33f1f3a17b3568d
Problem-ID:  SEC1800

Upstream-Description:

             zkey: Add properties file handling routines

             In preparation for a new feature, introduce property file
             handling routines. A property file stores key value pairs
             in a text file. Optionally a hash of all keys and values
             contained in the properties file can be generated to
             ensure integrity of the properties file and to detect
             manual modifications.

             Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
             Reviewed-by: Hendrik Brueckner <brueckner@linux.ibm.com>
             Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>


Signed-off-by: Philipp Rudo <prudo@linux.ibm.com>
---
 zkey/Makefile     |    5 
 zkey/properties.c |  409 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 zkey/properties.h |   36 ++++
 3 files changed, 448 insertions(+), 2 deletions(-)

--- a/zkey/Makefile
+++ b/zkey/Makefile
@@ -1,15 +1,16 @@
 include ../common.mak
 
 CPPFLAGS += -I../include
-LDLIBS += -ldl
+LDLIBS += -ldl -lcrypto
 
 all: zkey
 
 libs = $(rootdir)/libutil/libutil.a
 
 zkey.o: zkey.c pkey.h misc.h
+properties.o: properties.c properties.h
 
-zkey: zkey.o $(libs)
+zkey: zkey.o properties.o $(libs)
 
 install: all
 	$(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR)
--- /dev/null
+++ b/zkey/properties.c
@@ -0,0 +1,409 @@
+/*
+ * zkey - Generate, re-encipher, and validate secure keys
+ *
+ * Properties file handling functions
+ *
+ * Copyright IBM Corp. 2018
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/evp.h>
+
+#include "lib/util_libc.h"
+#include "lib/util_list.h"
+#include "lib/util_panic.h"
+
+#include "properties.h"
+
+struct properties {
+	struct util_list list;
+};
+
+struct property {
+	struct util_list_node node;
+	char *name;
+	char *value;
+};
+
+#define SHA256_DIGEST_LEN	32
+#define INTEGRITY_KEY_NAME      "__hash__"
+
+#define RESTRICTED_PROPERTY_NAME_CHARS   "=\n"
+#define RESTRICTED_PROPERTY_VALUE_CHARS  "\n"
+
+static int openssl_initialized;
+
+/**
+ * Allocate and initialize a SHA-256 context
+ *
+ * @returns a SHA context
+ */
+static EVP_MD_CTX *sha256_init(void)
+{
+	EVP_MD_CTX *ctx;
+	int rc;
+
+	if (!openssl_initialized) {
+		OpenSSL_add_all_algorithms();
+		openssl_initialized = 1;
+	}
+
+	ctx = EVP_MD_CTX_create();
+	util_assert(ctx != NULL,
+		    "Internal error: OpenSSL MD context allocation failed");
+
+	rc = EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
+	util_assert(rc == 1, "Internal error: SHA-256 digest init failed");
+
+	return ctx;
+}
+
+/**
+ * Add data to the SHA-256 context
+ *
+ * @parm[in]    ctx        the SHA context
+ * @parm[in]    data       the data to be hashed
+ * @parm[in]    data_len   the length of the data
+ */
+static void sha256_update(EVP_MD_CTX *ctx,
+			  const char *data, unsigned int data_len)
+{
+	int rc;
+
+	util_assert(ctx != NULL, "Internal error: OpenSSL MD context is NULL");
+	util_assert(data != NULL || data_len == 0,
+		    "Internal error: data is NULL");
+
+	rc = EVP_DigestUpdate(ctx, data, data_len);
+
+	util_assert(rc == 1, "Internal error: SHA-256 digest udpdate failed");
+}
+
+/**
+ * Produce the digest for the SHA-256 context and free the context
+ *
+ * @parm[in]     ctx          the SHA context
+ * @parm[out]    digest       a buffer where the digest is stored
+ * @parm[in/out] digest_len   on entry, *digest_len contains the size of the
+ *                            digest buffer, which must be large enough to hold
+ *                            a SHA-256 digest (32 bytes),
+ *                            on exit it contains the size of the digest
+ *                            returned in the buffer.
+ */
+static void sha256_final(EVP_MD_CTX *ctx,
+			 unsigned char *digest, unsigned int *digest_len)
+{
+	int rc;
+
+	util_assert(ctx != NULL, "Internal error: OpenSSL MD context is NULL");
+
+	if (digest != NULL && digest_len != NULL) {
+		util_assert(*digest_len >= (unsigned int)EVP_MD_CTX_size(ctx),
+			    "Internal error: digest_len is too small");
+
+		rc = EVP_DigestFinal_ex(ctx, digest, digest_len);
+		util_assert(rc == 1,
+			    "Internal error: SHA-256 digest final failed");
+	}
+
+	EVP_MD_CTX_destroy(ctx);
+}
+
+/**
+ * Allocates a new properties object
+ *
+ * @returns the properties object
+ */
+struct properties *properties_new(void)
+{
+	struct properties *properties;
+
+	properties = util_zalloc(sizeof(struct properties));
+
+	util_list_init_offset(&properties->list,
+			      offsetof(struct property, node));
+	return properties;
+}
+
+/**
+ * Frees a properties object with all its properties
+ *
+ * @param[in]  properties    the properties object
+ */
+void properties_free(struct properties *properties)
+{
+	struct property *property;
+
+	util_assert(properties != NULL, "Internal error: properties is NULL");
+
+	while ((property = util_list_start(&properties->list)) != NULL) {
+		free(property->name);
+		free(property->value);
+		util_list_remove(&properties->list, property);
+	}
+
+	free(properties);
+}
+
+/**
+ * Find a property by its name in the list iof properties
+ *
+ * @param[in]  properties    the properties object
+ * @param[in]  name          the name of the property to find
+ *
+ * @returns a pointer to the proerty when it has been found, or NULL if not
+ */
+static struct property *properties_find(struct properties *properties,
+					const char *name)
+{
+	struct property *property;
+
+	property = util_list_start(&properties->list);
+	while (property != NULL) {
+		if (strcmp(property->name, name) == 0)
+			return property;
+		property = util_list_next(&properties->list, property);
+	}
+	return NULL;
+}
+
+/**
+ * Adds or updates a property
+ *
+ * @param[in]  properties    the properties object
+ * @param[in]  name          the name of the property
+ * @param[in]  value         the value of the property
+ *
+ * @returns 0 on success,
+ *          -EINVAL if the name or value contains invalid characters
+ */
+int properties_set(struct properties *properties,
+		   const char *name, const char *value)
+{
+	struct property *property;
+
+	util_assert(properties != NULL, "Internal error: properties is NULL");
+	util_assert(name != NULL, "Internal error: name is NULL");
+	util_assert(value != NULL, "Internal error: value is NULL");
+
+	if (strpbrk(name, RESTRICTED_PROPERTY_NAME_CHARS) != NULL)
+		return -EINVAL;
+	if (strpbrk(value, RESTRICTED_PROPERTY_VALUE_CHARS) != NULL)
+		return -EINVAL;
+
+	property = properties_find(properties, name);
+	if (property != NULL) {
+		free(property->value);
+		property->value = util_strdup(value);
+	} else {
+		property = util_zalloc(sizeof(struct property));
+		property->name = util_strdup(name);
+		property->value = util_strdup(value);
+		util_list_add_tail(&properties->list, property);
+	}
+	return 0;
+}
+
+/**
+ * Gets a property
+ *
+ * @param[in]  properties    the properties object
+ * @param[in]  name          the name of the property
+ *
+ * @returns a string containing the property value, or NULL if the property
+ *          was not found.
+ *          Note: The returned string must be freed via free() by the caller.
+ */
+char *properties_get(struct properties *properties, const char *name)
+{
+	struct property *property;
+
+	util_assert(properties != NULL, "Internal error: properties is NULL");
+	util_assert(name != NULL, "Internal error: name is NULL");
+
+	property = properties_find(properties, name);
+	if (property == NULL)
+		return NULL;
+
+	return util_strdup(property->value);
+}
+
+/**
+ * Removes a property
+ *
+ * @param[in]  properties    the properties object
+ * @param[in]  name          the name of the property
+ *
+ * @returns 0 on success, -ENOENT if the property was not found.
+ */
+int properties_remove(struct properties *properties, const char *name)
+{
+	struct property *property;
+
+	util_assert(properties != NULL, "Internal error: properties is NULL");
+	util_assert(name != NULL, "Internal error: name is NULL");
+
+	property = properties_find(properties, name);
+	if (property == NULL)
+		return -ENOENT;
+
+	free(property->name);
+	free(property->value);
+	util_list_remove(&properties->list, property);
+	return 0;
+}
+
+/**
+ * Saves the properties to a file
+ *
+ * @param[in]  properties    the properties object
+ * @param[in]  filename      the file name
+ * @param[in]  check_integrity if TRUE, an hash of the key and values is
+ *                           stored as part of the file.
+ *
+ * @returns 0 on success, -EIO the file could not be created
+ */
+int properties_save(struct properties *properties, const char *filename,
+		    bool check_integrity)
+{
+	char digest_hex[SHA256_DIGEST_LEN * 2 + 1];
+	unsigned char digest[SHA256_DIGEST_LEN];
+	unsigned int digest_len = sizeof(digest);
+	struct property *property;
+	EVP_MD_CTX *ctx = NULL;
+	unsigned int i;
+	FILE *fp;
+
+	util_assert(properties != NULL, "Internal error: properties is NULL");
+	util_assert(filename != NULL, "Internal error: filename is NULL");
+
+	fp = fopen(filename, "w");
+	if (fp == NULL)
+		return -EIO;
+
+	if (check_integrity)
+		ctx = sha256_init();
+
+	property = util_list_start(&properties->list);
+	while (property != NULL) {
+		fprintf(fp, "%s=%s\n", property->name, property->value);
+
+		if (check_integrity) {
+			sha256_update(ctx, property->name,
+				      strlen(property->name));
+			sha256_update(ctx, property->value,
+				      strlen(property->value));
+		}
+
+		property = util_list_next(&properties->list, property);
+	}
+
+	if (check_integrity) {
+		sha256_final(ctx, digest, &digest_len);
+		util_assert(digest_len <= SHA256_DIGEST_LEN,
+			    "Internal error: digest length too long");
+
+		for (i = 0; i < digest_len; i++)
+			sprintf(&digest_hex[i * 2], "%02x", digest[i]);
+		digest_hex[digest_len * 2] = '\0';
+
+		fprintf(fp, "%s=%s\n", INTEGRITY_KEY_NAME, digest_hex);
+	}
+
+	fclose(fp);
+	return 0;
+}
+
+/**
+ * Loads the properties from a file
+ *
+ * @param[in]  properties    the properties object
+ * @param[in]  filename      the file name
+ * @param[in]  check_integrity if TRUE, an hash of the key and values is
+ *                           compared with the hash stored as part of the file.
+ *
+ * @returns 0 on success, -EIO the file could not be created,
+ *          -EPERM in case of a syntax error or an integrity error
+ */
+int properties_load(struct properties *properties, const char *filename,
+		    bool check_integrity)
+{
+	char digest_hex[SHA256_DIGEST_LEN * 2 + 1];
+	unsigned char digest[SHA256_DIGEST_LEN];
+	unsigned int digest_len = sizeof(digest);
+	char *digest_read = NULL;
+	EVP_MD_CTX *ctx = NULL;
+	char line[4096];
+	unsigned int len, i;
+	int rc = 0;
+	char *ch;
+	FILE *fp;
+
+	util_assert(properties != NULL, "Internal error: properties is NULL");
+	util_assert(filename != NULL, "Internal error: filename is NULL");
+
+	fp = fopen(filename, "r");
+	if (fp == NULL)
+		return -EIO;
+
+	if (check_integrity)
+		ctx = sha256_init();
+
+	while (fgets(line, sizeof(line), fp) != NULL) {
+		len = strlen(line);
+		if (line[len-1] == '\n')
+			line[len-1] = '\0';
+		ch = strchr(line, '=');
+		if (ch == NULL) {
+			rc = -EPERM;
+			goto out;
+		}
+
+		*ch = '\0';
+		ch++;
+
+		if (check_integrity) {
+			if (strcmp(line, INTEGRITY_KEY_NAME) == 0) {
+				digest_read = util_strdup(ch);
+				continue;
+			}
+
+			sha256_update(ctx, line, strlen(line));
+			sha256_update(ctx, ch, strlen(ch));
+		}
+
+		properties_set(properties, line, ch);
+	}
+
+	if (check_integrity) {
+		sha256_final(ctx, digest, &digest_len);
+		ctx = NULL;
+		util_assert(digest_len <= SHA256_DIGEST_LEN,
+			    "Internal error: digest length too long");
+
+		for (i = 0; i < digest_len; i++)
+			sprintf(&digest_hex[i * 2], "%02x", digest[i]);
+		digest_hex[digest_len * 2] = '\0';
+
+		if (digest_read == NULL ||
+		    strcmp(digest_hex, digest_read) != 0) {
+			rc = -EPERM;
+			goto out;
+		}
+	}
+
+out:
+	if (ctx != NULL)
+		sha256_final(ctx, NULL, NULL);
+	if (digest_read != NULL)
+		free(digest_read);
+	fclose(fp);
+	return rc;
+}
--- /dev/null
+++ b/zkey/properties.h
@@ -0,0 +1,36 @@
+/*
+ * zkey - Generate, re-encipher, and validate secure keys
+ *
+ * Properties file handling functions
+ *
+ * Copyright IBM Corp. 2018
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#ifndef PROPFILE_H
+#define PROPFILE_H
+
+#include <stdbool.h>
+
+struct properties;
+
+struct properties *properties_new(void);
+
+void properties_free(struct properties *properties);
+
+int properties_set(struct properties *properties,
+		   const char *name, const char *value);
+
+char *properties_get(struct properties *properties, const char *name);
+
+int properties_remove(struct properties *properties, const char *name);
+
+int properties_save(struct properties *properties, const char *filename,
+		    bool check_integrity);
+
+int properties_load(struct properties *properties, const char *filename,
+		    bool check_integrity);
+
+#endif
