diff --git a/include/ekmfweb/ekmfweb.h b/include/ekmfweb/ekmfweb.h index 032f4a27..6eb9050e 100644 --- a/include/ekmfweb/ekmfweb.h +++ b/include/ekmfweb/ekmfweb.h @@ -466,6 +466,73 @@ struct ekmf_template_info { const char *updated_on; }; +struct ekmf_tag { + /** name of the tag */ + const char *name; + /** value of the tag */ + const char *value; +}; + +struct ekmf_tag_list { + /** array of tags */ + struct ekmf_tag *tags; + /** number of tags in array above */ + size_t num_tags; +}; + +struct ekmf_exporting_key { + /** name of the exporting key */ + const char *name; + /** uuid of the exporting key */ + const char *uuid; +}; + +struct ekmf_export_control { + /** If true, export is allowed using the exporting keys below */ + bool export_allowed; + /** array of erporting keys */ + struct ekmf_exporting_key *exporting_keys; + /** number of keys in array above */ + size_t num_exporting_keys; +}; + +struct ekmf_key_info { + /** label (name) of the key */ + const char *label; + /** Optional: description of the key (can be NULL) */ + const char *description; + /** UUID of the key */ + const char *uuid; + /** type of the key, e.g. CIPHER */ + const char *key_type; + /** algorithm of the key, e.g. AES */ + const char *algorithm; + /** bit size of the key */ + size_t key_size; + /** state of the key, e.g. ACTIVE */ + const char *state; + /** type of the keystore, e.g. PERVASIVE_ENCRYPTION */ + const char *keystore_type; + /** name of the template used to generate the key */ + const char *template; + /** UUID of the template used to generate the key */ + const char *template_uuid; + /** label tag list */ + struct ekmf_tag_list label_tags; + /** custom tag list */ + struct ekmf_tag_list custom_tags; + /** export control information */ + struct ekmf_export_control export_control; + /** timestamp when the key was activated */ + const char *activate_on; + /** timestamp when the key expires */ + const char *expires_on; + /** timestamp when the key was created */ + const char *created_on; + /** timestamp when the key was updated */ + const char *updated_on; +}; + /** * Callback function used with the ekmf_list_templates function. This * callback is called for each template found. @@ -612,6 +679,121 @@ int ekmf_clone_template_info(const struct ekmf_template_info *src, */ void ekmf_free_template_info(struct ekmf_template_info *template); +/** + * Callback function used with the ekmf_list_keys function. This + * callback is called for each ky found. + * + * @param curl_handle a CURL handle that can be used to perform further + * EKMFWeb functions within the callback. + * @param template_info a struct containing information about the key. + * If any of the information needs to be kept, then the + * callback function must make a copy of the + * information. The memory holding the information + * passed to the callback is no longer valid after the + * callback has returned. + * @param private the private pointer that was specified with the + * ekmf_list_keys invocation. + * + * @returns zero for success, a negative errno in case of an error. + * When a nonzero return code is returned, the key listing process stops, + * and ekmf_list_keys returns the return code from the callback. + */ +typedef int (*ekmf_key_cb_t)(CURL *curl_handle, + struct ekmf_key_info *key_info, + void *private); + +/** + * List available keys. The keys are ordered by name in ascending order. + * + * 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_cb a callback function that is called for each key + * found + * @param private a pointer that is passed as-is to the callback + * @param name_pattern a pattern to filter by name, or NULL to list all. + * @param states the states of the keys to list, or NULL to list keys + * in ACTIVE state only. Multiple states can be + * specified separated by comma.* + * @param tags a list of custom tags to use as filter, or NULL + * @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 + * list the keys + */ +int ekmf_list_keys(const struct ekmf_config *config, CURL **curl_handle, + ekmf_key_cb_t key_cb, void *private, + const char *name_pattern, const char *states, + const struct ekmf_tag_list *tags, + char **error_msg, bool verbose); + +/** + * Get information about a key by its UUID. + * + * 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 key an address of a key info pointer. On return + * the pointer is updated to point to a newly allocated + * key info struct. It must be freed by the caller + * using ekmf_free_key_info when no longer needed. + * @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 + * get the key info + */ +int ekmf_get_key_info(const struct ekmf_config *config, CURL **curl_handle, + const char *key_uuid, struct ekmf_key_info **key, + char **error_msg, bool verbose); + +/** + * Clones a key info structure by making a deep copy of all strings and + * arrays. + * The copied key info must be freed using ekmf_free_key_info() by + * the caller. + * + * @param src the source key info structure + * @param dest the destination key info structure + * + * @returns zero for success, a negative errno in case of an error + */ +int ekmf_clone_key_info(const struct ekmf_key_info *src, + struct ekmf_key_info **dest); + +/** + * Free a key info structure. + * + * @param key the key info to free + */ +void ekmf_free_key_info(struct ekmf_key_info *key); + /** * Close the connection to the EKMFWeb server by destroying the CURL handle. * diff --git a/libekmfweb/ekmfweb.c b/libekmfweb/ekmfweb.c index 237efea3..ac4072cb 100644 --- a/libekmfweb/ekmfweb.c +++ b/libekmfweb/ekmfweb.c @@ -44,6 +44,15 @@ #define EKMF_URI_SYSTEM_PUBKEY "/api/v1/system/publicKey" #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_GET "/api/v1/keys/%s" +#define EKMF_URI_KEYS_LIST "/api/v1/keys" \ + "?state=%s" \ + "&orderBy=%s" \ + "&namePattern=%s" \ + "&tags=%s" +#define EKMF_URI_KEYS_LIST_STATE "&state=" #define EKMF_URI_TEMPLATE_GET "/api/v1/templates/%s" #define EKMF_URI_TEMPLATE_LIST "/api/v1/templates" \ "?templateStates=%s" \ @@ -53,9 +62,11 @@ #define LIST_ELEMENTS_PER_PAGE 20 #define TEMPLATE_STATE_ACTIVE "ACTIVE" +#define KEY_STATE_ACTIVE "ACTIVE" #define KEY_ALGORITHM_AES "AES" #define KEYSTORE_TYPE_PERV_ENCR "PERVASIVE_ENCRYPTION" #define ORDER_BY_NAME_ASC "name%3Aasc" +#define ORDER_BY_LABEL_ASC "label%3Aasc" #define pr_verbose(verbose, fmt...) do { \ if (verbose) \ @@ -2725,6 +2736,655 @@ void ekmf_free_template_info(struct ekmf_template_info *template) free(template); } +/** + * Gets the custom tags of a key by key-uuid. The custom tags are returned as + * JSON array. The returned JSON array must be freed by the caller using + * json_object_put(). + */ +static int _ekmf_get_custom_tags(const struct ekmf_config *config, + const char *key_uuid, CURL *curl, + json_object **custom_tags, + const char *login_token, char **error_msg, + bool verbose) +{ + json_object *response_obj = NULL; + char *escaped_uuid = NULL; + char *uri = NULL; + long status_code; + int rc; + + if (config == NULL || key_uuid == NULL || custom_tags == NULL || + curl == NULL) + return -EINVAL; + + 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; + } + + if (asprintf(&uri, EKMF_URI_KEYS_TAGS, escaped_uuid) < 0) { + pr_verbose(verbose, "asprintf failed"); + rc = -ENOMEM; + goto out; + } + + rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL, login_token, + &response_obj, NULL, &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: + 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; + default: + pr_verbose(verbose, "REST Call failed with HTTP status code: " + "%ld", status_code); + rc = -EIO; + goto out; + } + + JSON_CHECK_OBJ(response_obj, json_type_array, rc, -EIO, + "No or invalid response content", verbose, out); + + *custom_tags = response_obj; + rc = 0; + +out: + if (uri != NULL) + free(uri); + if (escaped_uuid != NULL) + curl_free(escaped_uuid); + if (rc != 0 && response_obj != NULL) + json_object_put(response_obj); + + return rc; +} + +/** + * Gets the export control infos of a key by key-uuid. The export control info + * is returned as JSON object. The returned JSON object must be freed by the + * caller using json_object_put(). + */ +static int _ekmf_get_export_control(const struct ekmf_config *config, + const char *key_uuid, CURL *curl, + json_object **export_control, + const char *login_token, char **error_msg, + bool verbose) +{ + json_object *response_obj = NULL; + char *escaped_uuid = NULL; + char *uri = NULL; + long status_code; + int rc; + + if (config == NULL || key_uuid == NULL || export_control == NULL || + curl == NULL) + return -EINVAL; + + 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; + } + + if (asprintf(&uri, EKMF_URI_KEYS_EXPORT_CONTROL, escaped_uuid) < 0) { + pr_verbose(verbose, "asprintf failed"); + rc = -ENOMEM; + goto out; + } + + rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL, login_token, + &response_obj, NULL, &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: + 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; + default: + pr_verbose(verbose, "REST Call failed with HTTP status code: " + "%ld", status_code); + rc = -EIO; + goto out; + } + + JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EIO, + "No or invalid response content", verbose, out); + + *export_control = response_obj; + rc = 0; + +out: + if (uri != NULL) + free(uri); + if (escaped_uuid != NULL) + curl_free(escaped_uuid); + if (rc != 0 && response_obj != NULL) + json_object_put(response_obj); + + return rc; +} + +/** + * Get the custom tags for a key and build the key info structure + */ +static int _ekmf_build_key_info(const struct ekmf_config *config, CURL *curl, + const char *login_token, json_object *obj, + struct ekmf_key_info *key, bool copy, + char **error_msg, bool verbose) +{ + json_object *export_control = NULL; + json_object *custom_tags = NULL; + int rc; + + rc = _ekmf_get_custom_tags(config, json_get_string(obj, "keyId"), + curl, &custom_tags, login_token, error_msg, + verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to get the custom tags for key %s", + json_get_string(obj, "keyId")); + goto out; + } + + rc = _ekmf_get_export_control(config, json_get_string(obj, "keyId"), + curl, &export_control, login_token, + error_msg, verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to get the custom tags for key %s", + json_get_string(obj, "keyId")); + goto out; + } + + rc = json_build_key_info(obj, custom_tags, export_control, key, copy); + if (rc != 0) { + pr_verbose(verbose, "Failed to build key info"); + goto out; + } + +out: + /* + * Add custom tags and export control JSON objects to the key object, + * so that these objects are also owned by the key object, and thus are + * freed together with it, when the caller puts/frees the key object. + */ + if (custom_tags != NULL) + json_object_object_add_ex(obj, "_custom_tags_", custom_tags, 0); + if (export_control != NULL) + json_object_object_add_ex(obj, "_export_control_", + export_control, 0); + + return rc; +} + + +struct ekmf_key_cb_data_t { + const struct ekmf_config *config; + const char *login_token; + char **error_msg; + ekmf_key_cb_t key_cb; + void *cb_private; +}; + +/** + * Callback for key list function. Builds the key info structure + * and calls the application callback. + */ +static int _ekmf_key_cb(CURL *curl, json_object *element, + void *private, bool verbose) +{ + struct ekmf_key_cb_data_t *cb_data = private; + struct ekmf_key_info key = { 0 }; + int rc; + + if (cb_data->key_cb == NULL) { + pr_verbose(verbose, "No key callback function"); + return -EINVAL; + } + + rc = _ekmf_build_key_info(cb_data->config, curl, cb_data->login_token, + element, &key, false, cb_data->error_msg, + verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to build key info"); + goto out; + } + + rc = cb_data->key_cb(curl, &key, cb_data->cb_private); + if (rc != 0) { + pr_verbose(verbose, "Key callback rc: %d", rc); + goto out; + } + +out: + free_tag_list(&key.label_tags, false); + free_tag_list(&key.custom_tags, false); + free_export_control(&key.export_control, false); + + return rc; +} + +/** + * Builds the state URL parameter(s) from a comma separated list of states + * + * @param curl the curl handle + * @param states a comma separaed list of states + * + * @returns an allocated URL parameter value, or NULL in case of an error + */ +static char *_ekmf_build_state_filter(CURL *curl, const char *states) +{ + char *list, *tok, *ret = NULL, *tmp; + char *escaped_state; + + if (states == NULL) + goto error; + + list = strdup(states); + if (list == NULL) + goto error; + + tok = strtok(list, ","); + while (tok != NULL) { + escaped_state = curl_easy_escape(curl, tok, 0); + if (escaped_state == NULL) + goto error; + + if (asprintf(&tmp, "%s%s%s", ret != NULL ? ret : "", + ret == NULL ? "" : EKMF_URI_KEYS_LIST_STATE, + escaped_state) < 0) + tmp = NULL; + curl_free(escaped_state); + if (tmp == NULL) + goto error; + if (ret != NULL) + free(ret); + ret = tmp; + + tok = strtok(NULL, ","); + } + + free(list); + return ret; + +error: + if (ret != NULL) + free(ret); + return NULL; +} + +/** + * List available keys. The keys are ordered by name in ascending order. + * + * 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_cb a callback function that is called for each key + * found + * @param private a pointer that is passed as-is to the callback + * @param name_pattern a pattern to filter by name, or NULL to list all. + * @param states the states of the keys to list, or NULL to list keys + * in ACTIVE state only. Multiple states can be + * specified separated by comma. + * @param tags a list of custom tags to use as filter, or NULL + * @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 + * list the keys + */ +int ekmf_list_keys(const struct ekmf_config *config, CURL **curl_handle, + ekmf_key_cb_t key_cb, void *private, + const char *name_pattern, const char *states, + const struct ekmf_tag_list *tags, + char **error_msg, bool verbose) +{ + struct ekmf_key_cb_data_t cb_data; + char *escaped_name_pattern = NULL; + json_object *tags_obj = NULL; + char *state_filter = NULL; + char *escaped_tags = NULL; + char *login_token = NULL; + bool token_valid = false; + CURL *curl = NULL; + char *uri = NULL; + size_t i; + int rc; + + if (config == NULL || key_cb == 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; + } + + cb_data.config = config; + cb_data.login_token = login_token; + cb_data.error_msg = error_msg; + cb_data.key_cb = key_cb; + cb_data.cb_private = private; + + escaped_name_pattern = curl_easy_escape(curl, name_pattern != NULL ? + name_pattern : "*", 0); + if (escaped_name_pattern == NULL) { + pr_verbose(verbose, "Failed to url-escape the name pattern"); + rc = -EIO; + goto out; + } + + state_filter = _ekmf_build_state_filter(curl, states != NULL ? states : + KEY_STATE_ACTIVE); + if (state_filter == NULL) { + pr_verbose(verbose, "Failed to build the state filter"); + rc = -EIO; + goto out; + } + + tags_obj = json_object_new_object(); + JSON_CHECK_ERROR(tags_obj == NULL, rc, -ENOMEM, + "Failed to generate JSON object", verbose, out); + for (i = 0; tags != NULL && i < tags->num_tags; i++) { + if (tags->tags[i].name == NULL || tags->tags[i].value == NULL) { + rc = -EINVAL; + goto out; + } + + rc = json_object_object_add_ex(tags_obj, tags->tags[i].name, + json_object_new_string( + tags->tags[i].value), + 0); + JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to " + "JSON object", verbose, out); + } + + escaped_tags = curl_easy_escape(curl, json_object_to_json_string_ext( + tags_obj, JSON_C_TO_STRING_PLAIN | + JSON_C_TO_STRING_NOSLASHESCAPE), 0); + if (escaped_tags == NULL) { + pr_verbose(verbose, "Failed to url-escape the tags"); + rc = -EIO; + goto out; + } + + if (asprintf(&uri, EKMF_URI_KEYS_LIST, state_filter, + ORDER_BY_LABEL_ASC, escaped_name_pattern, + escaped_tags) < 0) { + pr_verbose(verbose, "asprintf failed"); + rc = -ENOMEM; + goto out; + } + + rc = _ekmf_list_request(config, uri, curl, _ekmf_key_cb, + &cb_data, login_token, error_msg, verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to perform the list request"); + if (rc > 0) + rc = -EIO; + goto out; + } + +out: + _ekmf_release_curl_handle(curl_handle, curl); + + if (login_token != NULL) + free(login_token); + if (uri != NULL) + free(uri); + if (state_filter != NULL) + free(state_filter); + if (escaped_name_pattern != NULL) + curl_free(escaped_name_pattern); + if (escaped_tags != NULL) + curl_free(escaped_tags); + if (tags_obj != NULL) + json_object_put(tags_obj); + + return rc; +} + +/** + * Get information about a key by its UUID. + * + * 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 key an address of a key info pointer. On return + * the pointer is updated to point to a newly allocated + * key info struct. It must be freed by the caller + * using ekmf_free_key_info when no longer needed. + * @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 + * get the key info + */ +int ekmf_get_key_info(const struct ekmf_config *config, CURL **curl_handle, + const char *key_uuid, struct ekmf_key_info **key, + char **error_msg, bool verbose) +{ + json_object *response_obj = NULL; + char *escaped_uuid = NULL; + char *login_token = NULL; + bool token_valid = false; + CURL *curl = NULL; + char *uri = NULL; + long status_code; + int rc; + + if (config == NULL || key_uuid == NULL || key == NULL) + return -EINVAL; + + *key = NULL; + + 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; + } + + 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; + } + + if (asprintf(&uri, EKMF_URI_KEYS_GET, escaped_uuid) < 0) { + pr_verbose(verbose, "asprintf failed"); + rc = -ENOMEM; + goto out; + } + + rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL, + login_token, &response_obj, NULL, + &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: + 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; + default: + pr_verbose(verbose, "REST Call failed with HTTP status code: " + "%ld", status_code); + rc = -EIO; + goto out; + } + + JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EBADMSG, + "No or invalid response", verbose, out); + + *key = calloc(1, sizeof(struct ekmf_key_info)); + if (*key == NULL) { + pr_verbose(verbose, "calloc failed"); + rc = -ENOMEM; + goto out; + } + + rc = _ekmf_build_key_info(config, curl, login_token, response_obj, + *key, true, error_msg, verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to build template info"); + goto out; + } + +out: + _ekmf_release_curl_handle(curl_handle, curl); + + if (response_obj != NULL) + json_object_put(response_obj); + if (login_token != NULL) + free(login_token); + if (uri != NULL) + free(uri); + if (escaped_uuid != NULL) + curl_free(escaped_uuid); + if (rc != 0 && *key != NULL) { + free_key_info(*key); + free(*key); + *key = NULL; + } + + return rc; +} + +/** + * Clones a key info structure by making a deep copy of all strings and + * arrays. + * The copied key info must be freed using ekmf_free_key_info() by + * the caller. + * + * @param src the source key info structure + * @param dest the destination key info structure + * + * @returns zero for success, a negative errno in case of an error + */ +int ekmf_clone_key_info(const struct ekmf_key_info *src, + struct ekmf_key_info **dest) +{ + if (src == NULL || dest == NULL) + return -EINVAL; + + *dest = calloc(1, sizeof(struct ekmf_key_info)); + if (*dest == NULL) + return -ENOMEM; + + return clone_key_info(src, *dest); +} + +/** + * Free a key info structure. + * + * @param key the key info to free + */ +void ekmf_free_key_info(struct ekmf_key_info *key) +{ + free_key_info(key); + + free(key); +} + /** * Generate a secure identity key used to identify the client to EKMFWeb. * The secure key blob is stored in a file specified in field diff --git a/libekmfweb/libekmfweb.map b/libekmfweb/libekmfweb.map index 14e57c45..7170163f 100644 --- a/libekmfweb/libekmfweb.map +++ b/libekmfweb/libekmfweb.map @@ -14,6 +14,10 @@ LIBEKMFWEB_1.0 { ekmf_get_last_seq_no; ekmf_clone_template_info; ekmf_free_template_info; + ekmf_list_keys; + ekmf_get_key_info; + ekmf_clone_key_info; + ekmf_free_key_info; ekmf_curl_destroy; local: *; }; diff --git a/libekmfweb/utilities.c b/libekmfweb/utilities.c index ace48dbf..adaf22bd 100644 --- a/libekmfweb/utilities.c +++ b/libekmfweb/utilities.c @@ -927,6 +927,25 @@ static char *cond_strdup(const char *str, bool copy) return (char *)str; } +/** + * Returns the start of the UUId part of a href link. + * Returns NULL if href is NULL, or if the UUID is not found. + * The returned pointer (if not NULL) is within the specified href string! + */ +static const char *get_uuid_from_href(const char *href) +{ + const char *ch; + + if (href == NULL) + return NULL; + + ch = strrchr(href, '/'); + if (ch == NULL) + return NULL; + + return ch + 1; +} + /** * Builds a list of tag definitions from a JSON array. * @@ -1204,6 +1223,441 @@ void free_template_info(struct ekmf_template_info *template) free_tag_def_list(&template->label_tags, true); } +/** + * Builds a list of tags from a JSON array. + * + * @param array a JSON array of tags + * @param tag_list the tag list to build + * @param copy if true, the string values are copied (via strdup), + * if false, the string values re-use the JSON object's + * string buffer (see json_object_get_string). + * + * @returns zero for success, a negative errno in case of an error + */ +int json_build_tag_list(json_object *array, struct ekmf_tag_list *tag_list, + bool copy) +{ + json_object *obj; + size_t i; + int rc = 0; + + if (array == NULL || tag_list == NULL || + !json_object_is_type(array, json_type_array)) + return -EINVAL; + + tag_list->num_tags = json_object_array_length(array); + tag_list->tags = calloc(tag_list->num_tags, sizeof(struct ekmf_tag)); + if (tag_list->tags == NULL) + return -ENOMEM; + + for (i = 0; i < tag_list->num_tags; i++) { + obj = json_object_array_get_idx(array, i); + if (obj == NULL) + return -EBADMSG; + + tag_list->tags[i].name = cond_strdup( + json_get_string(obj, "name"), copy); + tag_list->tags[i].value = cond_strdup( + json_get_string(obj, "value"), copy); + + if (tag_list->tags[i].name == NULL || + tag_list->tags[i].value == NULL) { + rc = -ENOMEM; + goto out; + } + } + +out: + if (rc != 0) + free_tag_list(tag_list, copy); + + return rc; +} + +/** + * Clones (copies) a tag list + * + * @param src the source tag list + * @param dest the destination tag list + * + * @returns zero for success, a negative errno in case of an error + */ +int clone_tag_list(const struct ekmf_tag_list *src, + struct ekmf_tag_list *dest) +{ + size_t i; + int rc = 0; + + if (src == NULL || dest == NULL) + return -EINVAL; + + dest->num_tags = src->num_tags; + if (dest->num_tags == 0) { + dest->tags = NULL; + return 0; + } + + dest->tags = calloc(dest->num_tags, sizeof(struct ekmf_tag)); + if (dest->tags == NULL) + return -ENOMEM; + + for (i = 0; i < dest->num_tags; i++) { + dest->tags[i].name = cond_strdup(src->tags[i].name, true); + if (dest->tags[i].name == NULL) { + rc = -ENOMEM; + goto out; + } + dest->tags[i].value = cond_strdup(src->tags[i].value, true); + if (dest->tags[i].value == NULL) { + rc = -ENOMEM; + goto out; + } + } + +out: + if (rc != 0) + free_tag_list(dest, true); + return rc; +} + +/** + * Free a tag list + * + * @param tag_list the tag list to free + * @param free_tags if true, the tag name and value string s are + * freed, otherwise only the array is freed. + */ +void free_tag_list(struct ekmf_tag_list *tag_list, bool free_tags) +{ + size_t i; + + if (tag_list == NULL || tag_list->tags == NULL) + return; + + for (i = 0; free_tags && i < tag_list->num_tags; i++) { + free((char *)tag_list->tags[i].name); + free((char *)tag_list->tags[i].value); + } + + free(tag_list->tags); + tag_list->tags = NULL; + tag_list->num_tags = 0; +} + +/** + * Builds the export control information from a JSON object. + * + * @param export_control the JSON oibject + * @param tag_def_list the tag list to build + * @param copy if true, the string values are copied (via strdup), + * if false, the string values re-use the JSON object's + * string buffer (see json_object_get_string). + * + * @returns zero for success, a negative errno in case of an error + */ +int json_build_export_control(json_object *export_control, + struct ekmf_export_control *export_info, + bool copy) +{ + json_object *obj, *array; + size_t i; + int rc = 0; + + if (export_control == NULL || export_info == NULL || + !json_object_is_type(export_control, json_type_object)) + return -EINVAL; + + if (!json_object_object_get_ex(export_control, "exportAllowed", &obj) || + !json_object_is_type(obj, json_type_boolean)) + return -EINVAL; + + export_info->export_allowed = json_object_get_boolean(obj); + + if (!json_object_object_get_ex(export_control, "allowedKeys", &array) || + !json_object_is_type(array, json_type_array)) + return -EINVAL; + + export_info->num_exporting_keys = json_object_array_length(array); + export_info->exporting_keys = calloc(export_info->num_exporting_keys, + sizeof(struct ekmf_exporting_key)); + if (export_info->exporting_keys == NULL) + return -ENOMEM; + + for (i = 0; i < export_info->num_exporting_keys; i++) { + obj = json_object_array_get_idx(array, i); + if (obj == NULL) + return -EBADMSG; + + export_info->exporting_keys[i].name = cond_strdup( + json_get_string(obj, "title"), copy); + export_info->exporting_keys[i].uuid = cond_strdup( + get_uuid_from_href( + json_get_string(obj, "href")), copy); + + if (export_info->exporting_keys[i].name == NULL || + export_info->exporting_keys[i].uuid == NULL) { + rc = -ENOMEM; + goto out; + } + } + +out: + if (rc != 0) + free_export_control(export_info, copy); + + return rc; +} + +/** + * Clones (copies) an export control info + * + * @param src the source export control + * @param dest the destination export control + * + * @returns zero for success, a negative errno in case of an error + */ +int clone_export_control(const struct ekmf_export_control *src, + struct ekmf_export_control *dest) +{ + size_t i; + int rc = 0; + + if (src == NULL || dest == NULL) + return -EINVAL; + + dest->export_allowed = src->export_allowed; + + dest->num_exporting_keys = src->num_exporting_keys; + if (dest->num_exporting_keys == 0) { + dest->exporting_keys = NULL; + return 0; + } + + dest->exporting_keys = calloc(dest->num_exporting_keys, + sizeof(struct ekmf_exporting_key)); + if (dest->exporting_keys == NULL) + return -ENOMEM; + + for (i = 0; i < dest->num_exporting_keys; i++) { + dest->exporting_keys[i].name = + cond_strdup(src->exporting_keys[i].name, true); + if (dest->exporting_keys[i].name == NULL) { + rc = -ENOMEM; + goto out; + } + dest->exporting_keys[i].uuid = + cond_strdup(src->exporting_keys[i].uuid, true); + if (dest->exporting_keys[i].uuid == NULL) { + rc = -ENOMEM; + goto out; + } + } + +out: + if (rc != 0) + free_export_control(dest, true); + return rc; +} + +/** + * Free export control infos + * + * @param export_control the export control infos to free + * @param free_tags if true, the exporting keys name and uuid strings + * are freed, otherwise only the array is freed. + */ +void free_export_control(struct ekmf_export_control *export_control, + bool free_keys) +{ + size_t i; + + if (export_control == NULL || export_control->exporting_keys == NULL) + return; + + for (i = 0; free_keys && i < export_control->num_exporting_keys; i++) { + free((char *)export_control->exporting_keys[i].name); + free((char *)export_control->exporting_keys[i].uuid); + } + + free(export_control->exporting_keys); + export_control->exporting_keys = NULL; + export_control->num_exporting_keys = 0; +} + +/** + * Builds a key info structure from a JSON object. + * + * @param obj a JSON object containing the key info + * @param custom_tags a JSON array containing the custom tags + * @param export_control a JSON object containing the export_control infos + * @param key the key info struct to build + * @param copy if true, the string values are copied (via strdup), + * if false, the string values re-use the JSON object's + * string buffer (see json_object_get_string). + * + * @returns zero for success, a negative errno in case of an error + */ +int json_build_key_info(json_object *obj, json_object *custom_tags, + json_object *export_control, + struct ekmf_key_info *key, bool copy) +{ + json_object *field, *label_tags = NULL; + int rc = 0; + + if (obj == NULL || custom_tags == NULL || key == NULL || + !json_object_is_type(obj, json_type_object) || + !json_object_is_type(custom_tags, json_type_array)) + return -EINVAL; + + key->label = cond_strdup(json_get_string(obj, "label"), copy); + key->description = cond_strdup(json_get_string(obj, "description"), + copy); + key->uuid = cond_strdup(json_get_string(obj, "keyId"), copy); + key->key_type = cond_strdup(json_get_string(obj, "type"), copy); + key->algorithm = cond_strdup(json_get_string(obj, "algorithm"), copy); + if (json_object_object_get_ex(obj, "length", &field) && + json_object_is_type(field, json_type_int)) + key->key_size = json_object_get_int(field); + else + rc = -EBADMSG; + + key->state = cond_strdup(json_get_string(obj, "state"), copy); + key->keystore_type = cond_strdup(json_get_string(obj, "keystoreType"), + copy); + if (json_object_object_get_ex(obj, "template", &field) && + json_object_is_type(field, json_type_object)) { + key->template = cond_strdup(json_get_string(field, "title"), + copy); + key->template_uuid = cond_strdup(get_uuid_from_href( + json_get_string(field, "href")), copy); + } else { + rc = -EBADMSG; + } + key->activate_on = cond_strdup(json_get_string(obj, "activationDate"), + copy); + key->expires_on = cond_strdup(json_get_string(obj, "expirationDate"), + copy); + key->created_on = cond_strdup(json_get_string(obj, "createdOn"), copy); + key->updated_on = cond_strdup(json_get_string(obj, "updatedOn"), copy); + + if (rc != 0 || key->label == NULL || key->uuid == NULL || + key->algorithm == NULL || key->state == NULL || + key->keystore_type == NULL || key->template == NULL || + key->template_uuid == NULL || key->activate_on == NULL || + key->expires_on == NULL || key->created_on == NULL || + key->updated_on == NULL) { + rc = (rc != 0 ? rc : -ENOMEM); + goto out; + } + + json_object_object_get_ex(obj, "labelTags", &label_tags); + rc = json_build_tag_list(label_tags, &key->label_tags, copy); + if (rc != 0) + goto out; + + rc = json_build_tag_list(custom_tags, &key->custom_tags, copy); + if (rc != 0) + goto out; + + rc = json_build_export_control(export_control, &key->export_control, + copy); + if (rc != 0) + goto out; + +out: + if (rc != 0) { + free_tag_list(&key->label_tags, copy); + free_tag_list(&key->custom_tags, copy); + free_export_control(&key->export_control, copy); + if (copy) + free_key_info(key); + } + + return rc; +} + +/** + * Clones (copies) a key info structure + * + * @param src the source key info structure + * @param dest the destination key info structure + * + * @returns zero for success, a negative errno in case of an error + */ +int clone_key_info(const struct ekmf_key_info *src, + struct ekmf_key_info *dest) +{ + int rc; + + if (src == NULL || dest == NULL) + return -EINVAL; + + dest->label = cond_strdup(src->label, true); + dest->description = cond_strdup(src->description, true); + dest->uuid = cond_strdup(src->uuid, true); + dest->key_type = cond_strdup(src->key_type, true); + dest->algorithm = cond_strdup(src->algorithm, true); + dest->key_size = src->key_size; + dest->state = cond_strdup(src->state, true); + dest->keystore_type = cond_strdup(src->keystore_type, true); + dest->template = cond_strdup(src->template, true); + dest->template_uuid = cond_strdup(src->template_uuid, true); + dest->activate_on = cond_strdup(src->activate_on, true); + dest->expires_on = cond_strdup(src->expires_on, true); + dest->created_on = cond_strdup(src->created_on, true); + dest->updated_on = cond_strdup(src->updated_on, true); + if (dest->label == NULL || dest->uuid == NULL || + dest->algorithm == NULL || dest->state == NULL || + dest->keystore_type == NULL || dest->template == NULL || + dest->template_uuid == NULL || dest->activate_on == NULL || + dest->expires_on == NULL || dest->created_on == NULL || + dest->updated_on == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = clone_tag_list(&src->label_tags, &dest->label_tags); + if (rc != 0) + goto out; + + rc = clone_tag_list(&src->custom_tags, &dest->custom_tags); + if (rc != 0) + goto out; +out: + if (rc != 0) + free_key_info(dest); + return rc; +} + +/** + * Free a key info structure + * + * @param key the key info to free + */ +void free_key_info(struct ekmf_key_info *key) +{ + if (key == NULL) + return; + + free((char *)key->label); + free((char *)key->description); + free((char *)key->uuid); + free((char *)key->key_type); + free((char *)key->algorithm); + free((char *)key->state); + free((char *)key->keystore_type); + free((char *)key->template); + free((char *)key->template_uuid); + free((char *)key->activate_on); + free((char *)key->expires_on); + free((char *)key->created_on); + free((char *)key->updated_on); + + free_tag_list(&key->label_tags, true); + free_tag_list(&key->custom_tags, true); + free_export_control(&key->export_control, true); +} + struct ecc_curve_info { int curve_nid; enum { diff --git a/libekmfweb/utilities.h b/libekmfweb/utilities.h index 71bbbb47..85c06cca 100644 --- a/libekmfweb/utilities.h +++ b/libekmfweb/utilities.h @@ -55,6 +55,27 @@ int clone_template_info(const struct ekmf_template_info *src, struct ekmf_template_info *dest); void free_template_info(struct ekmf_template_info *template); +int json_build_tag_list(json_object *array, struct ekmf_tag_list *tag_list, + bool copy); +int clone_tag_list(const struct ekmf_tag_list *src, + struct ekmf_tag_list *dest); +void free_tag_list(struct ekmf_tag_list *tag_list, bool free_tags); + +int json_build_export_control(json_object *export_control, + struct ekmf_export_control *export_info, + bool copy); +int clone_export_control(const struct ekmf_export_control *src, + struct ekmf_export_control *dest); +void free_export_control(struct ekmf_export_control *export_control, + bool free_keys); + +int json_build_key_info(json_object *obj, json_object *custom_tags, + json_object *export_control, + struct ekmf_key_info *key, bool copy); +int clone_key_info(const struct ekmf_key_info *src, + struct ekmf_key_info *dest); +void free_key_info(struct ekmf_key_info *key); + 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);