Files
s390-tools/zkey/kmip/profiles.c
Ingo Franzki 48bcfc3366 zkey-kmip: Fix possible use after free
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2022-06-03 18:40:36 +02:00

604 lines
17 KiB
C

/*
* zkey-kmip - KMIP zkey KMS plugin
*
* Copyright IBM Corp. 2021
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#define _DEFAULT_SOURCE
#include <dirent.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <regex.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <err.h>
#include "lib/zt_common.h"
#include "lib/util_libc.h"
#include "zkey-kmip.h"
#include "../properties.h"
#define _set_error(ph, fmt...) plugin_set_error(&(ph)->pd, fmt)
/**
* Returns the profile directory. If environment variable ZKEY_KMIP_PROFILES
* is set, then its value specifies the profile directory, otherwise the
* default profile directory '/etc/zkey/kmip/profiles' is returned.
*/
static const char *get_profiles_directory(void)
{
const char *dir;
dir = secure_getenv(KMIP_PROFILES_LOCATION_ENVVAR);
return dir != NULL ? dir : KMIP_PROFILES_LOCATION;
}
static int profile_get_bool(struct plugin_handle *ph, struct properties *props,
const char *file_name, const char *prop_name,
bool default_val, bool *bool_var)
{
int rc = 0;
char *val;
val = properties_get(props, prop_name);
if (val == NULL) {
*bool_var = default_val;
goto ret;
}
if (strcasecmp(val, KMIP_PROFILES_BOOLEAN_FALSE) == 0) {
*bool_var = false;
} else if (strcasecmp(val, KMIP_PROFILES_BOOLEAN_TRUE) == 0) {
*bool_var = true;
} else {
_set_error(ph, "Profile '%s': Invalid value for '%s': '%s'",
file_name, prop_name,
val);
rc = -EINVAL;
goto out;
}
out:
free(val);
ret:
if (rc == 0)
pr_verbose(&ph->pd, "Profile: '%s': '%s'", prop_name,
*bool_var ? "True" : "False");
return rc;
}
/**
* Reads a profile from a file
*
* @param ph the plugin handle
* @param profile_dir the directory containing the profiles. If NULL, then
* the default profile directory or the one specified
* by environment variable ZKEY_KMIP_PROFILES is used.
* @param profile_file name of the profile file to read
* @param profile On return: the allocated profile. The caller must
* free the profile with profile_free.
*
* @returns 0 on success, a negative errno in case of an error.
*/
int profile_read(struct plugin_handle *ph, const char *profile_dir,
const char *profile_file, struct kmip_profile **profile)
{
struct kmip_profile *prof = NULL;
struct properties *props;
char *file_name = NULL;
char *val = NULL, *tok;
int rc;
if (profile_dir == NULL)
profile_dir = get_profiles_directory();
util_asprintf(&file_name, "%s/%s", profile_dir, profile_file);
pr_verbose(&ph->pd, "Read profile from '%s'", file_name);
props = properties_new();
rc = properties_load(props, file_name, false);
if (rc != 0) {
_set_error(ph, "Failed to read profile from file '%s': %s",
file_name, strerror(-rc));
goto out;
}
prof = util_zalloc(sizeof(struct kmip_profile));
tok = strrchr(profile_file, '/');
if (tok == NULL)
tok = (char *)profile_file;
prof->name = util_strdup(tok);
tok = strchr(prof->name, '.');
if (tok != NULL)
*tok = 0;
pr_verbose(&ph->pd, "Profile name: '%s'", prof->name);
prof->server_regex = properties_get(props, KMIP_PROFILES_SERVER_REGEX);
if (prof->server_regex == NULL) {
_set_error(ph, "Profile '%s': Missing value for '%s'",
file_name, KMIP_PROFILES_SERVER_REGEX);
rc = -EINVAL;
goto out;
}
pr_verbose(&ph->pd, "Profile: '%s': '%s'", KMIP_PROFILES_SERVER_REGEX,
prof->server_regex);
val = properties_get(props, KMIP_PROFILES_KMIP_VERSION);
if (val == NULL)
val = util_strdup(KMIP_PROFILES_VERSION_AUTO);
pr_verbose(&ph->pd, "Profile: '%s': '%s'", KMIP_PROFILES_KMIP_VERSION,
val);
if (strcasecmp(val, KMIP_PROFILES_VERSION_AUTO) == 0) {
prof->kmip_version.major = 0;
prof->kmip_version.minor = 0;
} else {
if (sscanf(val, "%u.%u", &prof->kmip_version.major,
&prof->kmip_version.minor) != 2) {
_set_error(ph, "Profile '%s': Invalid value for '%s': "
"'%s'", file_name,
KMIP_PROFILES_KMIP_VERSION, val);
rc = -EINVAL;
goto out;
}
if (prof->kmip_version.major == 0) {
_set_error(ph, "Profile '%s': Invalid value for '%s': "
"'%s'", file_name,
KMIP_PROFILES_KMIP_VERSION, val);
rc = -EINVAL;
goto out;
}
}
free(val);
val = NULL;
val = properties_get(props, KMIP_PROFILES_TRANSPORT);
if (val == NULL)
val = util_strdup(KMIP_PROFILES_TRANSPORT_TLS);
pr_verbose(&ph->pd, "Profile: '%s': '%s'", KMIP_PROFILES_TRANSPORT,
val);
if (strcasecmp(val, KMIP_PROFILES_TRANSPORT_TLS) == 0) {
prof->transport = KMIP_TRANSPORT_PLAIN_TLS;
} else if (strcasecmp(val, KMIP_PROFILES_TRANSPORT_HTTPS) == 0) {
prof->transport = KMIP_TRANSPORT_HTTPS;
} else {
_set_error(ph, "Profile '%s': Invalid value for '%s': '%s'",
file_name, KMIP_PROFILES_TRANSPORT, val);
rc = -EINVAL;
goto out;
}
free(val);
val = NULL;
val = properties_get(props, KMIP_PROFILES_ENCODING);
if (val == NULL)
val = util_strdup(KMIP_PROFILES_ENCODING_TTLV);
pr_verbose(&ph->pd, "Profile: '%s': '%s'", KMIP_PROFILES_ENCODING,
val);
if (strcasecmp(val, KMIP_PROFILES_ENCODING_TTLV) == 0) {
prof->encoding = KMIP_ENCODING_TTLV;
} else if (strcasecmp(val, KMIP_PROFILES_ENCODING_JSON) == 0) {
if (prof->transport != KMIP_TRANSPORT_HTTPS) {
_set_error(ph, "Profile '%s': JSON encoding is only "
"possible with HTTP transport",
file_name);
rc = -EINVAL;
goto out;
}
prof->encoding = KMIP_ENCODING_JSON;
} else if (strcasecmp(val, KMIP_PROFILES_ENCODING_XML) == 0) {
prof->encoding = KMIP_ENCODING_XML;
if (prof->transport != KMIP_TRANSPORT_HTTPS) {
_set_error(ph, "Profile '%s': XML encoding is only "
"possible with HTTP transport",
file_name);
rc = -EINVAL;
goto out;
}
} else {
_set_error(ph, "Profile '%s': Invalid value for '%s': '%s'",
file_name, KMIP_PROFILES_ENCODING, val);
rc = -EINVAL;
goto out;
}
free(val);
val = NULL;
val = properties_get(props, KMIP_PROFILES_HTTPS_URI);
switch (prof->transport) {
case KMIP_TRANSPORT_HTTPS:
if (val == NULL)
val = util_strdup(KMIP_PROFILES_HTTPS_URI_DEFAULT);
prof->https_uri = val;
pr_verbose(&ph->pd, "Profile: '%s': '%s'",
KMIP_PROFILES_HTTPS_URI, prof->https_uri);
break;
default:
if (val != NULL) {
pr_verbose(&ph->pd, "Profile: '%s': ignored '%s'",
KMIP_PROFILES_HTTPS_URI, val);
free(val);
}
break;
}
val = properties_get(props, KMIP_PROFILES_AUTH_SCHEME);
if (val == NULL)
val = util_strdup(KMIP_PROFILES_AUTH_TLS_CLIENT_CERT);
pr_verbose(&ph->pd, "Profile: '%s': '%s'", KMIP_PROFILES_AUTH_SCHEME,
val);
if (strcasecmp(val, KMIP_PROFILES_AUTH_TLS_CLIENT_CERT) == 0) {
prof->auth_scheme = KMIP_PROFILE_AUTH_TLS_CLIENT_CERT;
} else {
_set_error(ph, "Profile '%s': Invalid value for '%s': '%s'",
file_name, KMIP_PROFILES_AUTH_SCHEME, val);
rc = -EINVAL;
goto out;
}
free(val);
val = NULL;
val = properties_get(props, KMIP_PROFILES_WRAP_KEY_ALGORITHM);
if (val == NULL)
val = util_strdup(KMIP_PROFILES_WRAP_KEY_ALGORITHM_RSA);
pr_verbose(&ph->pd, "Profile: '%s': '%s'",
KMIP_PROFILES_WRAP_KEY_ALGORITHM, val);
if (strcasecmp(val, KMIP_PROFILES_WRAP_KEY_ALGORITHM_RSA) == 0) {
prof->wrap_key_algo = KMIP_CRYPTO_ALGO_RSA;
} else {
_set_error(ph, "Profile '%s': Invalid value for '%s': '%s'",
file_name, KMIP_PROFILES_WRAP_KEY_ALGORITHM, val);
rc = -EINVAL;
goto out;
}
free(val);
val = NULL;
val = properties_get(props, KMIP_PROFILES_WRAP_KEY_PARAMS);
if (prof->wrap_key_algo == KMIP_CRYPTO_ALGO_RSA) {
if (val == NULL) {
_set_error(ph, "Profile '%s': Missing value for '%s'",
file_name, KMIP_PROFILES_WRAP_KEY_PARAMS);
rc = -EINVAL;
goto out;
}
pr_verbose(&ph->pd, "Profile: '%s': '%s'",
KMIP_PROFILES_WRAP_KEY_PARAMS, val);
prof->wrap_key_size = atoi(val);
if (prof->wrap_key_size == 0) {
_set_error(ph, "Profile '%s': Invalid value for '%s' "
"(RSA modulus size): '%s'", file_name,
KMIP_PROFILES_WRAP_KEY_PARAMS, val);
rc = -EINVAL;
goto out;
}
} else if (val != NULL) {
pr_verbose(&ph->pd, "Profile: '%s': ignored '%s'",
KMIP_PROFILES_WRAP_KEY_PARAMS, val);
}
free(val);
val = NULL;
val = properties_get(props, KMIP_PROFILES_WRAP_KEY_FORMAT);
switch (prof->wrap_key_algo) {
case KMIP_CRYPTO_ALGO_RSA:
if (val == NULL)
val = util_strdup(KMIP_PROFILES_WRAP_KEY_FORMAT_PKCS1);
pr_verbose(&ph->pd, "Profile: '%s': '%s'",
KMIP_PROFILES_WRAP_KEY_FORMAT, val);
if (strcasecmp(val, KMIP_PROFILES_WRAP_KEY_FORMAT_PKCS1) == 0) {
prof->wrap_key_format = KMIP_KEY_FORMAT_TYPE_PKCS_1;
} else if (strcasecmp(val,
KMIP_PROFILES_WRAP_KEY_FORMAT_PKCS8) == 0) {
prof->wrap_key_format = KMIP_KEY_FORMAT_TYPE_PKCS_8;
} else if (strcasecmp(val,
KMIP_PROFILES_WRAP_KEY_FORMAT_TRANSP) == 0) {
prof->wrap_key_format =
KMIP_KEY_FORMAT_TYPE_TRANSPARENT_RSA_PUBLIC_KEY;
} else {
_set_error(ph, "Profile '%s': Invalid value for '%s': "
"'%s'", file_name,
KMIP_PROFILES_WRAP_KEY_FORMAT, val);
rc = -EINVAL;
goto out;
}
break;
default:
if (val != NULL)
pr_verbose(&ph->pd, "Profile: '%s': ignored '%s'",
KMIP_PROFILES_WRAP_KEY_FORMAT, val);
break;
}
free(val);
val = NULL;
val = properties_get(props, KMIP_PROFILES_WRAP_PADDING_METHOD);
switch (prof->wrap_key_algo) {
case KMIP_CRYPTO_ALGO_RSA:
if (val == NULL)
val = util_strdup(KMIP_PROFILES_WRAP_PADDING_PKCS1_5);
pr_verbose(&ph->pd, "Profile: '%s': '%s'",
KMIP_PROFILES_WRAP_PADDING_METHOD, val);
if (strcasecmp(val, KMIP_PROFILES_WRAP_PADDING_PKCS1_5) == 0) {
prof->wrap_padding_method =
KMIP_PADDING_METHOD_PKCS_1_5;
} else if (strcasecmp(val,
KMIP_PROFILES_WRAP_PADDING_OAEP) == 0) {
prof->wrap_padding_method = KMIP_PADDING_METHOD_OAEP;
} else {
_set_error(ph, "Profile '%s': Invalid value for '%s': "
"'%s'", file_name,
KMIP_PROFILES_WRAP_PADDING_METHOD, val);
rc = -EINVAL;
goto out;
}
break;
default:
if (val != NULL)
pr_verbose(&ph->pd, "Profile: '%s': ignored '%s'",
KMIP_PROFILES_WRAP_PADDING_METHOD, val);
break;
}
free(val);
val = NULL;
val = properties_get(props, KMIP_PROFILES_WRAP_HASHING_ALOGRITHM);
switch (prof->wrap_padding_method) {
case KMIP_PADDING_METHOD_OAEP:
if (val == NULL)
val = util_strdup(KMIP_PROFILES_WRAP_HASHING_ALGO_SHA1);
pr_verbose(&ph->pd, "Profile: '%s': '%s'",
KMIP_PROFILES_WRAP_HASHING_ALOGRITHM, val);
if (strcasecmp(val,
KMIP_PROFILES_WRAP_HASHING_ALGO_SHA1) == 0) {
prof->wrap_hashing_algo = KMIP_HASHING_ALGO_SHA_1;
} else if (strcasecmp(val,
KMIP_PROFILES_WRAP_HASHING_ALGO_SHA256) == 0) {
prof->wrap_hashing_algo = KMIP_HASHING_ALGO_SHA_256;
} else {
_set_error(ph, "Profile '%s': Invalid value for '%s': "
"'%s'", file_name,
KMIP_PROFILES_WRAP_PADDING_METHOD, val);
rc = -EINVAL;
goto out;
}
break;
default:
if (val != NULL)
pr_verbose(&ph->pd, "Profile: '%s': ignored '%s'",
KMIP_PROFILES_WRAP_HASHING_ALOGRITHM, val);
break;
}
free(val);
val = NULL;
rc = profile_get_bool(ph, props, file_name,
KMIP_PROFILES_SUPPORTS_LINK_ATTR, false,
&prof->supports_link_attr);
if (rc != 0)
goto out;
rc = profile_get_bool(ph, props, file_name,
KMIP_PROFILES_SUPPORTS_DESCRIPTION_ATTR, false,
&prof->supports_description_attr);
if (rc != 0)
goto out;
rc = profile_get_bool(ph, props, file_name,
KMIP_PROFILES_SUPPORTS_COMMENT_ATTR, false,
&prof->supports_comment_attr);
if (rc != 0)
goto out;
val = properties_get(props, KMIP_PROFILES_CUSTOM_ATTR_SCHEME);
if (val == NULL)
val = util_strdup(KMIP_PROFILES_CUST_ATTR_SCHEME_V1);
pr_verbose(&ph->pd, "Profile: '%s': '%s'",
KMIP_PROFILES_CUSTOM_ATTR_SCHEME, val);
if (strcasecmp(val, KMIP_PROFILES_CUST_ATTR_SCHEME_V1) == 0) {
prof->cust_attr_scheme = KMIP_PROFILE_CUST_ATTR_V1_STYLE;
} else if (strcasecmp(val, KMIP_PROFILES_CUST_ATTR_SCHEME_V2) == 0) {
prof->cust_attr_scheme = KMIP_PROFILE_CUST_ATTR_V2_STYLE;
} else {
_set_error(ph, "Profile '%s': Invalid value for '%s': '%s'",
file_name, KMIP_PROFILES_CUSTOM_ATTR_SCHEME, val);
rc = -EINVAL;
goto out;
}
free(val);
val = NULL;
rc = profile_get_bool(ph, props, file_name,
KMIP_PROFILES_SUPPORTS_SENSITIVE_ATTR, false,
&prof->supports_sensitive_attr);
if (rc != 0)
goto out;
rc = profile_get_bool(ph, props, file_name,
KMIP_PROFILES_CHECK_ALWAYS_SENS_ATTR, false,
&prof->check_always_sensitive_attr);
if (rc != 0)
goto out;
out:
properties_free(props);
if (file_name != NULL)
free(file_name);
if (val != NULL)
free(val);
if (rc != 0)
profile_free(prof);
else
*profile = prof;
return rc;
}
/**
* Frees a profile
*
* @param profile On return: the allocated profile
*
* @returns 0 on success, a negative errno in case of an error.
*/
void profile_free(struct kmip_profile *profile)
{
if (profile == NULL)
return;
if (profile->name != NULL)
free((char *)profile->name);
if (profile->server_regex != NULL)
free((char *)profile->server_regex);
if (profile->https_uri != NULL)
free((char *)profile->https_uri);
free(profile);
}
/**
* Filters directory entries for scanfile(). Only entries that are regular
* files and who's name ends with '.info' are matched.
*/
static int profile_file_filter(const struct dirent *dirent)
{
size_t len;
if (dirent->d_type != DT_REG && dirent->d_type != DT_UNKNOWN)
return 0;
len = strlen(dirent->d_name);
if (len > KMIP_PROFILES_FILE_TYPE_LEN &&
strcmp(&dirent->d_name[len - KMIP_PROFILES_FILE_TYPE_LEN],
KMIP_PROFILES_FILE_TYPE) == 0)
return 1;
return 0;
}
/**
* Scans the default profile directory or the one specified by environment
* variable ZKEY_KMIP_PROFILES for profiles that match the server info.
* Returns the first profile that matches, or the default profile if none
* matches.
*
* @param ph the plugin handle
* @param server_info the server info string to match the profiles against
* @param profile On return: the matched profile. The caller must free
* the profile with profile_free.
*
* @returns 0 on success, a negative errno in case of an error.
*/
int profile_find_by_server_info(struct plugin_handle *ph,
const char *server_info,
struct kmip_profile **profile)
{
struct kmip_profile *prof = NULL;
struct dirent **namelist;
const char *profile_dir;
regmatch_t pmatch[1];
char err_buf[256];
int i, n, rc = 0;
regex_t reg_buf;
profile_dir = get_profiles_directory();
pr_verbose(&ph->pd, "profile_dir: %s", profile_dir);
n = scandir(profile_dir, &namelist, profile_file_filter, alphasort);
if (n < 0) {
rc = -errno;
pr_verbose(&ph->pd, "scandir failed with: %s", strerror(-rc));
return rc;
}
for (i = 0; i < n; i++) {
if (strcmp(namelist[i]->d_name,
KMIP_PROFILES_DEFAULT_PROFILE) == 0)
continue;
pr_verbose(&ph->pd, "Found profile '%s'", namelist[i]->d_name);
rc = profile_read(ph, profile_dir, namelist[i]->d_name, &prof);
if (rc != 0) {
pr_verbose(&ph->pd, "profile_read failed with: %s",
strerror(-rc));
goto out;
}
rc = regcomp(&reg_buf, prof->server_regex, REG_EXTENDED);
if (rc != 0) {
regerror(rc, &reg_buf, err_buf, sizeof(err_buf));
_set_error(ph, "Profile '%s': Regular expression "
"error: '%s'", namelist[i]->d_name, err_buf);
rc = -EINVAL;
goto out;
}
rc = regexec(&reg_buf, server_info, (size_t)1, pmatch, 0);
regfree(&reg_buf);
if (rc == 0)
break;
profile_free(prof);
prof = NULL;
}
if (prof == NULL) {
rc = profile_read(ph, profile_dir,
KMIP_PROFILES_DEFAULT_PROFILE, &prof);
if (rc != 0) {
pr_verbose(&ph->pd, "default profile_read failed with: "
"%s", strerror(-rc));
goto out;
}
}
*profile = prof;
out:
for (i = 0; i < n; i++)
free(namelist[i]);
free(namelist);
return rc;
}
/**
* Reads a profile by its name. Tries to find a profile with the specified name
* in the default profile directory or the one specified by environment
* variable ZKEY_KMIP_PROFILES and loads it.
*
* @param ph the plugin handle
* @param profile_name name of the profile to read
* @param profile On return: the allocated profile. The caller must
* free the profile with profile_free.
*
* @returns 0 on success, a negative errno in case of an error.
*/
int profile_find_by_name(struct plugin_handle *ph, const char *profile_name,
struct kmip_profile **profile)
{
char *profile_file;
int rc;
util_asprintf(&profile_file, "%s%s", profile_name,
KMIP_PROFILES_FILE_TYPE);
rc = profile_read(ph, NULL, profile_file, profile);
free(profile_file);
return rc;
}