From e44ae22989a8be7460a3ff3c2e7ec80cb7a7e4ba Mon Sep 17 00:00:00 2001 From: Ingo Franzki Date: Wed, 13 May 2020 10:40:45 +0200 Subject: [PATCH] libekmfweb: Add, change and delete key tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow to add, change and delete custom tags of a key. Custom tags can be used store any kind of textual information with a key. Tags have a name and a value. Signed-off-by: Ingo Franzki Signed-off-by: Jan Höppner --- include/ekmfweb/ekmfweb.h | 80 +++++++++ libekmfweb/ekmfweb.c | 354 ++++++++++++++++++++++++++++++++++++++ libekmfweb/libekmfweb.map | 2 + libekmfweb/utilities.c | 37 ++++ libekmfweb/utilities.h | 3 + 5 files changed, 476 insertions(+) diff --git a/include/ekmfweb/ekmfweb.h b/include/ekmfweb/ekmfweb.h index 7fdcea31..82ab8fc1 100644 --- a/include/ekmfweb/ekmfweb.h +++ b/include/ekmfweb/ekmfweb.h @@ -815,6 +815,86 @@ int ekmf_set_key_state(const struct ekmf_config *config, CURL **curl_handle, const char *key_uuid, const char *new_state, const char *updated_on, char **error_msg, bool verbose); +/** + * Sets (changed/adds) custom tags of a key identified by its UUID. To update a + * key, the timestamp from the last update is required. This can be found in + * the key info struct in field update_on. + * + * To perform a single request, set curl_handle to NULL. This will cause the + * function to initialize a new CURL handle, use it, and destroy it. + * If you plan to perform multiple requests to the same host, supply the address + * of a CURL pointer that is initially NULL. This function will then initialize + * a new CURL handle on the first call. On subsequent calls, pass in the address + * of the same CURL pointer so that the CURL handle is reused. After the last + * request, the CURL handle must be destroyed by calling ekmf_curl_destroy). + * + * @param config the configuration structure + * @param curl_handle address of a CURL handle used for reusing the same + * CURL handle with multiple requests. + * @param key_uuid the UUID of the key to get info for + * @param tags a list of tags to set + * @param updated_on the timestamp of the last update (must match) + * @param new_updated_on on return: if not NULL, the new timestamp of the + * current update. Can be used for subsequent updates + * on the key. + * @param error_msg on return: If not NULL, then a textual error message + * is returned in case of a failing request. The caller + * must free the error string when it is not NULL. + * @param verbose if true, verbose messages are printed + * + * @returns zero for success, a negative errno in case of an error. + * -EACCES is returned, if no or no valid login token is available. + * -EPERM is returned if the login token does not have permission to + * update the key. + * -EAGAIN is returned if the timestamp does not match, indicating that + * the key has been updated in the meantime. + */ +int ekmf_set_key_tags(const struct ekmf_config *config, CURL **curl_handle, + const char *key_uuid, const struct ekmf_tag_list *tags, + const char *updated_on, char **new_updated_on, + char **error_msg, bool verbose); + +/** + * Deletes custom tags of a key identified by its UUID. To update a + * key, the timestamp from the last update is required. This can be found in + * the key info struct in field update_on. + * + * To perform a single request, set curl_handle to NULL. This will cause the + * function to initialize a new CURL handle, use it, and destroy it. + * If you plan to perform multiple requests to the same host, supply the address + * of a CURL pointer that is initially NULL. This function will then initialize + * a new CURL handle on the first call. On subsequent calls, pass in the address + * of the same CURL pointer so that the CURL handle is reused. After the last + * request, the CURL handle must be destroyed by calling ekmf_curl_destroy). + * + * @param config the configuration structure + * @param curl_handle address of a CURL handle used for reusing the same + * CURL handle with multiple requests. + * @param key_uuid the UUID of the key to get info for + * @param tags a list of tags to delete. Only the name of the tags + * must be present in the tag structs of the list, the + * values are ignored. + * @param updated_on the timestamp of the last update (must match) + * @param new_updated_on on return: if not NULL, the new timestamp of the + * current update. Can be used for subsequent updates + * on the key. + * @param error_msg on return: If not NULL, then a textual error message + * is returned in case of a failing request. The caller + * must free the error string when it is not NULL. + * @param verbose if true, verbose messages are printed + * + * @returns zero for success, a negative errno in case of an error. + * -EACCES is returned, if no or no valid login token is available. + * -EPERM is returned if the login token does not have permission to + * update the key. + * -EAGAIN is returned if the timestamp does not match, indicating that + * the key has been updated in the meantime. + */ +int ekmf_delete_key_tags(const struct ekmf_config *config, CURL **curl_handle, + const char *key_uuid, const struct ekmf_tag_list *tags, + const char *updated_on, char **new_updated_on, + char **error_msg, bool verbose); + /** * Clones a key info structure by making a deep copy of all strings and * arrays. diff --git a/libekmfweb/ekmfweb.c b/libekmfweb/ekmfweb.c index 19429692..4aa9723d 100644 --- a/libekmfweb/ekmfweb.c +++ b/libekmfweb/ekmfweb.c @@ -47,6 +47,7 @@ #define EKMF_URI_KEYS_EXPORT "/api/v1/keys/%s/export" #define EKMF_URI_KEYS_TAGS "/api/v1/keys/%s/tags" #define EKMF_URI_KEYS_EXPORT_CONTROL "/api/v1/keys/%s/exportControl" +#define EKMF_URI_KEYS_SET_TAG "/api/v1/keys/%s/tags/%s" #define EKMF_URI_KEYS_GET "/api/v1/keys/%s" #define EKMF_URI_KEYS_SET_STATE "/api/v1/keys/%s" #define EKMF_URI_KEYS_LIST "/api/v1/keys" \ @@ -3502,6 +3503,359 @@ out: return rc; } +/** + * Sets (adds/changes) a custom tag of a key identified by its UUID. To update + * a key, the timestamp from the last update is required. This can be found in + * the key info struct in field update_on. + * + * @param config the configuration structure + * @param curl the CURL handle + * @param login_token the login token to authenticate + * @param key_uuid the UUID of the key to get info for + * @param tag the tag to set + * @param updated_on the timestamp of the last update (must match) + * @param delete if true, the tag is deleted, otherwise it is updated + * @param etag On return: the new update timestamp returned via + * the etag HTTP header. Must be freed by the caller. + * @param error_msg on return: If not NULL, then a textual error message + * is returned in case of a failing request. The caller + * must free the error string when it is not NULL. + * @param verbose if true, verbose messages are printed + * + * @returns zero for success, a negative errno in case of an error. + * -EACCES is returned, if no or no valid login token is available. + * -EPERM is returned if the login token does not have permission to + * update the key. + * -EAGAIN is returned if the timestamp does not match, indicating that + * the key has been updated in the meantime. + */ +static int _ekmf_set_key_tag(const struct ekmf_config *config, CURL *curl, + const char *login_token, const char *key_uuid, + const struct ekmf_tag *tag, const char *updated_on, + bool delete, char **etag, char **error_msg, + bool verbose) +{ + struct curl_slist *response_headers = NULL; + char *request_headers[2] = { NULL, NULL }; + json_object *request_obj = NULL; + char *escaped_tag_name = NULL; + char *escaped_uuid = NULL; + char *if_match_hdr = NULL; + char *uri = NULL; + long status_code; + int rc; + + if (config == NULL || curl == NULL || login_token == NULL || + key_uuid == NULL || tag == NULL || updated_on == NULL || + etag == NULL) + return -EINVAL; + + *etag = NULL; + + if (!delete) { + request_obj = json_object_new_object(); + JSON_CHECK_ERROR(request_obj == NULL, rc, -ENOMEM, + "Failed to generate JSON object", verbose, + out); + + rc = json_object_object_add_ex(request_obj, "value", + json_object_new_string(tag->value), 0); + JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to " + "JSON object", verbose, out); + } + + escaped_uuid = curl_easy_escape(curl, key_uuid, 0); + if (escaped_uuid == NULL) { + pr_verbose(verbose, "Failed to url-escape the key uuid"); + rc = -EIO; + goto out; + } + + escaped_tag_name = curl_easy_escape(curl, tag->name, 0); + if (escaped_tag_name == NULL) { + pr_verbose(verbose, "Failed to url-escape the tag name"); + rc = -EIO; + goto out; + } + + if (asprintf(&uri, EKMF_URI_KEYS_SET_TAG, escaped_uuid, + escaped_tag_name) < 0) { + pr_verbose(verbose, "asprintf failed"); + rc = -ENOMEM; + goto out; + } + + if (asprintf(&if_match_hdr, "If-Match : %s", updated_on) < 0) { + pr_verbose(verbose, "asprintf failed"); + rc = -ENOMEM; + goto out; + } + request_headers[0] = if_match_hdr; + + rc = _ekmf_perform_request(config, uri, delete ? "DELETE" : "PUT", + request_obj, request_headers, login_token, + NULL, &response_headers, &status_code, + error_msg, curl, verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed perform the REST call"); + if (rc > 0) + rc = -EIO; + goto out; + } + + switch (status_code) { + case 200: + case 204: + break; + case 400: + pr_verbose(verbose, "Bad request"); + rc = -EBADMSG; + goto out; + case 401: + pr_verbose(verbose, "Not authorized"); + rc = -EACCES; + goto out; + case 403: + pr_verbose(verbose, "Insufficient permissions"); + rc = -EPERM; + goto out; + case 404: + pr_verbose(verbose, "Not found"); + rc = -ENOENT; + goto out; + case 409: + pr_verbose(verbose, "Key was updated in the meantime"); + rc = -EAGAIN; + goto out; + default: + pr_verbose(verbose, "REST Call failed with HTTP status code: " + "%ld", status_code); + rc = -EIO; + goto out; + } + + *etag = get_http_header_value(response_headers, "Etag"); + if (*etag == NULL) { + pr_verbose(verbose, "No ETag in response headers"); + rc = -EBADMSG; + goto out; + } + +out: + if (request_obj != NULL) + json_object_put(request_obj); + if (uri != NULL) + free(uri); + if (escaped_uuid != NULL) + curl_free(escaped_uuid); + if (escaped_tag_name != NULL) + curl_free(escaped_tag_name); + if (if_match_hdr != NULL) + free(if_match_hdr); + if (response_headers != NULL) + curl_slist_free_all(response_headers); + + return rc; +} + +/** + * Sets (changed/adds) custom tags of a key identified by its UUID. To update a + * key, the timestamp from the last update is required. This can be found in + * the key info struct in field update_on. + * + * To perform a single request, set curl_handle to NULL. This will cause the + * function to initialize a new CURL handle, use it, and destroy it. + * If you plan to perform multiple requests to the same host, supply the address + * of a CURL pointer that is initially NULL. This function will then initialize + * a new CURL handle on the first call. On subsequent calls, pass in the address + * of the same CURL pointer so that the CURL handle is reused. After the last + * request, the CURL handle must be destroyed by calling ekmf_curl_destroy). + * + * @param config the configuration structure + * @param curl_handle address of a CURL handle used for reusing the same + * CURL handle with multiple requests. + * @param key_uuid the UUID of the key to get info for + * @param tags a list of tags to set + * @param updated_on the timestamp of the last update (must match) + * @param new_updated_on on return: if not NULL, the new timestamp of the + * current update. Can be used for subsequent updates + * on the key. + * @param error_msg on return: If not NULL, then a textual error message + * is returned in case of a failing request. The caller + * must free the error string when it is not NULL. + * @param verbose if true, verbose messages are printed + * + * @returns zero for success, a negative errno in case of an error. + * -EACCES is returned, if no or no valid login token is available. + * -EPERM is returned if the login token does not have permission to + * update the key. + * -EAGAIN is returned if the timestamp does not match, indicating that + * the key has been updated in the meantime. + */ +int ekmf_set_key_tags(const struct ekmf_config *config, CURL **curl_handle, + const char *key_uuid, const struct ekmf_tag_list *tags, + const char *updated_on, char **new_updated_on, + char **error_msg, bool verbose) +{ + char *update_ts = (char *)updated_on; + char *login_token = NULL; + bool token_valid = false; + CURL *curl = NULL; + char *etag = NULL; + size_t i; + int rc; + + if (config == NULL || key_uuid == NULL || tags == NULL || + updated_on == NULL) + return -EINVAL; + + rc = ekmf_check_login_token(config, &token_valid, &login_token, + verbose); + if (rc != 0 || !token_valid) { + pr_verbose(verbose, "No valid login token available"); + rc = -EACCES; + goto out; + } + + rc = _ekmf_get_curl_handle(curl_handle, &curl); + if (rc != 0) { + pr_verbose(verbose, "Failed to get CURL handle"); + rc = -EIO; + goto out; + } + + for (i = 0; i < tags->num_tags; i++) { + rc = _ekmf_set_key_tag(config, curl, login_token, key_uuid, + &tags->tags[i], update_ts, false, &etag, + error_msg, verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to set tag '%s'", + tags->tags[i].name); + goto out; + } + + if (update_ts != NULL && update_ts != updated_on) + free(update_ts); + update_ts = etag; + etag = NULL; + } + + if (new_updated_on != NULL) + *new_updated_on = strdup(update_ts); + +out: + _ekmf_release_curl_handle(curl_handle, curl); + + if (login_token != NULL) + free(login_token); + if (update_ts != NULL && update_ts != updated_on) + free(update_ts); + if (etag != NULL) + free(etag); + + return rc; +} + +/** + * Deletes custom tags of a key identified by its UUID. To update a + * key, the timestamp from the last update is required. This can be found in + * the key info struct in field update_on. + * + * To perform a single request, set curl_handle to NULL. This will cause the + * function to initialize a new CURL handle, use it, and destroy it. + * If you plan to perform multiple requests to the same host, supply the address + * of a CURL pointer that is initially NULL. This function will then initialize + * a new CURL handle on the first call. On subsequent calls, pass in the address + * of the same CURL pointer so that the CURL handle is reused. After the last + * request, the CURL handle must be destroyed by calling ekmf_curl_destroy). + * + * @param config the configuration structure + * @param curl_handle address of a CURL handle used for reusing the same + * CURL handle with multiple requests. + * @param key_uuid the UUID of the key to get info for + * @param tags a list of tags to delete. Only the name of the tags + * must be present in the tag structs of the list, the + * values are ignored. + * @param updated_on the timestamp of the last update (must match) + * @param new_updated_on on return: if not NULL, the new timestamp of the + * current update. Can be used for subsequent updates + * on the key. + * @param error_msg on return: If not NULL, then a textual error message + * is returned in case of a failing request. The caller + * must free the error string when it is not NULL. + * @param verbose if true, verbose messages are printed + * + * @returns zero for success, a negative errno in case of an error. + * -EACCES is returned, if no or no valid login token is available. + * -EPERM is returned if the login token does not have permission to + * update the key. + * -EAGAIN is returned if the timestamp does not match, indicating that + * the key has been updated in the meantime. + */ +int ekmf_delete_key_tags(const struct ekmf_config *config, CURL **curl_handle, + const char *key_uuid, const struct ekmf_tag_list *tags, + const char *updated_on, char **new_updated_on, + char **error_msg, bool verbose) +{ + char *update_ts = (char *)updated_on; + char *login_token = NULL; + bool token_valid = false; + CURL *curl = NULL; + char *etag = NULL; + size_t i; + int rc; + + if (config == NULL || key_uuid == NULL || tags == NULL || + updated_on == NULL) + return -EINVAL; + + rc = ekmf_check_login_token(config, &token_valid, &login_token, + verbose); + if (rc != 0 || !token_valid) { + pr_verbose(verbose, "No valid login token available"); + rc = -EACCES; + goto out; + } + + rc = _ekmf_get_curl_handle(curl_handle, &curl); + if (rc != 0) { + pr_verbose(verbose, "Failed to get CURL handle"); + rc = -EIO; + goto out; + } + + for (i = 0; i < tags->num_tags; i++) { + rc = _ekmf_set_key_tag(config, curl, login_token, key_uuid, + &tags->tags[i], update_ts, true, &etag, + error_msg, verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to delete tag '%s'", + tags->tags[i].name); + goto out; + } + + if (update_ts != NULL && update_ts != updated_on) + free(update_ts); + update_ts = etag; + etag = NULL; + } + + if (new_updated_on != NULL) + *new_updated_on = strdup(update_ts); + +out: + _ekmf_release_curl_handle(curl_handle, curl); + + if (login_token != NULL) + free(login_token); + if (update_ts != NULL && update_ts != updated_on) + free(update_ts); + if (etag != NULL) + free(etag); + + return rc; +} + /** * Clones a key info structure by making a deep copy of all strings and * arrays. diff --git a/libekmfweb/libekmfweb.map b/libekmfweb/libekmfweb.map index eaf5390a..399d036f 100644 --- a/libekmfweb/libekmfweb.map +++ b/libekmfweb/libekmfweb.map @@ -17,6 +17,8 @@ LIBEKMFWEB_1.0 { ekmf_list_keys; ekmf_get_key_info; ekmf_set_key_state; + ekmf_set_key_tags; + ekmf_delete_key_tags; ekmf_clone_key_info; ekmf_free_key_info; ekmf_generate_key; diff --git a/libekmfweb/utilities.c b/libekmfweb/utilities.c index 10c905bf..c2a40e81 100644 --- a/libekmfweb/utilities.c +++ b/libekmfweb/utilities.c @@ -1734,6 +1734,43 @@ void free_key_info(struct ekmf_key_info *key) free_export_control(&key->export_control, true); } +/** + * Finds the specified HTTP header in the list of HTTP headers. Returns a newly + * allocated string containing the header value. The caller must free the string + * when no longer needed. + * + * @param headers the list of HTTP headers + * @param name the name of the header to look for. + * + * @returns a newly allocated string, or NULL if the header is not found + */ +char *get_http_header_value(const struct curl_slist *headers, const char *name) +{ + const struct curl_slist *hdr +; + char *ch; + + if (headers == NULL || name == NULL) + return NULL; + + for (hdr = headers; hdr != NULL; hdr = hdr->next) { + if (hdr->data == NULL) + continue; + + ch = strchr(hdr->data, ':'); + if (ch == NULL) + continue; + if (strncasecmp(hdr->data, name, ch - hdr->data) != 0) + continue; + + for (ch++; *ch == ' '; ch++) + ; + return strdup(ch); + } + + return NULL; +} + struct ecc_curve_info { int curve_nid; enum { diff --git a/libekmfweb/utilities.h b/libekmfweb/utilities.h index f718009b..2f61c862 100644 --- a/libekmfweb/utilities.h +++ b/libekmfweb/utilities.h @@ -18,6 +18,7 @@ #include #include +#include #include "ekmfweb/ekmfweb.h" @@ -78,6 +79,8 @@ int clone_key_info(const struct ekmf_key_info *src, struct ekmf_key_info *dest); void free_key_info(struct ekmf_key_info *key); +char *get_http_header_value(const struct curl_slist *headers, const char *name); + size_t ecc_get_curve_prime_bits(int curve_nid); size_t ecc_get_curve_prime_length(int curve_nid); const char *ecc_get_curve_id(int curve_nid);