From 9f99706c26c0b9eedda0ee42fa902b881662abf9 Mon Sep 17 00:00:00 2001 From: Ingo Franzki Date: Mon, 4 May 2020 11:20:40 +0200 Subject: [PATCH] libekmfweb: Retrieve a key from EKMF Web using an EC-DH protocol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To retrieve a secure key from EKMF Web, a Elliptic-curve Diffie-Hellman (EC-DH) protocol is used to securely transport the key, without revealing the key to be transported in clear. The key to be transported remains encrypted all the time, either encrypted with the master key of the cryptographic adapter on the source or target side, or with a transport key, that is derived using the EC-DH key agreement protocol. The transport key is also a secure key, itself encrypted with the master key of the cryptographic adapters on both sides. To generate the transport key, each side generates a new secure ECC session key with its cryptographic adapter. This session keys are then used with EC-DH to derive the secure transport key. The secure key to be transported is then exported by EKMF Web under the transport key, is sent to the client, and is then imported using the transport key. The key retrieval request is signed using the identity key of the client. EKMF Web knows the public key of the client's identity key through a one time registration process, and can therefore verify the signature with the client's public key. The response is also signed by the EKMF Web server's identity key, and the client can verify the signature with the server's public key that it retrieved once during registration. Signed-off-by: Ingo Franzki Signed-off-by: Jan Höppner --- include/ekmfweb/ekmfweb.h | 84 ++++ libekmfweb/cca.c | 593 +++++++++++++++++++++- libekmfweb/cca.h | 112 +++++ libekmfweb/ekmfweb.c | 999 +++++++++++++++++++++++++++++++++++++- libekmfweb/libekmfweb.map | 3 + libekmfweb/utilities.c | 832 +++++++++++++++++++++++++++++++ libekmfweb/utilities.h | 32 ++ 7 files changed, 2649 insertions(+), 6 deletions(-) diff --git a/include/ekmfweb/ekmfweb.h b/include/ekmfweb/ekmfweb.h index 5f1cd8ce..8647542e 100644 --- a/include/ekmfweb/ekmfweb.h +++ b/include/ekmfweb/ekmfweb.h @@ -13,6 +13,8 @@ #include #include +typedef void CURL; + struct ekmf_config { /** The base URL of the server. Should use https:// ! */ const char *base_url; @@ -52,6 +54,9 @@ struct ekmf_config { * This key represents the client identity against EKMFWeb. Some * requests sent to EKMFWeb are signed with this (secure) key */ const char *identity_secure_key; + /** File name of a PEM file containing the EKMFWeb servers public key + * used to sign key export responses. */ + const char *ekmf_server_pubkey; }; struct ekmf_cca_lib { @@ -344,4 +349,83 @@ int ekmf_generate_ss_cert(const struct ekmf_config *config, const char *cert_pem_filename, const struct ekmf_ext_lib *ext_lib, bool verbose); +/** + * Request the EKMFWeb server's public signing key and store it into PEM file + * specified in field server_pubkey of the config structure. + * + * 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 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. + */ +int ekmf_get_public_key(const struct ekmf_config *config, CURL **curl_handle, + char **error_msg, bool verbose); + +/** + * Requests a key to be retrieved from EKMFweb and imported under the current + * HSM's master key. + * + * 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 retrieve + * @param sess_ec_curve_nid The OpenSSL nid of the EC curve used for the session + * ECC key. If 0, then the default curve is used. + * @param sign_rsa_digest_nid The OpenSSL nid of a digest used to sign the + * request with if the identity key is an RSA-type key. + * If 0, then the default digest is used. + * Ignored for ECC-type identity keys. + * @param use_rsa_pss If true, and the identity key is an RSA-type key, + * use RSA-PSS to sign the request. + * @param signature_kid the Key ID for the signature of the request + * @param key_blob a buffer to store the retrieved key blob to + * @param key_blob_length On entry: the size ofthe buffer + * On return: the size of the key blob retrieved + * @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 ext_lib External secure key crypto library to use + * @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 + * retrieve the key + */ +int ekmf_retrieve_key(const struct ekmf_config *config, CURL **curl_handle, + const char *key_uuid, int sess_ec_curve_nid, + int sign_rsa_digest_nid, bool use_rsa_pss, + const char *signature_kid, unsigned char *key_blob, + size_t *key_blob_length, char **error_msg, + const struct ekmf_ext_lib *ext_lib, bool verbose); + +/** + * Close the connection to the EKMFWeb server by destroying the CURL handle. + * + * @param curl_handle the CURL handle to destroy + */ +void ekmf_curl_destroy(CURL *curl_handle); + #endif diff --git a/libekmfweb/cca.c b/libekmfweb/cca.c index cb32107e..411057c7 100644 --- a/libekmfweb/cca.c +++ b/libekmfweb/cca.c @@ -52,6 +52,13 @@ struct cca_rsa_key_pair_value_struct { unsigned char public_exponent[3]; } __packed; +struct cca_ecc_pub_key_value_struct { + uint8_t curve_type; + uint8_t reserved; + uint16_t curve_length; + uint16_t public_key_len; +} __packed; + #define CCA_PRIME_CURVE 0x00 #define CCA_BRAINPOOL_CURVE 0x01 @@ -160,9 +167,14 @@ static int _cca_get_library_functions(const struct ekmf_cca_lib *cca_lib, cca->dll_CSNDPKG = (CSNDPKG_t)dlsym(cca_lib->cca_lib, "CSNDPKG"); cca->dll_CSNDKTC = (CSNDKTC_t)dlsym(cca_lib->cca_lib, "CSNDKTC"); cca->dll_CSNDDSG = (CSNDDSG_t)dlsym(cca_lib->cca_lib, "CSNDDSG"); + cca->dll_CSNBKTB2 = (CSNBKTB2_t)dlsym(cca_lib->cca_lib, "CSNBKTB2"); + cca->dll_CSNDEDH = (CSNDEDH_t)dlsym(cca_lib->cca_lib, "CSNDEDH"); + cca->dll_CSNDSYI2 = (CSNDSYI2_t)dlsym(cca_lib->cca_lib, "CSNDSYI2"); if (cca->dll_CSNDPKB == NULL || cca->dll_CSNDPKG == NULL || - cca->dll_CSNDKTC == NULL || cca->dll_CSNDDSG == NULL) + cca->dll_CSNDKTC == NULL || cca->dll_CSNDDSG == NULL || + cca->dll_CSNBKTB2 == NULL || cca->dll_CSNDEDH == NULL || + cca->dll_CSNDSYI2 == NULL) return -EIO; return 0; @@ -600,6 +612,171 @@ out: return rc; } +/** + * Extracts the ECC public key from an CCA internal ECC key token, and returns a + * JSON object representing the public key as JSON Web Key (JWK, see RFC7517). + * The returned JSON objects must be freed by the caller using json_object_put() + * when no longer needed. + * + * @param key_token the key token containing an CCA ECC key + * @param key_token_length the size of the key token + * qparam jwk the ECC public key as JWT JSON object + * @param verbose if true, verbose messages are printed + * + * @returns a negative errno in case of an error, 0 if success. + */ +int cca_get_ecc_pub_key_as_json_web_key(const unsigned char *key_token, + size_t key_token_length, + json_object **jwk, bool verbose) +{ + const struct cca_ecc_pub_key_section *ecc_pub_section; + const struct cca_section_header *section_hdr; + const struct cca_token_header *token_hdr; + const unsigned char *ecc_pub_key, *x, *y; + json_object *jwk_obj = NULL; + unsigned char *buf = NULL; + size_t ofs, prime_len; + int nid, y_bit = 0; + int rc = 0; + + if (key_token == NULL || jwk == NULL) + return -EINVAL; + + if (key_token_length < sizeof(struct cca_token_header)) { + pr_verbose(verbose, "key token length too small"); + return -EINVAL; + } + + token_hdr = (struct cca_token_header *)key_token; + if (token_hdr->token_length > key_token_length) { + pr_verbose(verbose, "key token length too small"); + return -EINVAL; + } + if (token_hdr->token_identifier != CCA_TOKEN_ID_INTERNAL_PKA) { + pr_verbose(verbose, "not an internal PKA token"); + return -EINVAL; + } + if (token_hdr->token_version1 != CCA_TOKEN_VERS1_V0) { + pr_verbose(verbose, "invalid token version"); + return -EINVAL; + } + + ofs = sizeof(struct cca_token_header); + section_hdr = (struct cca_section_header *)&key_token[ofs]; + + while (section_hdr->section_identifier != CCA_SECTION_ID_ECC_PUBL) { + ofs += section_hdr->section_length; + if (ofs >= token_hdr->token_length) { + pr_verbose(verbose, "no ECC public key section found"); + return -EINVAL; + } + section_hdr = (struct cca_section_header *)&key_token[ofs]; + } + + if (section_hdr->section_version != 0x00) { + pr_verbose(verbose, "invalid ECC public key section version"); + return -EINVAL; + } + if (section_hdr->section_length < + sizeof(struct cca_ecc_pub_key_section)) { + pr_verbose(verbose, "invalid ECC public key section length"); + return -EINVAL; + } + + ecc_pub_section = (struct cca_ecc_pub_key_section *)section_hdr; + ofs += sizeof(struct cca_ecc_pub_key_section); + ecc_pub_key = &key_token[ofs]; + + if (ecc_pub_section->curve_type == CCA_PRIME_CURVE) + nid = ecc_get_prime_curve_by_prime_bits( + ecc_pub_section->prime_bits_length); + else if (ecc_pub_section->curve_type == CCA_BRAINPOOL_CURVE) + nid = ecc_get_brainpool_curve_by_prime_bits( + ecc_pub_section->prime_bits_length); + else + nid = 0; + if (nid == 0) { + pr_verbose(verbose, "unsupported curve"); + rc = -EIO; + goto out; + } + prime_len = ecc_get_curve_prime_length(nid); + + x = ecc_pub_key + 1; + + /* First byte of public key contains indication of key compression */ + switch (ecc_pub_key[0]) { + case POINT_CONVERSION_COMPRESSED: + case POINT_CONVERSION_COMPRESSED + POINT_CONVERSION_ODD_EVEN: + /* Compressed form, only x is available */ + y_bit = (ecc_pub_key[0] & POINT_CONVERSION_ODD_EVEN) ? 1 : 0; + + buf = malloc(prime_len); + if (buf == NULL) { + pr_verbose(verbose, "malloc failed"); + rc = -ENOMEM; + goto out; + } + + rc = ecc_calculate_y_coordinate(nid, prime_len, x, y_bit, buf); + if (rc != 0) { + pr_verbose(verbose, "ecc_calculate_y_coordinate " + "failed"); + goto out; + } + + y = buf; + break; + + case POINT_CONVERSION_UNCOMPRESSED: + case POINT_CONVERSION_HYBRID: + case POINT_CONVERSION_HYBRID + POINT_CONVERSION_ODD_EVEN: + /* Uncompressed or hybrid, x and y are available */ + y = x + prime_len; + break; + + default: + pr_verbose(verbose, "invalid compression indication"); + rc = -EIO; + goto out; + } + + /* construct the JWK */ + jwk_obj = json_object_new_object(); + if (jwk_obj == NULL) { + rc = -ENOMEM; + goto out; + } + + /* + * Note: The order of the fields is important, EKMFWeb expects it in + * exactly this order! + */ + rc = json_object_object_add_ex(jwk_obj, "kty", + json_object_new_string("EC"), 0); + rc |= json_object_object_add_ex(jwk_obj, "crv", json_object_new_string( + ecc_get_curve_id(nid)), 0); + rc |= json_object_object_add_ex(jwk_obj, "x", + json_object_new_base64url(x, prime_len), + 0); + rc |= json_object_object_add_ex(jwk_obj, "y", + json_object_new_base64url(y, prime_len), + 0); + if (rc != 0) { + rc = -EIO; + goto out; + } + + *jwk = jwk_obj; + +out: + if (buf != NULL) + free(buf); + if (rc != 0 && jwk_obj != NULL) + json_object_put(jwk_obj); + return rc; +} + /** * Extracts the RSA public key from an CCA internal RSA key token, and returns a * it a OpenSSL PKEY. @@ -773,6 +950,419 @@ int cca_reencipher_key(const struct ekmf_cca_lib *cca_lib, return 0; } +/** + * Import a CCA key from a JSON object representing a key as JSON Web Key (JWK, + * see RFC7517). The JWK can either be an ECC public key (kty=EC), or an + * symmetric key (kty=oct) containing an CCA external variable length key token + * (alg=A256KW-CCA). + * + * @param cca_lib the CCA library structure + * @param jwk the JWT JSON object containing the key to import + * @param key_token a buffer to store the imported key token + * @param key_token_length On entry: the size of the buffer + * On return: the size of the key token + + * @param verbose if true, verbose messages are printed + * + * @returns a negative errno in case of an error, 0 if success. + */ +int cca_import_key_from_json_web_key(const struct ekmf_cca_lib *cca_lib, + json_object *jwk, unsigned char *key_token, + size_t *key_token_length, bool verbose) +{ + long return_code, reason_code, rule_array_count, exit_data_len = 0; + long key_value_struct_length, private_key_name_length = 0; + struct cca_ecc_pub_key_value_struct *key_value_struct = NULL; + unsigned char private_key_name[CCA_KEY_ID_SIZE] = { 0, }; + unsigned char rule_array[1 * CCA_KEYWORD_SIZE] = { 0, }; + unsigned char *exit_data = NULL; + size_t prime_len, q_len, len; + unsigned char *param2 = NULL; + struct cca_token_header *hdr; + const char *kty, *crv, *alg; + struct cca_lib cca; + long token_length; + unsigned char *q; + long param1 = 0; + int nid, rc = 0; + + if (cca_lib == NULL || jwk == NULL || key_token == NULL || + key_token_length == NULL) + return -EINVAL; + + rc = _cca_get_library_functions(cca_lib, &cca); + if (rc != 0) { + pr_verbose(verbose, "Failed to get CCA functions from library"); + return rc; + } + + memset(key_token, 0, *key_token_length); + + kty = json_get_string(jwk, "kty"); + if (kty == NULL) { + pr_verbose(verbose, "JWK does not contain field 'kty'"); + rc = -EIO; + goto out; + } + + if (strcmp(kty, "EC") == 0) { + crv = json_get_string(jwk, "crv"); + if (crv == NULL) { + pr_verbose(verbose, "JWK does not contain field 'crv'"); + rc = -EIO; + goto out; + } + + nid = ecc_get_curve_by_id(crv); + if (nid == 0) { + pr_verbose(verbose, "curve '%s' not supported", crv); + rc = -EIO; + goto out; + } + + prime_len = ecc_get_curve_prime_length(nid); + if (prime_len == 0) { + pr_verbose(verbose, "curve %d not supported", nid); + rc = -EIO; + goto out; + } + + q_len = 1 + 2 * prime_len; + key_value_struct_length = + sizeof(struct cca_ecc_pub_key_value_struct) + q_len; + key_value_struct = (struct cca_ecc_pub_key_value_struct *) + malloc(key_value_struct_length); + if (key_value_struct == NULL) { + pr_verbose(verbose, "malloc failed"); + rc = -ENOMEM; + goto out; + } + + memset(key_value_struct, 0, sizeof(*key_value_struct)); + if (ecc_is_prime_curve(nid)) { + key_value_struct->curve_type = CCA_PRIME_CURVE; + } else if (ecc_is_brainpool_curve(nid)) { + key_value_struct->curve_type = CCA_BRAINPOOL_CURVE; + } else { + pr_verbose(verbose, "Unsupported curve: %d", nid); + rc = -EINVAL; + goto out; + } + key_value_struct->curve_length = ecc_get_curve_prime_bits(nid); + key_value_struct->public_key_len = q_len; + + q = ((unsigned char *)key_value_struct) + + sizeof(struct cca_ecc_pub_key_value_struct); + q[0] = POINT_CONVERSION_UNCOMPRESSED; + + len = prime_len; + rc = json_object_get_base64url(jwk, "x", &q[1], &len); + if (rc != 0) { + pr_verbose(verbose, "Failed to get and decode x"); + goto out; + } + + len = prime_len; + rc = json_object_get_base64url(jwk, "y", &q[1 + prime_len], + &len); + if (rc != 0) { + pr_verbose(verbose, "Failed to get and decode y"); + goto out; + } + + rule_array_count = 1; + memcpy(rule_array, "ECC-PUBL", CCA_KEYWORD_SIZE); + + token_length = *key_token_length; + + cca.dll_CSNDPKB(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &key_value_struct_length, + (unsigned char *)key_value_struct, + &private_key_name_length, private_key_name, + ¶m1, param2, ¶m1, param2, + ¶m1, param2, ¶m1, param2, + ¶m1, param2, + &token_length, key_token); + if (return_code != 0) { + pr_verbose(verbose, "CCA CSNDPKB (EC KEY TOKEN BUILD) " + "failed: return_code: %ld reason_code: %ld", + return_code, reason_code); + return -EIO; + } + + *key_token_length = token_length; + } else if (strcmp(kty, "oct") == 0) { + alg = json_get_string(jwk, "alg"); + if (alg == NULL) { + pr_verbose(verbose, "JWK does not contain field 'alg'"); + rc = -EIO; + goto out; + } + + if (strcmp(alg, "A256KW-CCA") != 0) { + pr_verbose(verbose, "JWK alg is not A256KW-CCA"); + rc = -EIO; + goto out; + } + + rc = json_object_get_base64url(jwk, "k", key_token, + key_token_length); + if (rc != 0) { + pr_verbose(verbose, "failed to get and decode k"); + goto out; + } + + /* Ensure that this is an CCA external AES CIPHER key token */ + if (*key_token_length < sizeof(struct cca_token_header)) { + pr_verbose(verbose, "key token is too small"); + rc = -EIO; + goto out; + } + hdr = (struct cca_token_header *)key_token; + if (hdr->token_identifier != CCA_TOKEN_ID_EXTERNAL_SYMMETRIC || + hdr->token_version2 != CCA_TOKEN_VERS2_AES_CIPHER || + *key_token_length < hdr->token_length) { + pr_verbose(verbose, "key token is not a valid CCA " + "external AES CIPHER key"); + rc = -EIO; + goto out; + } + } else { + pr_verbose(verbose, "Key type '%s' not supported", kty); + rc = -EIO; + goto out; + } + +out: + if (key_value_struct != NULL) + free(key_value_struct); + + return rc; +} + +/** + * Drives an AES-256 key using the ED-DH key derivation method using a local ECC + * private/public key pair, a foreign public ECC key, and a shared party + * information data. The derived key is an internal CCA AES key token containing + * the derived key in its IMPORTER key form. + * + * @param cca_lib the CCA library structure + * @param priv_ecc_keyf_token the ECC private key token + * @param priv_ecc_key_token_length the length of the ECC private key token + * @param pub_ecc_key_token the ECC public key token of the other side + * @param pub_ecc_key_token_length the length of the ECC public key token + * @param party_info the shared data used on both sides + * @param party_info_length the length of the shared data + * @param kdf the key derivation function to use + * @param derived_key_token a buffer to store the derived key token + * @param derived_key_token_length On entry: the size of the buffer + * On return: the size of the derived key token + * @param verbose if true, verbose messages are printed + * + * @returns a negative errno in case of an error, 0 if success. + */ +int cca_ec_dh_derive_importer(const struct ekmf_cca_lib *cca_lib, + const unsigned char *priv_ecc_key_token, + size_t priv_ecc_key_token_length, + const unsigned char *pub_ecc_key_token, + size_t pub_ecc_key_token_length, + const unsigned char *party_info, + size_t party_info_length, + enum cca_kdf kdf, + unsigned char *derived_key_token, + size_t *derived_key_token_length, + bool verbose) +{ + long return_code, reason_code, rule_array_count, exit_data_len = 0; + long priv_length, pub_length, info_length, derived_length; + unsigned char rule_array[3 * CCA_KEYWORD_SIZE] = { 0, }; + unsigned char key_name[CCA_KEY_ID_SIZE] = { 0, }; + long key_name_length = 0, key_skeleton_length; + unsigned char *exit_data = NULL; + unsigned char *param2 = NULL; + long key_bit_length = 256; + struct cca_lib cca; + long param1 = 0; + int rc; + + if (cca_lib == NULL || priv_ecc_key_token == NULL || + pub_ecc_key_token == NULL || party_info == NULL || + derived_key_token == NULL || derived_key_token_length == NULL) + return -EINVAL; + + rc = _cca_get_library_functions(cca_lib, &cca); + if (rc != 0) { + pr_verbose(verbose, "Failed to get CCA functions from library"); + return rc; + } + + memset(derived_key_token, 0, *derived_key_token_length); + + rule_array_count = 3; + memcpy(rule_array, "INTERNAL", CCA_KEYWORD_SIZE); + memcpy(rule_array + CCA_KEYWORD_SIZE, "AES ", CCA_KEYWORD_SIZE); + memcpy(rule_array + 2 * CCA_KEYWORD_SIZE, "IMPORTER", CCA_KEYWORD_SIZE); + + key_skeleton_length = *derived_key_token_length; + + cca.dll_CSNBKTB2(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + ¶m1, param2, + &key_name_length, key_name, + ¶m1, param2, + ¶m1, param2, + ¶m1, param2, + &key_skeleton_length, derived_key_token); + if (return_code != 0) { + pr_verbose(verbose, "CCA CSNBKTB2 (KEY TOKEN BUILD2) failed: " + "return_code: %ld reason_code: %ld", return_code, + reason_code); + return -EIO; + } + + switch (kdf) { + case CCA_KDF_ANS_X9_63_CCA: + rule_array_count = 1; + memcpy(rule_array, "DERIV01 ", CCA_KEYWORD_SIZE); + break; + case CCA_KDF_ANS_X9_63_SHA224: + rule_array_count = 2; + memcpy(rule_array, "DERIV02 ", CCA_KEYWORD_SIZE); + memcpy(rule_array + CCA_KEYWORD_SIZE, "SHA-224 ", + CCA_KEYWORD_SIZE); + break; + case CCA_KDF_ANS_X9_63_SHA256: + rule_array_count = 2; + memcpy(rule_array, "DERIV02 ", CCA_KEYWORD_SIZE); + memcpy(rule_array + CCA_KEYWORD_SIZE, "SHA-256 ", + CCA_KEYWORD_SIZE); + break; + case CCA_KDF_ANS_X9_63_SHA384: + rule_array_count = 2; + memcpy(rule_array, "DERIV02 ", CCA_KEYWORD_SIZE); + memcpy(rule_array + CCA_KEYWORD_SIZE, "SHA-384 ", + CCA_KEYWORD_SIZE); + break; + case CCA_KDF_ANS_X9_63_SHA512: + rule_array_count = 2; + memcpy(rule_array, "DERIV02 ", CCA_KEYWORD_SIZE); + memcpy(rule_array + CCA_KEYWORD_SIZE, "SHA-512 ", + CCA_KEYWORD_SIZE); + break; + default: + pr_verbose(verbose, "Invalid CCA KDF: %d", kdf); + return -EINVAL; + } + + priv_length = priv_ecc_key_token_length; + pub_length = pub_ecc_key_token_length; + derived_length = *derived_key_token_length; + info_length = party_info_length; + + cca.dll_CSNDEDH(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &priv_length, (unsigned char *)priv_ecc_key_token, + ¶m1, param2, + &pub_length, (unsigned char *)pub_ecc_key_token, + ¶m1, param2, + &info_length, (unsigned char *)party_info, + &key_bit_length, + ¶m1, param2, ¶m1, param2, + ¶m1, param2, ¶m1, param2, + ¶m1, param2, ¶m1, param2, + &derived_length, derived_key_token); + + if (return_code != 0) { + pr_verbose(verbose, "CCA CSNDEDH (EC DIFFIE-HELLMAN) failed: " + "return_code: %ld reason_code: %ld", return_code, + reason_code); + return -EIO; + } + + *derived_key_token_length = derived_length; + + return 0; +} + +/** + * Imports an CCA external variable length AES key token using a wrapping key + * in IMPORTER key form. + * + * @param cca_lib the CCA library structure + * @param external_key_token the external key to import + * @param external_key_token_length the length of the external key + * @param importer_key_token the wrapping key in IMPORTER key form + * @param importer_key_token_length the length of the wrapping key + * @param imported_key_token a buffer to store the derived key token + * @param imported_key_token_length On entry: the size of the buffer + * On return: the size of the imported key token + * @param verbose if true, verbose messages are printed + * + * @returns a negative errno in case of an error, 0 if success. + */ +int cca_import_external_key(const struct ekmf_cca_lib *cca_lib, + const unsigned char *external_key_token, + size_t external_key_token_length, + const unsigned char *importer_key_token, + size_t importer_key_token_length, + unsigned char *imported_key_token, + size_t *imported_key_token_length, + bool verbose) +{ + long return_code, reason_code, rule_array_count, exit_data_len = 0; + unsigned char rule_array[2 * CCA_KEYWORD_SIZE] = { 0, }; + unsigned char key_name[CCA_KEY_ID_SIZE] = { 0, }; + long ext_length, importer_length, imp_length; + unsigned char *exit_data = NULL; + long key_name_length = 0; + struct cca_lib cca; + int rc; + + if (cca_lib == NULL || external_key_token == NULL || + importer_key_token == NULL || imported_key_token == NULL || + imported_key_token_length == NULL) + return -EINVAL; + + rc = _cca_get_library_functions(cca_lib, &cca); + if (rc != 0) { + pr_verbose(verbose, "Failed to get CCA functions from library"); + return rc; + } + + memset(imported_key_token, 0, *imported_key_token_length); + + rule_array_count = 2; + memcpy(rule_array, "AES ", CCA_KEYWORD_SIZE); + memcpy(rule_array + CCA_KEYWORD_SIZE, "AESKW ", CCA_KEYWORD_SIZE); + + ext_length = external_key_token_length; + importer_length = importer_key_token_length; + imp_length = *imported_key_token_length; + + cca.dll_CSNDSYI2(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &ext_length, (unsigned char *)external_key_token, + &importer_length, (unsigned char *)importer_key_token, + &key_name_length, key_name, + &imp_length, imported_key_token); + + if (return_code != 0) { + pr_verbose(verbose, "CCA CSNDSYI2 (SYMM. KEY IMPORT) failed: " + "return_code: %ld reason_code: %ld", return_code, + reason_code); + return -EIO; + } + + *imported_key_token_length = imp_length; + + return 0; +} + static const unsigned char der_DigestInfo_SHA1[] = { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14, }; @@ -1200,3 +1790,4 @@ out: return rc; } + diff --git a/libekmfweb/cca.h b/libekmfweb/cca.h index f7c5f342..10a67fa6 100644 --- a/libekmfweb/cca.h +++ b/libekmfweb/cca.h @@ -15,6 +15,8 @@ #include +#include + #include "ekmfweb/ekmfweb.h" /* CCA PKA Key Generate function */ @@ -80,14 +82,87 @@ typedef void (*CSNDDSG_t)(long *return_code, long *signature_bit_length, unsigned char *signature_field); +/* CCA Key Token Build2 function */ +typedef void (*CSNBKTB2_t)(long *return_code, + long *reason_code, + long *exit_data_length, + unsigned char *exit_data, + long *rule_array_count, + unsigned char *rule_array, + long *clear_key_bit_length, + unsigned char *clear_key_value, + long *key_name_length, + unsigned char *key_name, + long *user_associated_data_length, + unsigned char *user_associated_data, + long *token_data_length, + unsigned char *token_data, + long *verb_data_length, + unsigned char *verb_data, + long *target_key_token_length, + unsigned char *target_key_token); + +/* CCA EC Diffie-Hellman function */ +typedef void (*CSNDEDH_t)(long *return_code, + long *reason_code, + long *exit_data_length, + unsigned char *exit_data, + long *rule_array_count, + unsigned char *rule_array, + long *private_key_identifier_length, + unsigned char *private_key_identifier, + long *private_KEK_key_identifier_length, + unsigned char *private_KEK_key_identifier, + long *public_key_identifier_length, + unsigned char *public_key_identifier, + long *chaining_vector_length, + unsigned char *chaining_vector, + long *party_info_length, + unsigned char *party_info, + long *key_bit_length, + long *reserved_1_length, + unsigned char *reserved_1, + long *reserved_2_length, + unsigned char *reserved_2, + long *reserved_3_length, + unsigned char *reserved_3, + long *reserved_4_length, + unsigned char *reserved_4, + long *reserved_5_length, + unsigned char *reserved_5, + long *output_KEK_key_identifier_length, + unsigned char *output_KEK_key_identifier, + long *output_key_identifier_length, + unsigned char *output_key_identifier); + +/* CCA Symmetric Key Import2 function */ +typedef void (*CSNDSYI2_t)(long *return_code, + long *reason_code, + long *exit_data_length, + unsigned char *exit_data, + long *rule_array_count, + unsigned char *rule_array, + long *enciphered_key_length, + unsigned char *enciphered_key, + long *transport_key_identifier_length, + unsigned char *transport_key_identifier, + long *key_name_length, + unsigned char *key_name, + long *target_key_identifier_length, + unsigned char *target_key_identifier); + struct cca_lib { CSNDPKB_t dll_CSNDPKB; CSNDPKG_t dll_CSNDPKG; CSNDKTC_t dll_CSNDKTC; CSNDDSG_t dll_CSNDDSG; + CSNBKTB2_t dll_CSNBKTB2; + CSNDEDH_t dll_CSNDEDH; + CSNDSYI2_t dll_CSNDSYI2; }; #define CCA_MAX_PKA_KEY_TOKEN_SIZE 3500 +#define CCA_MAX_SYM_KEY_TOKEN_SIZE 725 int cca_generate_ecc_key_pair(const struct ekmf_cca_lib *cca_lib, int curve_nid, unsigned char *key_token, @@ -109,10 +184,47 @@ int cca_get_ecc_pub_key_as_pkey(const unsigned char *key_token, size_t key_token_length, EVP_PKEY **pkey, bool verbose); +int cca_get_ecc_pub_key_as_json_web_key(const unsigned char *key_token, + size_t key_token_length, + json_object **jwk, bool verbose); + int cca_get_rsa_pub_key_as_pkey(const unsigned char *key_token, size_t key_token_length, int pkey_type, EVP_PKEY **pkey, bool verbose); +int cca_import_key_from_json_web_key(const struct ekmf_cca_lib *cca_lib, + json_object *jwk, unsigned char *key_token, + size_t *key_token_length, bool verbose); + +enum cca_kdf { + CCA_KDF_ANS_X9_63_CCA = 1, /* CCA DERIVE01 method */ + CCA_KDF_ANS_X9_63_SHA224 = 2, /* CCA DERIVE02 method with SHA-224 */ + CCA_KDF_ANS_X9_63_SHA256 = 3, /* CCA DERIVE02 method with SHA-256 */ + CCA_KDF_ANS_X9_63_SHA384 = 4, /* CCA DERIVE02 method with SHA-284 */ + CCA_KDF_ANS_X9_63_SHA512 = 5, /* CCA DERIVE02 method with SHA-512 */ +}; + +int cca_ec_dh_derive_importer(const struct ekmf_cca_lib *cca_lib, + const unsigned char *priv_ecc_key_token, + size_t priv_ecc_key_token_length, + const unsigned char *pub_ecc_key_token, + size_t pub_ecc_key_token_length, + const unsigned char *party_info, + size_t party_info_length, + enum cca_kdf kdf, + unsigned char *derived_key_token, + size_t *derived_key_token_length, + bool verbose); + +int cca_import_external_key(const struct ekmf_cca_lib *cca_lib, + const unsigned char *external_key_token, + size_t external_key_token_length, + const unsigned char *importer_key_token, + size_t importer_key_token_length, + unsigned char *imported_key_token, + size_t *imported_key_token_length, + bool verbose); + int cca_rsa_sign(const struct ekmf_cca_lib *cca_lib, const unsigned char *key_token, size_t key_token_length, unsigned char *sig, size_t *siglen, diff --git a/libekmfweb/ekmfweb.c b/libekmfweb/ekmfweb.c index 2528d42a..b714e653 100644 --- a/libekmfweb/ekmfweb.c +++ b/libekmfweb/ekmfweb.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #ifndef JSON_C_TO_STRING_NOSLASHESCAPE @@ -36,7 +37,13 @@ #define SERIAL_NUMBER_BIT_SIZE 159 +#define DEFAULT_SESSION_EC_KEY_CURVE NID_secp521r1 + #define MAX_KEY_BLOB_SIZE CCA_MAX_PKA_KEY_TOKEN_SIZE +#define MAX_SYM_KEY_BLOB_SIZE CCA_MAX_SYM_KEY_TOKEN_SIZE + +#define EKMF_URI_SYSTEM_PUBKEY "/api/v1/system/publicKey" +#define EKMF_URI_KEYS_EXPORT "/api/v1/keys/%s/export" #define pr_verbose(verbose, fmt...) do { \ if (verbose) \ @@ -52,6 +59,27 @@ } \ } while (0) +#define JSON_CHECK_OBJ(obj, type, rc_var, rc, text, verbose, label) \ + do { \ + if (obj == NULL || \ + !json_object_is_type(obj, type)) { \ + rc_var = rc; \ + pr_verbose(verbose, "%s: %s", text, \ + strerror(-rc_var)); \ + goto label; \ + } \ + } while (0) + +#define JSON_CHECK_ERROR(cond, rc_var, rc, text, verbose, label) \ + do { \ + if (cond) { \ + rc_var = rc; \ + pr_verbose(verbose, "%s: %s", text, \ + strerror(-rc_var)); \ + goto label; \ + } \ + } while (0) + struct curl_header_cb_data { struct curl_slist **headers; bool error; @@ -78,6 +106,20 @@ const char *accepted_content_types[] = { "application/json", "text/x-json", NULL}; +struct private_data { + const struct ekmf_ext_lib *ext_lib; + bool verbose; +}; + +static int _ekmf_setup_sign_context(const unsigned char *key_blob, + size_t key_blob_size, EVP_PKEY *pkey, + int digest_nid, + struct ekmf_rsa_pss_params *rsa_pss_params, + EVP_MD_CTX **md_ctx, + EVP_PKEY_CTX **pkey_ctx, + struct private_data *private, + bool verbose); + /** * Extract the public key from a certificate in PEM format and store it into a * PEM file @@ -955,6 +997,44 @@ out: return rc; } +/** + * Allocates or reuses a CURL handle. If curl_handle is not NULL, and + * points to a non-NULL CURL handle, it is used, otherwise a new CURL handle + * is allocated. + */ +static int _ekmf_get_curl_handle(CURL **curl_handle, CURL **curl) +{ + if (curl == NULL) + return -EINVAL; + + if (curl_handle != NULL) + *curl = *curl_handle; + + if (*curl == NULL) + *curl = curl_easy_init(); + + if (*curl == NULL) + return -EIO; + + return 0; +} + +/** + * Releases a CURL handle. If curl_handle is not NULL, then the used CURL + * handle is passed back via *curl_handle. If curl_handle is NULL, then the + * used CURL handle is destroyed. + */ +static void _ekmf_release_curl_handle(CURL **curl_handle, CURL *curl) +{ + if (curl == NULL) + return; + + if (curl_handle != NULL) + *curl_handle = curl; + else + curl_easy_cleanup(curl); +} + /** * Print the certificate(s) contained in the specified PEM file. * @@ -1137,6 +1217,907 @@ out: return rc; } +/** + * Request the EKMFWeb server's public signing key and store it into PEM file + * specified in field server_pubkey of the config structure. + * + * 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 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. + */ +int ekmf_get_public_key(const struct ekmf_config *config, CURL **curl_handle, + char **error_msg, bool verbose) +{ + json_object *response_obj = NULL; + char *login_token = NULL; + bool token_valid = false; + EVP_PKEY *pkey = NULL; + CURL *curl = NULL; + long status_code; + int rc; + + if (config == 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; + } + + rc = _ekmf_perform_request(config, EKMF_URI_SYSTEM_PUBKEY, "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 401: + pr_verbose(verbose, "Not authorized"); + rc = -EACCES; + 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); + + rc = json_web_key_as_pkey(response_obj, EVP_PKEY_RSA, &pkey); + if (rc != 0) { + pr_verbose(verbose, "Failed convert the JWK to PKEY"); + goto out; + } + + rc = write_public_key(config->ekmf_server_pubkey, pkey); + if (rc != 0) { + pr_verbose(verbose, "Failed to write public key '%s': %s", + config->ekmf_server_pubkey, strerror(-rc)); + goto out; + } + + pr_verbose(verbose, "EKMFWeb public key written to file '%s'", + config->ekmf_server_pubkey); + +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 (pkey != NULL) + EVP_PKEY_free(pkey); + + return rc; +} + +/** + * Build the party info JSON object as base64(sha256(key_uuid|timestamp)). + * Digest_nid specifies the digest to use, ot 0 to use the default (SHA256). + * The function returns the party info JSON object, as well as the raw party + * info. + */ +static int _ekmf_build_party_info(const char *key_uuid, const char *timestamp, + int digest_nid, unsigned char *party_info, + size_t *party_info_length, + json_object **party_info_obj, bool verbose) +{ + unsigned int digest_len; + EVP_MD_CTX *ctx = NULL; + const EVP_MD *md; + int rc; + + md = EVP_get_digestbynid(digest_nid != 0 ? digest_nid : NID_sha256); + if (md == NULL) { + pr_verbose(verbose, "Failed to get specified digest"); + rc = -EINVAL; + goto out; + } + + if (*party_info_length < (size_t)EVP_MD_size(md)) { + pr_verbose(verbose, "Party info buffer is too small"); + return -ERANGE; + goto out; + } + + ctx = EVP_MD_CTX_create(); + if (ctx == NULL) { + pr_verbose(verbose, "Failed to allocate MD context"); + rc = -ENOMEM; + goto out; + } + + rc = EVP_DigestInit_ex(ctx, md, NULL); + if (rc != 1) { + pr_verbose(verbose, "Failed to initialize MD context"); + rc = -EIO; + goto out; + } + + rc = EVP_DigestUpdate(ctx, key_uuid, strlen(key_uuid)); + if (rc != 1) { + pr_verbose(verbose, "Failed to add data to the MD context"); + rc = -EIO; + goto out; + } + + rc = EVP_DigestUpdate(ctx, timestamp, strlen(timestamp)); + if (rc != 1) { + pr_verbose(verbose, "Failed to add data to the MD context"); + rc = -EIO; + goto out; + } + + rc = EVP_DigestFinal_ex(ctx, party_info, &digest_len); + if (rc != 1) { + pr_verbose(verbose, "Failed to finalize the MD context"); + rc = -EIO; + goto out; + } + + *party_info_length = digest_len; + *party_info_obj = json_object_new_base64url(party_info, digest_len); + rc = 0; + +out: + if (ctx != NULL) + EVP_MD_CTX_destroy(ctx); + + return rc; +} + +/** + * Builds a (detached) JSON Web Signature using the secure identity key from + * the payload and returns a signature JSON object + */ +static int _ekmf_build_signature(unsigned char *key_blob, + size_t key_blob_length, + json_object *payload_obj, + json_object **signature_obj, + int digest_nid, bool use_rsa_pss, + const char *jws_kid, + const struct ekmf_ext_lib *ext_lib, + bool verbose) +{ + struct ekmf_rsa_pss_params rsa_pss_params; + EVP_PKEY_CTX *pkey_ctx = NULL; + struct private_data private; + EVP_MD_CTX *md_ctx = NULL; + bool pkey_meth = false; + EVP_PKEY *pkey = NULL; + const char *payload; + const char *jws_alg; + int rc, curve_nid; + char *jws = NULL; + int pkey_type; + BIO *b; + + switch (ext_lib->type) { + case EKMF_EXT_LIB_CCA: + rc = cca_get_key_type(key_blob, key_blob_length, &pkey_type); + if (rc != 0) { + pr_verbose(verbose, "Failed to get the identity key " + "type"); + goto out; + } + + switch (pkey_type) { + case EVP_PKEY_EC: + rc = cca_get_ecc_pub_key_as_pkey(key_blob, + key_blob_length, + &pkey, verbose); + break; + case EVP_PKEY_RSA: + case EVP_PKEY_RSA_PSS: + rc = cca_get_rsa_pub_key_as_pkey(key_blob, + key_blob_length, + use_rsa_pss ? + EVP_PKEY_RSA_PSS : + EVP_PKEY_RSA, + &pkey, verbose); + break; + } + + if (rc != 0) { + pr_verbose(verbose, "Failed to get the identity PKEY"); + goto out; + } + break; + default: + pr_verbose(verbose, "Invalid ext lib type: %d", ext_lib->type); + return -EINVAL; + } + + /* + * Only the following combinations are allowed per RFC7518 for JSON + * Web Signatures (JWS) using ECC or RSA identity keys: + * alg=ES256: ECDSA using P-256 and SHA-256 + * alg=ES384: ECDSA using P-384 and SHA-384 + * alg=ES512: ECDSA using P-521 and SHA-512 + * alg=RS256: RSA-PKCS1 using SHA-256 + * alg=RS384: RSA-PKCS1 using SHA-384 + * alg=RS512: RSA-PKCS1 using SHA-512 + * alg=PS256: RSA-PSS using SHA-256, MGF1 with SHA-256, salt=digest + * alg=PS384: RSA-PSS using SHA-384, MGF1 with SHA-384, salt=digest + * alg=PS512: RSA-PSS using SHA-512, MGF1 with SHA-512, salt=digest + */ + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_EC: + curve_nid = EC_GROUP_get_curve_name(EC_KEY_get0_group( + EVP_PKEY_get0_EC_KEY(pkey))); + switch (curve_nid) { + case NID_secp521r1: + digest_nid = NID_sha512; + jws_alg = "ES512"; + break; + case NID_secp384r1: + digest_nid = NID_sha384; + jws_alg = "ES384"; + break; + case NID_X9_62_prime256v1: + digest_nid = NID_sha256; + jws_alg = "ES256"; + break; + default: + pr_verbose(verbose, "Unsupported curve"); + rc = -EINVAL; + goto out; + } + break; + case EVP_PKEY_RSA: + switch (digest_nid) { + case NID_sha256: + jws_alg = "RS256"; + break; + case NID_sha384: + jws_alg = "RS384"; + break; + case NID_sha512: + case 0: + jws_alg = "RS512"; + digest_nid = NID_sha512; + break; + default: + pr_verbose(verbose, "Unsupported digest"); + rc = -EINVAL; + goto out; + } + break; + case EVP_PKEY_RSA_PSS: + switch (digest_nid) { + case NID_sha256: + jws_alg = "PS256"; + break; + case NID_sha384: + jws_alg = "PS384"; + break; + case NID_sha512: + case 0: + jws_alg = "PS512"; + digest_nid = NID_sha512; + break; + default: + pr_verbose(verbose, "Unsupported digest"); + rc = -EINVAL; + goto out; + } + rsa_pss_params.mgf_digest_nid = digest_nid; + rsa_pss_params.salt_len = RSA_PSS_SALTLEN_DIGEST; + break; + default: + pr_verbose(verbose, "Unsupported key type"); + rc = -EINVAL; + goto out; + } + + private.ext_lib = ext_lib; + private.verbose = verbose; + + rc = _ekmf_setup_sign_context(key_blob, key_blob_length, pkey, + digest_nid, &rsa_pss_params, &md_ctx, + &pkey_ctx, &private, verbose); + if (rc != 0) + goto out; + pkey_meth = true; + + payload = json_object_to_json_string_ext(payload_obj, + JSON_C_TO_STRING_PLAIN | + JSON_C_TO_STRING_NOSLASHESCAPE); + if (payload == NULL) { + pr_verbose(verbose, "Failed to get the payload string"); + rc = -EIO; + goto out; + } + + if (verbose) { + pr_verbose(verbose, "JWS Payload: ->%s<-", payload); + pr_verbose(verbose, "JWS alg: %s", jws_alg); + pr_verbose(verbose, "Public signing key:"); + b = BIO_new_fp(stderr, BIO_NOCLOSE); + PEM_write_bio_PUBKEY(b, pkey); + BIO_free(b); + } + + rc = create_json_web_signature(jws_alg, false, jws_kid, + (unsigned char *)payload, + strlen(payload), true, md_ctx, &jws); + if (rc != 0) { + pr_verbose(verbose, "Failed to build the JWS"); + goto out; + } + + *signature_obj = json_object_new_string(jws); + rc = 0; + +out: + if (md_ctx != NULL) + EVP_MD_CTX_free(md_ctx); + if (pkey_meth) + cleanup_secure_key_pkey_method(EVP_PKEY_id(pkey)); + if (pkey != NULL) + EVP_PKEY_free(pkey); + if (jws != NULL) + free(jws); + + return rc; +} + +/** + * Verifies the (detached) JSON Web Signature using the server's public signing + * key and the response payload. + * Note: This function removes the signature field from the response JSON + * object! + */ +static int _ekmf_verify_signature(json_object *response_obj, + EVP_PKEY *server_pubkey, bool verbose) +{ + json_object *signature_obj = NULL; + const char *sign_payload; + BIO *b; + int rc; + + if (response_obj == NULL) + return -EINVAL; + + json_object_object_get_ex(response_obj, "signature", + &signature_obj); + JSON_CHECK_OBJ(signature_obj, json_type_string, rc, -EIO, + "Failed to get the response signature", verbose, out); + + json_object_get(signature_obj); /* Take ownership */ + json_object_object_del(response_obj, "signature"); + + sign_payload = json_object_to_json_string_ext(response_obj, + JSON_C_TO_STRING_PLAIN | + JSON_C_TO_STRING_NOSLASHESCAPE); + if (sign_payload == NULL) { + rc = -ENOMEM; + goto out; + } + + if (verbose) { + pr_verbose(verbose, "JWS Payload: ->%s<-", sign_payload); + pr_verbose(verbose, "Public signing key:"); + b = BIO_new_fp(stderr, BIO_NOCLOSE); + PEM_write_bio_PUBKEY(b, server_pubkey); + BIO_free(b); + } + + rc = verify_json_web_signature(json_object_get_string(signature_obj), + (const unsigned char *)sign_payload, + strlen(sign_payload), server_pubkey); + if (rc != 0) { + pr_verbose(verbose, "Signature verify of response failed"); + goto out; + } + + pr_verbose(verbose, "Signature of response successfully verified"); + +out: + if (signature_obj != NULL) + json_object_put(signature_obj); + + return rc; +} + +/** + * Import the key retrieved from EKMFWeb. + */ +static int _ekmf_import_key(unsigned char *req_sess_key, + size_t req_sess_key_length, + unsigned char *req_party_info, + size_t req_party_info_length, + unsigned char *resp_party_info, + size_t resp_party_info_length, + json_object *resp_sess_jwk_obj, + json_object *resp_exp_jwk_obj, + unsigned char *key_blob, size_t *key_blob_length, + const struct ekmf_ext_lib *ext_lib, bool verbose) +{ + size_t resp_sess_ec_key_length, resp_exported_key_length; + unsigned char resp_exported_key[MAX_SYM_KEY_BLOB_SIZE]; + unsigned char transport_key[MAX_SYM_KEY_BLOB_SIZE]; + unsigned char resp_sess_key[MAX_KEY_BLOB_SIZE]; + size_t party_info_length, transport_key_length; + unsigned char *party_info = NULL; + int rc; + + party_info_length = req_party_info_length + resp_party_info_length; + party_info = malloc(party_info_length); + if (party_info == NULL) { + pr_verbose(verbose, "Failed to allocate memory"); + rc = -ENOMEM; + goto out; + } + + memcpy(party_info, req_party_info, req_party_info_length); + memcpy(party_info + req_party_info_length, resp_party_info, + resp_party_info_length); + + switch (ext_lib->type) { + case EKMF_EXT_LIB_CCA: + resp_sess_ec_key_length = sizeof(resp_sess_key); + rc = cca_import_key_from_json_web_key(ext_lib->cca, + resp_sess_jwk_obj, + resp_sess_key, + &resp_sess_ec_key_length, + verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to import the session EC " + "key"); + goto out; + } + + transport_key_length = sizeof(transport_key); + rc = cca_ec_dh_derive_importer(ext_lib->cca, + req_sess_key, + req_sess_key_length, + resp_sess_key, + resp_sess_ec_key_length, + party_info, party_info_length, + CCA_KDF_ANS_X9_63_CCA, + transport_key, + &transport_key_length, + verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to derive transport key"); + goto out; + } + + resp_exported_key_length = sizeof(resp_exported_key); + rc = cca_import_key_from_json_web_key(ext_lib->cca, + resp_exp_jwk_obj, + resp_exported_key, + &resp_exported_key_length, + verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to import the exported " + "key"); + goto out; + } + + rc = cca_import_external_key(ext_lib->cca, resp_exported_key, + resp_exported_key_length, + transport_key, + transport_key_length, + key_blob, key_blob_length, + verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to unwrap the exported " + "key with the transport key"); + goto out; + } + + break; + default: + pr_verbose(verbose, "Invalid ext lib type: %d", ext_lib->type); + return -EINVAL; + } + +out: + if (party_info != NULL) + free(party_info); + + return rc; +} + +/** + * Requests a key to be retrieved from EKMFweb and imported under the current + * HSM's master key. + * + * 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 retrieve + * @param sess_ec_curve_nid The OpenSSL nid of the EC curve used for the session + * ECC key. If 0, then the default curve is used. + * @param sign_rsa_digest_nid The OpenSSL nid of a digest used to sign the + * request with if the identity key is an RSA-type key. + * If 0, then the default digest is used. + * Ignored for ECC-type identity keys. + * @param use_rsa_pss If true, and the identity key is an RSA-type key, + * use RSA-PSS to sign the request. + * @param signature_kid the Key ID for the signature of the request + * @param key_blob a buffer to store the retrieved key blob to + * @param key_blob_length On entry: the size ofthe buffer + * On return: the size of the key blob retrieved + * @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 ext_lib External secure key crypto library to use + * @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 + * retrieve the key + */ +int ekmf_retrieve_key(const struct ekmf_config *config, CURL **curl_handle, + const char *key_uuid, int sess_ec_curve_nid, + int sign_rsa_digest_nid, bool use_rsa_pss, + const char *signature_kid, unsigned char *key_blob, + size_t *key_blob_length, char **error_msg, + const struct ekmf_ext_lib *ext_lib, bool verbose) +{ + size_t req_party_info_length, resp_party_info_length; + unsigned char req_party_info[SHA512_DIGEST_LENGTH]; + size_t req_sess_ec_key_length, identity_key_length; + unsigned char req_sess_ec_key[MAX_KEY_BLOB_SIZE]; + unsigned char identity_key[MAX_KEY_BLOB_SIZE]; + json_object *resp_originator_obj = NULL; + json_object *resp_addl_info_obj = NULL; + json_object *req_party_info_obj = NULL; + json_object *req_originator_obj = NULL; + json_object *req_timestamp_obj = NULL; + json_object *req_addl_info_obj = NULL; + json_object *req_signature_obj = NULL; + unsigned char *resp_party_info = NULL; + json_object *resp_sess_jwk_obj = NULL; + json_object *req_sess_jwk_obj = NULL; + json_object *resp_exp_jwk_obj = NULL; + json_object *response_obj = NULL; + json_object *request_obj = NULL; + EVP_PKEY *server_pubkey = NULL; + char *escaped_uuid = NULL; + char *login_token = NULL; + bool token_valid = false; + CURL *curl = NULL; + long status_code; + char *uri = NULL; + int rc; + + if (config == NULL || key_uuid == NULL || key_blob == NULL || + key_blob_length == NULL || ext_lib == 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; + } + + rc = read_public_key(config->ekmf_server_pubkey, &server_pubkey); + if (rc != 0) { + pr_verbose(verbose, "Failed to read EKMFWeb server's public key" + " '%s': %s", config->ekmf_server_pubkey, + strerror(-rc)); + goto out; + } + + identity_key_length = sizeof(identity_key); + rc = read_key_blob(config->identity_secure_key, identity_key, + &identity_key_length); + if (rc != 0) { + pr_verbose(verbose, "Failed to read identity key from file " + "'%s': %s", config->identity_secure_key, + strerror(-rc)); + goto out; + } + + switch (ext_lib->type) { + case EKMF_EXT_LIB_CCA: + req_sess_ec_key_length = sizeof(req_sess_ec_key); + rc = cca_generate_ecc_key_pair(ext_lib->cca, + sess_ec_curve_nid != 0 ? + sess_ec_curve_nid : + DEFAULT_SESSION_EC_KEY_CURVE, + req_sess_ec_key, + &req_sess_ec_key_length, + verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to generate a session EC " + "key"); + goto out; + } + + rc = cca_get_ecc_pub_key_as_json_web_key(req_sess_ec_key, + req_sess_ec_key_length, + &req_sess_jwk_obj, + verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to generate session JWK"); + goto out; + } + break; + default: + pr_verbose(verbose, "Invalid ext lib type: %d", ext_lib->type); + return -EINVAL; + } + + req_timestamp_obj = get_json_timestamp(); + JSON_CHECK_ERROR(req_timestamp_obj == NULL, rc, -EIO, + "Failed to generate timestamp", verbose, out); + + req_party_info_length = sizeof(req_party_info); + rc = _ekmf_build_party_info(key_uuid, + json_object_get_string(req_timestamp_obj), + NID_sha256, req_party_info, + &req_party_info_length, + &req_party_info_obj, verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to build the party info"); + goto out; + } + + /* + * Note: The order of the fields is important, EKMFWeb expects it in + * exactly this order! + */ + req_addl_info_obj = json_object_new_object(); + JSON_CHECK_ERROR(req_addl_info_obj == NULL, rc, -ENOMEM, + "Failed to generate JSON object", verbose, out); + + rc = json_object_object_add_ex(req_addl_info_obj, "kdf", + json_object_new_string("ANS-X9.63-CCA"), + 0); + JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object", + verbose, out); + rc = json_object_object_add_ex(req_addl_info_obj, "requestedKey", + json_object_new_string(key_uuid), 0); + JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object", + verbose, out); + rc = json_object_object_add_ex(req_addl_info_obj, "timestamp", + req_timestamp_obj, 0); + JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object", + verbose, out); + req_timestamp_obj = NULL; + + req_originator_obj = json_object_new_object(); + JSON_CHECK_ERROR(req_originator_obj == NULL, rc, -ENOMEM, + "Failed to generate JSON object", verbose, out); + + rc = json_object_object_add_ex(req_originator_obj, "session", + req_sess_jwk_obj, 0); + JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object", + verbose, out); + req_sess_jwk_obj = NULL; + rc = json_object_object_add_ex(req_originator_obj, "partyInfo", + req_party_info_obj, 0); + JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object", + verbose, out); + req_party_info_obj = NULL; + + 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, "originator", + req_originator_obj, 0); + JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object", + verbose, out); + req_originator_obj = NULL; + rc = json_object_object_add_ex(request_obj, "additionalInfo", + req_addl_info_obj, 0); + JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object", + verbose, out); + req_addl_info_obj = NULL; + + rc = _ekmf_build_signature(identity_key, identity_key_length, + request_obj, &req_signature_obj, + sign_rsa_digest_nid, use_rsa_pss, + signature_kid, ext_lib, verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to build the signature"); + goto out; + } + + rc = json_object_object_add_ex(request_obj, "signature", + req_signature_obj, 0); + JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object", + verbose, out); + req_signature_obj = NULL; + + 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, escaped_uuid) < 0) { + pr_verbose(verbose, "asprintf failed"); + rc = -ENOMEM; + goto out; + } + + rc = _ekmf_perform_request(config, uri, "POST", request_obj, 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); + + rc = _ekmf_verify_signature(response_obj, server_pubkey, verbose); + if (rc != 0) + goto out; + + json_object_object_get_ex(response_obj, "originator", + &resp_originator_obj); + JSON_CHECK_OBJ(resp_originator_obj, json_type_object, rc, -EBADMSG, + "Failed to get the response originator", verbose, out); + + json_object_object_get_ex(resp_originator_obj, "session", + &resp_sess_jwk_obj); + JSON_CHECK_OBJ(resp_sess_jwk_obj, json_type_object, rc, -EBADMSG, + "Failed to get the response session key", verbose, out); + + rc = json_object_get_base64url(resp_originator_obj, "partyInfo", + NULL, &resp_party_info_length); + if (rc != 0) { + pr_verbose(verbose, "Failed to get the response partyInfo"); + goto out; + } + + resp_party_info = malloc(resp_party_info_length); + if (resp_party_info == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = json_object_get_base64url(resp_originator_obj, "partyInfo", + resp_party_info, + &resp_party_info_length); + if (rc != 0) { + pr_verbose(verbose, "Failed to get the response partyInfo"); + goto out; + } + + json_object_object_get_ex(response_obj, "additionalInfo", + &resp_addl_info_obj); + JSON_CHECK_OBJ(resp_addl_info_obj, json_type_object, rc, -EBADMSG, + "Failed to get the response addl.info", verbose, out); + + json_object_object_get_ex(resp_addl_info_obj, "exportedKey", + &resp_exp_jwk_obj); + JSON_CHECK_OBJ(resp_exp_jwk_obj, json_type_object, rc, -EBADMSG, + "Failed to get the response exported key", verbose, out); + + rc = _ekmf_import_key(req_sess_ec_key, req_sess_ec_key_length, + req_party_info, req_party_info_length, + resp_party_info, resp_party_info_length, + resp_sess_jwk_obj, resp_exp_jwk_obj, + key_blob, key_blob_length, ext_lib, verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to import the retrieved key"); + goto out; + } + +out: + _ekmf_release_curl_handle(curl_handle, curl); + + if (req_sess_jwk_obj != NULL) + json_object_put(req_sess_jwk_obj); + if (req_timestamp_obj != NULL) + json_object_put(req_timestamp_obj); + if (req_addl_info_obj != NULL) + json_object_put(req_addl_info_obj); + if (req_party_info_obj != NULL) + json_object_put(req_party_info_obj); + if (req_originator_obj != NULL) + json_object_put(req_originator_obj); + if (req_signature_obj != NULL) + json_object_put(req_signature_obj); + if (request_obj != NULL) + json_object_put(request_obj); + if (response_obj != NULL) + json_object_put(response_obj); + if (uri != NULL) + free(uri); + if (login_token != NULL) + free(login_token); + if (server_pubkey != NULL) + EVP_PKEY_free(server_pubkey); + if (resp_party_info != NULL) + free(resp_party_info); + if (escaped_uuid != NULL) + curl_free(escaped_uuid); + + return rc; +} + /** * Generate a secure identity key used to identify the client to EKMFWeb. * The secure key blob is stored in a file specified in field @@ -1298,11 +2279,6 @@ int ekmf_reencipher_identity_key(const struct ekmf_config *config, return 0; } -struct private_data { - const struct ekmf_ext_lib *ext_lib; - bool verbose; -}; - /** * Wrapper for the RSA sign callback to route the call to the selected * secure key library. @@ -1974,6 +2950,19 @@ out: return rc; } +/** + * Close the connection to the EKMFWeb server by destroying the CURL handle. + * + * @param curl_handle the CURL handle to destroy + */ +void ekmf_curl_destroy(CURL *curl_handle) +{ + if (curl_handle == NULL) + return; + + curl_easy_cleanup(curl_handle); +} + /** * Library constructor */ diff --git a/libekmfweb/libekmfweb.map b/libekmfweb/libekmfweb.map index 7ba168eb..f00de30d 100644 --- a/libekmfweb/libekmfweb.map +++ b/libekmfweb/libekmfweb.map @@ -7,5 +7,8 @@ LIBEKMFWEB_1.0 { ekmf_reencipher_identity_key; ekmf_generate_csr; ekmf_generate_ss_cert; + ekmf_get_public_key; + ekmf_retrieve_key; + ekmf_curl_destroy; local: *; }; diff --git a/libekmfweb/utilities.c b/libekmfweb/utilities.c index dfa241e8..ce30a765 100644 --- a/libekmfweb/utilities.c +++ b/libekmfweb/utilities.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -20,8 +21,14 @@ #include #include +#include "lib/zt_common.h" + #include "utilities.h" +#ifndef JSON_C_TO_STRING_NOSLASHESCAPE +#define JSON_C_TO_STRING_NOSLASHESCAPE (1 << 4) +#endif + /** * Decodes a Base64URL encoded string. Base64URL is like Base64, but using a * URL and Filename Safe Alphabet, not using characters like '+', '/', or '='. @@ -364,6 +371,547 @@ out: return rc; } +/** + * Creates a JSON Web Signature object with the specified parts and returns a + * a character string containing the serialized JWS (see RFC 7515 for details) + * + * @param algorithm the JWS algorithm (e.g. ES512) (in the JWS header) + * @param b64 the b64 property of the JWS header. If b64 is true, + * then the payload (if any) is base64url encoded, + * if false, the payload (if any) is used as-is. + * @param kid the Key ID JWS header field (can be NULL) + * @param payload the JWS payload. + * @param payload_len the length of the payload in bytes + * @param detached_payload if true a JWS with detached payload is created (see + * RFC 7515 Appendix F) + * @param md_ctx An OpenSSL MD that has been set up with the desired + * digest and signing algorithm, options, and key + * @param jws On return: a C-string allocated by this function + * containing the serialized JWS. The caller must + * free the memory used by the returned string. + * + * @returns zero for success, a negative errno in case of an error + */ +int create_json_web_signature(const char *algorithm, bool b64, const char *kid, + const unsigned char *payload, size_t payload_len, + bool detached_payload, EVP_MD_CTX *md_ctx, + char **jws) +{ + unsigned char *signature = NULL; + json_object *header_obj = NULL; + json_object *crit_obj = NULL; + size_t signature_b64_len = 0; + size_t payload_b64_len = 0; + char *signature_b64 = NULL; + size_t header_b64_len = 0; + size_t signature_len = 0; + char *payload_b64 = NULL; + ECDSA_SIG *ec_sig = NULL; + char *header_b64 = NULL; + const unsigned char *p; + const char *header; + size_t prime_len; + EVP_PKEY *pkey; + int rc; + + if (algorithm == NULL || payload == NULL || md_ctx == NULL || + jws == NULL) + return -EINVAL; + + pkey = EVP_PKEY_CTX_get0_pkey(EVP_MD_CTX_pkey_ctx(md_ctx)); + if (pkey == NULL) { + rc = -EIO; + goto out; + } + + header_obj = json_object_new_object(); + if (header_obj == NULL) { + rc = -ENOMEM; + goto out; + } + + /* + * Note: The order of the fields is important, EKMFWeb expects it in + * exactly this order! + */ + rc = json_object_object_add_ex(header_obj, "alg", + json_object_new_string(algorithm), 0); + if (kid != NULL) + rc |= json_object_object_add_ex(header_obj, "kid", + json_object_new_string(kid), 0); + rc |= json_object_object_add_ex(header_obj, "b64", + json_object_new_boolean(b64), 0); + crit_obj = json_object_new_array(); + rc |= (crit_obj == NULL ? -1 : 0); + rc |= json_object_array_add(crit_obj, json_object_new_string("b64")); + rc |= json_object_object_add_ex(header_obj, "crit", crit_obj, 0); + crit_obj = NULL; + if (rc != 0) { + rc = -EIO; + goto out; + } + + header = json_object_to_json_string_ext(header_obj, + JSON_C_TO_STRING_PLAIN | + JSON_C_TO_STRING_NOSLASHESCAPE); + if (header == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = encode_base64url(NULL, &header_b64_len, (unsigned char *)header, + strlen(header)); + if (rc != 0) + goto out; + + header_b64 = malloc(header_b64_len); + if (header_b64 == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = encode_base64url(header_b64, &header_b64_len, + (unsigned char *)header, strlen(header)); + if (rc != 0) + goto out; + + if (b64) { + rc = encode_base64url(NULL, &payload_b64_len, payload, + payload_len); + if (rc != 0) + goto out; + + payload_b64 = malloc(payload_b64_len); + if (payload_b64 == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = encode_base64url(payload_b64, &payload_b64_len, payload, + payload_len); + if (rc != 0) + goto out; + } + + /* Sign: BASE64URL(UTF8(JWSHeader)) | '.' | [BASE64URL](JWS Payload) */ + rc = EVP_DigestSignUpdate(md_ctx, header_b64, strlen(header_b64)); + if (rc != 1) { + rc = -EIO; + goto out; + } + + rc = EVP_DigestSignUpdate(md_ctx, ".", 1); + if (rc != 1) { + rc = -EIO; + goto out; + } + + if (b64) + rc = EVP_DigestSignUpdate(md_ctx, payload_b64, + strlen(payload_b64)); + else + rc = EVP_DigestSignUpdate(md_ctx, payload, payload_len); + if (rc != 1) { + rc = -EIO; + goto out; + } + + signature_len = EVP_PKEY_size(pkey); + signature = malloc(signature_len); + if (signature == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = EVP_DigestSignFinal(md_ctx, signature, &signature_len); + if (rc != 1) { + rc = -EIO; + goto out; + } + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_EC: + prime_len = ecc_get_curve_prime_length(EC_GROUP_get_curve_name( + EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pkey)))); + + p = signature; + if (d2i_ECDSA_SIG(&ec_sig, &p, signature_len) == NULL) { + rc = -EIO; + goto out; + } + + if (signature_len < 2 * prime_len) { + rc = -EINVAL; + goto out; + } + + memset(signature, 0, signature_len); + BN_bn2binpad(ECDSA_SIG_get0_r(ec_sig), signature, prime_len); + BN_bn2binpad(ECDSA_SIG_get0_s(ec_sig), signature + prime_len, + prime_len); + signature_len = 2 * prime_len; + break; + + case EVP_PKEY_RSA: + case EVP_PKEY_RSA_PSS: + /* No signature encoding for RSA */ + break; + + default: + rc = -EINVAL; + goto out; + } + + rc = encode_base64url(NULL, &signature_b64_len, signature, + signature_len); + if (rc != 0) + goto out; + + signature_b64 = malloc(signature_b64_len); + if (signature_b64 == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = encode_base64url(signature_b64, &signature_b64_len, signature, + signature_len); + if (rc != 0) + goto out; + + if (detached_payload) { + if (asprintf(jws, "%s..%s", header_b64, signature_b64) < 0) { + rc = -ENOMEM; + goto out; + } + } else if (b64) { + if (asprintf(jws, "%s.%s.%s", header_b64, payload_b64, + signature_b64) < 0) { + rc = -ENOMEM; + goto out; + } + } else { + if (asprintf(jws, "%s.%.*s.%s", header_b64, (int)payload_len, + payload, signature_b64) < 0) { + rc = -ENOMEM; + goto out; + } + } + + rc = 0; + +out: + if (header_obj != NULL) + json_object_put(header_obj); + if (header_b64 != NULL) + free(header_b64); + if (payload_b64 != NULL) + free(payload_b64); + if (signature != NULL) + free(signature); + if (signature_b64 != NULL) + free(signature_b64); + if (ec_sig != NULL) + ECDSA_SIG_free(ec_sig); + + return rc; +} + +/** + * Verifies a JSON Web Signature object (see RFC 7515 for details). + * + * @param jws the JWS string + * @param payload if not NULL: the detached JWS payload. + * @param payload_len the length of the detached payload in bytes + * @param md_ctx An OpenSSL MD that has been set up with the desired + * digest and signing algorithm, options, and key + * + * @returns zero for success, a negative errno in case of an error + */ +int verify_json_web_signature(const char *jws, const unsigned char *payload, + size_t payload_len, EVP_PKEY *pkey) +{ + size_t header_len, hdr_pld_len, payload_b64_len, signature_len = 0; + unsigned char *signature = NULL, *der = NULL, *sig = NULL; + json_object *header_obj = NULL, *b64_obj = NULL; + int der_len, rc, curve_nid = 0, digest_nid = 0; + struct ekmf_rsa_pss_params rsa_pss_params; + bool b64 = true, rsa_pss = false; + EVP_MD_CTX *md_ctx = NULL; + EVP_PKEY_CTX *pctx = NULL; + ECDSA_SIG *ec_sig = NULL; + char *payload_b64 = NULL; + const EVP_MD *md = NULL; + BIGNUM *bn_r = NULL; + BIGNUM *bn_s = NULL; + const char *alg; + size_t sig_len; + char *ch; + + if (jws == NULL || pkey == NULL) + return -EINVAL; + + rc = parse_json_web_token(jws, &header_obj, NULL, &signature, + &signature_len); + if (rc != 0) + goto out; + + ch = strchr(jws, '.'); + if (ch == NULL) { + rc = -EBADMSG; + goto out; + } + header_len = ch - jws; + + ch = strchr(++ch, '.'); + if (ch == NULL) { + rc = -EBADMSG; + goto out; + } + hdr_pld_len = ch - jws; + + if (EVP_PKEY_id(pkey) == EVP_PKEY_EC) { + curve_nid = EC_GROUP_get_curve_name(EC_KEY_get0_group( + EVP_PKEY_get0_EC_KEY(pkey))); + } + + alg = json_get_string(header_obj, "alg"); + if (alg == NULL) { + rc = -EIO; + goto out; + } + + /* + * Only the following combinations are allowed per RFC7518 for JSON + * Web Signatures (JWS) using ECC or RSA signing keys: + * alg=ES256: ECDSA using P-256 and SHA-256 + * alg=ES384: ECDSA using P-384 and SHA-384 + * alg=ES512: ECDSA using P-521 and SHA-512 + * alg=RS256: RSA-PKCS1 using SHA-256 + * alg=RS384: RSA-PKCS1 using SHA-384 + * alg=RS512: RSA-PKCS1 using SHA-512 + * alg=PS256: RSA-PSS using SHA-256, MGF1 with SHA-256, salt=digest + * alg=PS384: RSA-PSS using SHA-384, MGF1 with SHA-384, salt=digest + * alg=PS512: RSA-PSS using SHA-512, MGF1 with SHA-512, salt=digest + */ + if (strncmp(alg, "ES", 2) == 0) { + if (EVP_PKEY_id(pkey) != EVP_PKEY_EC) { + rc = EINVAL; + goto out; + } + if ((strncmp(alg + 2, "512", 3) == 0 && + curve_nid != NID_secp521r1) || + (strncmp(alg + 2, "384", 3) == 0 && + curve_nid != NID_secp384r1) || + (strncmp(alg + 2, "256", 3) == 0 && + curve_nid != NID_X9_62_prime256v1)) { + rc = EINVAL; + goto out; + } + } else if (strncmp(alg, "RS", 2) == 0) { + if (EVP_PKEY_id(pkey) != EVP_PKEY_RSA) { + rc = EINVAL; + goto out; + } + } else if (strncmp(alg, "PS", 2) == 0) { + if (EVP_PKEY_id(pkey) != EVP_PKEY_RSA && + EVP_PKEY_id(pkey) != EVP_PKEY_RSA_PSS) { + rc = EINVAL; + goto out; + } + rsa_pss = true; + } else { + rc = -ENOTSUP; + goto out; + } + + if (strncmp(alg + 2, "512", 3) == 0) + digest_nid = NID_sha512; + else if (strncmp(alg + 2, "384", 3) == 0) + digest_nid = NID_sha384; + else if (strncmp(alg + 2, "256", 3) == 0) + digest_nid = NID_sha256; + if (digest_nid == 0) { + rc = -ENOTSUP; + goto out; + } + + md = EVP_get_digestbynid(digest_nid); + if (md == NULL) { + rc = -ENOTSUP; + goto out; + } + + md_ctx = EVP_MD_CTX_new(); + if (md_ctx == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = EVP_DigestVerifyInit(md_ctx, &pctx, md, NULL, pkey); + if (rc != 1) { + rc = -EIO; + goto out; + } + + if (rsa_pss) { + rsa_pss_params.mgf_digest_nid = digest_nid; + rsa_pss_params.salt_len = RSA_PSS_SALTLEN_DIGEST; + rc = setup_rsa_pss_pkey_context(pctx, &rsa_pss_params); + if (rc != 0) + goto out; + } + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_EC: + ec_sig = ECDSA_SIG_new(); + if (ec_sig == NULL) { + rc = -ENOMEM; + goto out; + } + + bn_r = BN_bin2bn(signature, signature_len / 2, NULL); + bn_s = BN_bin2bn(signature + signature_len / 2, + signature_len / 2, NULL); + if (bn_r == NULL || bn_s == NULL) { + rc = -EIO; + goto out; + } + + if (ECDSA_SIG_set0(ec_sig, bn_r, bn_s) != 1) { + rc = -EIO; + goto out; + } + bn_r = NULL; + bn_s = NULL; + + der_len = i2d_ECDSA_SIG(ec_sig, &der); + if (der_len <= 0) { + rc = -EIO; + goto out; + } + + sig = der; + sig_len = der_len; + break; + + case EVP_PKEY_RSA: + case EVP_PKEY_RSA_PSS: + /* No signature encoding for RSA */ + sig = signature; + sig_len = signature_len; + break; + + default: + rc = -EINVAL; + goto out; + } + + if (payload != NULL && payload_len > 0) { + /* Detached payload */ + if (json_object_object_get_ex(header_obj, "b64", &b64_obj) && + json_object_is_type(b64_obj, json_type_boolean)) + b64 = json_object_get_boolean(b64_obj); + + if (b64) { + rc = encode_base64url(NULL, &payload_b64_len, payload, + payload_len); + if (rc != 0) + goto out; + + payload_b64 = malloc(payload_b64_len); + if (payload_b64 == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = encode_base64url(payload_b64, &payload_b64_len, + payload, payload_len); + if (rc != 0) + goto out; + } + + /* Take header plus '.' as is */ + rc = EVP_DigestVerifyUpdate(md_ctx, jws, header_len + 1); + if (rc != 1) { + rc = -EIO; + goto out; + } + + if (b64) + rc = EVP_DigestVerifyUpdate(md_ctx, payload_b64, + payload_b64_len); + else + rc = EVP_DigestVerifyUpdate(md_ctx, payload, + payload_len); + if (rc != 1) { + rc = -EIO; + goto out; + } + } else { + /* Take header plus '.' plus payload as is */ + rc = EVP_DigestVerifyUpdate(md_ctx, jws, hdr_pld_len); + if (rc != 1) { + rc = -EIO; + goto out; + } + } + + rc = EVP_DigestVerifyFinal(md_ctx, sig, sig_len); + if (rc != 1) { + rc = -EIO; + goto out; + } + + rc = 0; + +out: + if (header_obj != NULL) + json_object_put(header_obj); + if (signature != NULL) + free(signature); + if (payload_b64 != NULL) + free(payload_b64); + if (ec_sig != NULL) + ECDSA_SIG_free(ec_sig); + if (der != NULL) + OPENSSL_free(der); + if (bn_r != NULL) + BN_free(bn_r); + if (bn_s != NULL) + BN_free(bn_s); + if (md_ctx != NULL) + EVP_MD_CTX_free(md_ctx); + + return rc; +} + +/** + * Builds a JSON Object containing a timestamp value in ISO 8601 format, e.g. + * { "timestamp": "2020-04-27T10:02:18.123Z" }. The time is expressed in UTC, + * regardless of the local time zone. + * + * @returns a JSON object containing the timestamp, or NULL in case of an error. + */ +json_object *get_json_timestamp(void) +{ + char timestamp[100]; + struct timeval tv; + struct tm *tm; + char temp[20]; + + if (gettimeofday(&tv, NULL) != 0) + return NULL; + + tm = gmtime(&tv.tv_sec); + if (strftime(timestamp, sizeof(timestamp), "%FT%T", tm) == 0) + return NULL; + + snprintf(temp, sizeof(temp), ".%06ldZ", tv.tv_usec); + strcat(timestamp, temp); + + return json_object_new_string(timestamp); +} + struct ecc_curve_info { int curve_nid; enum { @@ -789,6 +1337,125 @@ out: return rc; } +/** + * Converts a JSON Web Key (ECC or RSA) into a OpenSSL PKEY + * + * @param jwk The JSON Web Key to convert + * @param pkey_type If the JWK contains an RSA key, then the pkey_type + * can be EVP_PKEY_RSA or EVP_PKEY_RSA_PSS + * @param pkey On return: the OpenSSL PKEY + * + * @returns zero for success, a negative errno in case of an error + */ +int json_web_key_as_pkey(json_object *jwk, int pkey_type, EVP_PKEY **pkey) +{ + unsigned char *x = NULL, *y = NULL, *n = NULL, *e = NULL; + size_t prime_len, len, n_len, e_len; + const char *kty, *crv; + int nid, rc = 0; + + if (jwk == NULL || pkey == NULL) + return -EINVAL; + + *pkey = NULL; + + kty = json_get_string(jwk, "kty"); + if (kty == NULL) { + rc = -EIO; + goto out; + } + + if (strcmp(kty, "EC") == 0) { + crv = json_get_string(jwk, "crv"); + if (crv == NULL) { + rc = -EIO; + goto out; + } + + nid = ecc_get_curve_by_id(crv); + if (nid == 0) { + rc = -EIO; + goto out; + } + + prime_len = ecc_get_curve_prime_length(nid); + if (prime_len == 0) { + rc = -EIO; + goto out; + } + + x = malloc(prime_len); + y = malloc(prime_len); + if (x == NULL || y == NULL) { + rc = -ENOMEM; + goto out; + } + + len = prime_len; + rc = json_object_get_base64url(jwk, "x", x, &len); + if (rc != 0) + goto out; + + len = prime_len; + rc = json_object_get_base64url(jwk, "y", y, &len); + if (rc != 0) + goto out; + + rc = ecc_pub_key_as_pkey(nid, prime_len, x, y, pkey); + if (rc != 0) + goto out; + } else if (strcmp(kty, "RSA") == 0) { + n_len = 0; + rc = json_object_get_base64url(jwk, "n", NULL, &n_len); + if (rc != 0) + goto out; + + n = malloc(n_len); + if (n == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = json_object_get_base64url(jwk, "n", n, &n_len); + if (rc != 0) + goto out; + + e_len = 0; + rc = json_object_get_base64url(jwk, "e", NULL, &e_len); + if (rc != 0) + goto out; + + e = malloc(e_len); + if (e == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = json_object_get_base64url(jwk, "e", e, &e_len); + if (rc != 0) + goto out; + + rc = rsa_pub_key_as_pkey(n, n_len, e, e_len, pkey_type, pkey); + if (rc != 0) + goto out; + + } else { + return -EIO; + } + +out: + if (x != NULL) + free(x); + if (y != NULL) + free(y); + if (n != NULL) + free(n); + if (e != NULL) + free(e); + return rc; +} + + /** * Write a secure key blob to the specified file. * @@ -982,6 +1649,71 @@ int write_x509_request(const char *pem_filename, X509_REQ *req, bool new_hdr) return 0; } +/** + * Reads a public key from the specified PEM file. + * + * @param pem_filename the name of the PEM file to read + * @param pkey on Return: the PKEY object + * + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: invalid parameter + * -EIO: error during reading in the certificate + * any other errno as returned by fopen + */ +int read_public_key(const char *pem_filename, EVP_PKEY **pkey) +{ + FILE *fp; + + if (pem_filename == NULL || pkey == NULL) + return -EINVAL; + + fp = fopen(pem_filename, "r"); + if (fp == NULL) + return -errno; + + *pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL); + + fclose(fp); + + if (*pkey == NULL) + return -EIO; + + return 0; +} + +/** + * Writes apublic key to the specified PEM file. + * + * @param pem_filename the name of the PEM file to write to + * @param pkey the PKEY object to write + * + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: invalid parameter + * -EIO: error during writing out the certificate + * any other errno as returned by fopen + */ +int write_public_key(const char *pem_filename, EVP_PKEY *pkey) +{ + FILE *fp; + int rc; + + if (pem_filename == NULL || pkey == NULL) + return -EINVAL; + + fp = fopen(pem_filename, "w"); + if (fp == NULL) + return -errno; + + rc = PEM_write_PUBKEY(fp, pkey); + + fclose(fp); + + if (rc != 1) + return -EIO; + + return 0; +} + /* Secure key PKEY context control */ #define EVP_PKEY_CTRL_SK_KEY_BLOB 0x10000001 #define EVP_PKEY_CTRL_SK_SIGN_FUNCTIONS 0x10000002 @@ -1835,3 +2567,103 @@ out: return rc; } +/** + * Gets a String field from a JSON object. + * + * @param obj the JSON object + * @param name the name of the String field to get + * + * @returns the contents of the String field or NULL. + * Note: The memory returned is owned by the JSON object, and must not be freed + * by the caller. It is valid until the JSON object is freed, which also + * frees the memory used for the string value. + */ +const char *json_get_string(json_object *obj, const char *name) +{ + json_object *field; + + if (!json_object_object_get_ex(obj, name, &field) || + !json_object_is_type(field, json_type_string)) + return NULL; + + return json_object_get_string(field); +} + +/** + * Gets a base64url field form a JSON object, decodes it and returns the + * decoded data. + * + * @param obj the JSON object + * @param name the name of the String field to get + * @param data buffer to return the decoded data, or NULL to + * return only the required buffer size + * @param data_len on entry: the size of tne buffer + * on exit: the size of the decoded data + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: a function parameter is invalid + * -ENOMEM: failed to allocate memory + * -EIO: OpenSSL failed to calculate the y coordinate + */ +int json_object_get_base64url(json_object *obj, const char *name, + unsigned char *data, size_t *data_len) +{ + const char *b64; + + b64 = json_get_string(obj, name); + if (b64 == NULL) + return -ENOENT; + + return decode_base64url(data, data_len, b64, strlen(b64)); +} + +/** + * Base64URL encodes the data and creates a JSON string object of it + * + * @param data the data to base64url encode + * @param len the length of the data + + * @returns a new JSON object, or NULL in case of an error + */ +json_object *json_object_new_base64url(const unsigned char *data, size_t len) +{ + json_object *ret = NULL; + char *b64 = NULL; + size_t b64len; + int rc; + + rc = encode_base64url(NULL, &b64len, data, len); + if (rc != 0) + goto out; + + b64 = malloc(b64len); + if (b64 == NULL) + goto out; + + rc = encode_base64url(b64, &b64len, data, len); + if (rc != 0) + goto out; + + ret = json_object_new_string(b64); + +out: + if (b64 != NULL) + free(b64); + + return ret; +} + +#ifdef IMPLEMENT_LOCAL_JSON_OBJECT_OBJECT_ADD + +/** + * JSON-C of version 0.12 does not have json_object_object_add_ex(), and + * json_object_object_add does not return a return code, so implement + * json_object_object_add_ex here instead. + */ +int json_object_object_add_ex(struct json_object *obj, const char *const key, + struct json_object *const val, + const unsigned int UNUSED(opts)) +{ + json_object_object_add(obj, key, val); + return 0; +} +#endif diff --git a/libekmfweb/utilities.h b/libekmfweb/utilities.h index 3c01bc97..dfd8111a 100644 --- a/libekmfweb/utilities.h +++ b/libekmfweb/utilities.h @@ -31,6 +31,16 @@ int parse_json_web_token(const char *token, json_object **header_obj, json_object **payload_obj, unsigned char **signature, size_t *signature_len); +int create_json_web_signature(const char *algorithm, bool b64, const char *kid, + const unsigned char *payload, size_t payload_len, + bool detached_payload, EVP_MD_CTX *md_ctx, + char **jws); + +int verify_json_web_signature(const char *jws, const unsigned char *payload, + size_t payload_len, EVP_PKEY *pkey); + +json_object *get_json_timestamp(void); + 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); @@ -51,6 +61,8 @@ int rsa_pub_key_as_pkey(const unsigned char *modulus, size_t modulus_length, const unsigned char *pub_exp, size_t pub_exp_length, int pkey_type, EVP_PKEY **pkey); +int json_web_key_as_pkey(json_object *jwk, int pkey_type, EVP_PKEY **pkey); + int write_key_blob(const char *filename, unsigned char *key_blob, size_t key_blob_len); @@ -63,6 +75,10 @@ int write_x509_certificate(const char *pem_filename, X509 *cert); int write_x509_request(const char *pem_filename, X509_REQ *req, bool new_hdr); +int read_public_key(const char *pem_filename, EVP_PKEY **pkey); + +int write_public_key(const char *pem_filename, EVP_PKEY *pkey); + typedef int (*rsa_sign_t)(const unsigned char *key_blob, size_t key_blob_length, unsigned char *sig, size_t *siglen, const unsigned char *tbs, size_t tbslen, @@ -104,4 +120,20 @@ int build_certificate_extensions(X509 *cert, X509_REQ *req, int generate_x509_serial_number(X509 *cert, size_t sn_bit_size); +const char *json_get_string(json_object *obj, const char *name); + +int json_object_get_base64url(json_object *obj, const char *name, + unsigned char *data, size_t *data_len); + +json_object *json_object_new_base64url(const unsigned char *data, size_t len); + +#ifndef JSON_C_OBJECT_ADD_KEY_IS_NEW +#define JSON_C_OBJECT_ADD_KEY_IS_NEW (1 << 1) +#define IMPLEMENT_LOCAL_JSON_OBJECT_OBJECT_ADD + +int json_object_object_add_ex(struct json_object *obj, const char *const key, + struct json_object *const val, + const unsigned int opts); +#endif + #endif