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