From 65e94351dc4a87cdd90edba66458d9262540f185 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Sat, 9 May 2020 16:30:09 +0300 Subject: [PATCH] DPP2: Reconfig Authentication Request processing and Response generation Extend Enrollee functionality to process Reconfig Authentication Request message, derive the needed keys, and generate Reconfig Authentication Response message. Signed-off-by: Jouni Malinen --- src/common/dpp.c | 9 +- src/common/dpp.h | 10 ++ src/common/dpp_crypto.c | 108 ++++++++++++++ src/common/dpp_i.h | 7 + src/common/dpp_reconfig.c | 254 ++++++++++++++++++++++++++++++++ wpa_supplicant/dpp_supplicant.c | 50 +++++++ 6 files changed, 435 insertions(+), 3 deletions(-) diff --git a/src/common/dpp.c b/src/common/dpp.c index c7e4421eb..37f248468 100644 --- a/src/common/dpp.c +++ b/src/common/dpp.c @@ -108,7 +108,7 @@ struct dpp_controller { }; -static void dpp_auth_fail(struct dpp_authentication *auth, const char *txt) +void dpp_auth_fail(struct dpp_authentication *auth, const char *txt) { wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_FAIL "%s", txt); } @@ -1002,6 +1002,7 @@ int dpp_prepare_channel_list(struct dpp_authentication *auth, return -1; auth->num_freq = 1; auth->freq[0] = neg_freq; + auth->curr_freq = neg_freq; return 0; } @@ -3255,10 +3256,12 @@ void dpp_auth_deinit(struct dpp_authentication *auth) dpp_configuration_free(auth->conf2_sta); EVP_PKEY_free(auth->own_protocol_key); EVP_PKEY_free(auth->peer_protocol_key); + EVP_PKEY_free(auth->reconfig_old_protocol_key); wpabuf_free(auth->req_msg); wpabuf_free(auth->resp_msg); wpabuf_free(auth->conf_req); wpabuf_free(auth->reconfig_req_msg); + wpabuf_free(auth->reconfig_resp_msg); for (i = 0; i < auth->num_conf_obj; i++) { struct dpp_config_obj *conf = &auth->conf_obj[i]; @@ -4477,8 +4480,8 @@ static int dpp_parse_cred_legacy(struct dpp_config_obj *conf, } -static EVP_PKEY * dpp_parse_jwk(struct json_token *jwk, - const struct dpp_curve_params **key_curve) +EVP_PKEY * dpp_parse_jwk(struct json_token *jwk, + const struct dpp_curve_params **key_curve) { struct json_token *token; const struct dpp_curve_params *curve; diff --git a/src/common/dpp.h b/src/common/dpp.h index 7e3952d14..a2c0f185c 100644 --- a/src/common/dpp.h +++ b/src/common/dpp.h @@ -20,6 +20,7 @@ struct crypto_ecdh; struct hostapd_ip_addr; struct dpp_global; +struct json_token; #ifdef CONFIG_TESTING_OPTIONS #define DPP_VERSION (dpp_version_override) @@ -246,9 +247,11 @@ struct dpp_authentication { u8 r_capab; EVP_PKEY *own_protocol_key; EVP_PKEY *peer_protocol_key; + EVP_PKEY *reconfig_old_protocol_key; struct wpabuf *req_msg; struct wpabuf *resp_msg; struct wpabuf *reconfig_req_msg; + struct wpabuf *reconfig_resp_msg; /* Intersection of possible frequencies for initiating DPP * Authentication exchange */ unsigned int freq[DPP_BOOTSTRAP_MAX_FREQ]; @@ -641,6 +644,13 @@ struct wpabuf * dpp_build_reconfig_announcement(const u8 *csign_key, struct dpp_authentication * dpp_reconfig_init(struct dpp_global *dpp, void *msg_ctx, struct dpp_configurator *conf, unsigned int freq); +struct dpp_authentication * +dpp_reconfig_auth_req_rx(struct dpp_global *dpp, void *msg_ctx, + const char *own_connector, + const u8 *net_access_key, size_t net_access_key_len, + const u8 *csign_key, size_t csign_key_len, + unsigned int freq, const u8 *hdr, + const u8 *attr_start, size_t attr_len); #endif /* CONFIG_DPP */ #endif /* DPP_H */ diff --git a/src/common/dpp_crypto.c b/src/common/dpp_crypto.c index 224d2871f..048f52752 100644 --- a/src/common/dpp_crypto.c +++ b/src/common/dpp_crypto.c @@ -2236,6 +2236,114 @@ int dpp_pkex_derive_z(const u8 *mac_init, const u8 *mac_resp, } +int dpp_reconfig_derive_ke_responder(struct dpp_authentication *auth, + const u8 *net_access_key, + size_t net_access_key_len, + struct json_token *peer_net_access_key) +{ + BN_CTX *bnctx = NULL; + EVP_PKEY *own_key = NULL, *peer_key = NULL; + BIGNUM *sum = NULL, *q = NULL, *mx = NULL; + EC_POINT *m = NULL; + const EC_KEY *cR, *pR; + const EC_GROUP *group; + const BIGNUM *cR_bn, *pR_bn; + const EC_POINT *CI_point; + const EC_KEY *CI; + u8 Mx[DPP_MAX_SHARED_SECRET_LEN]; + u8 prk[DPP_MAX_HASH_LEN]; + const struct dpp_curve_params *curve; + int res = -1; + + own_key = dpp_set_keypair(&auth->curve, net_access_key, + net_access_key_len); + if (!own_key) { + dpp_auth_fail(auth, "Failed to parse own netAccessKey"); + goto fail; + } + + peer_key = dpp_parse_jwk(peer_net_access_key, &curve); + if (!peer_key) + goto fail; + dpp_debug_print_key("DPP: Received netAccessKey", peer_key); + + if (auth->curve != curve) { + wpa_printf(MSG_DEBUG, + "DPP: Mismatching netAccessKey curves (%s != %s)", + auth->curve->name, curve->name); + goto fail; + } + + auth->own_protocol_key = dpp_gen_keypair(curve); + if (!auth->own_protocol_key) + goto fail; + + /* M = { cR + pR } * CI */ + cR = EVP_PKEY_get0_EC_KEY(own_key); + pR = EVP_PKEY_get0_EC_KEY(auth->own_protocol_key); + group = EC_KEY_get0_group(pR); + bnctx = BN_CTX_new(); + sum = BN_new(); + mx = BN_new(); + q = BN_new(); + m = EC_POINT_new(group); + if (!cR || !pR || !bnctx || !sum || !mx || !q || !m) + goto fail; + cR_bn = EC_KEY_get0_private_key(cR); + pR_bn = EC_KEY_get0_private_key(pR); + if (!cR_bn || !pR_bn) + goto fail; + CI = EVP_PKEY_get0_EC_KEY(peer_key); + CI_point = EC_KEY_get0_public_key(CI); + if (EC_GROUP_get_order(group, q, bnctx) != 1 || + BN_mod_add(sum, cR_bn, pR_bn, q, bnctx) != 1 || + EC_POINT_mul(group, m, NULL, CI_point, sum, bnctx) != 1 || + EC_POINT_get_affine_coordinates_GFp(group, m, mx, NULL, + bnctx) != 1) { + wpa_printf(MSG_ERROR, + "OpenSSL: failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + if (dpp_bn2bin_pad(mx, Mx, curve->prime_len) < 0) + goto fail; + wpa_hexdump_key(MSG_DEBUG, "DPP: M.x", Mx, curve->prime_len); + + /* ke = HKDF(I-nonce, "dpp reconfig key", M.x) */ + + /* HKDF-Extract(I-nonce, M.x) */ + if (dpp_hmac(curve->hash_len, auth->i_nonce, curve->nonce_len, + Mx, curve->prime_len, prk) < 0) + goto fail; + wpa_hexdump_key(MSG_DEBUG, "DPP: PRK", prk, curve->hash_len); + + /* HKDF-Expand(PRK, "dpp reconfig key", L) */ + if (dpp_hkdf_expand(curve->hash_len, prk, curve->hash_len, + "dpp reconfig key", auth->ke, curve->hash_len) < 0) + goto fail; + wpa_hexdump_key(MSG_DEBUG, + "DPP: ke = HKDF(I-nonce, \"dpp reconfig key\", M.x)", + auth->ke, curve->hash_len); + + res = 0; + EVP_PKEY_free(auth->reconfig_old_protocol_key); + auth->reconfig_old_protocol_key = own_key; + own_key = NULL; +fail: + forced_memzero(prk, sizeof(prk)); + forced_memzero(Mx, sizeof(Mx)); + EC_POINT_clear_free(m); + BN_free(q); + BN_clear_free(mx); + BN_clear_free(sum); + EVP_PKEY_free(own_key); + EVP_PKEY_free(peer_key); + BN_CTX_free(bnctx); + return res; +} + + static char * dpp_build_jws_prot_hdr(struct dpp_configurator *conf, size_t *signed1_len) { diff --git a/src/common/dpp_i.h b/src/common/dpp_i.h index 300bf5ce9..c6e9ae471 100644 --- a/src/common/dpp_i.h +++ b/src/common/dpp_i.h @@ -38,9 +38,12 @@ int dpp_connector_match_groups(struct json_token *own_root, struct json_token *peer_root, bool reconfig); int dpp_build_jwk(struct wpabuf *buf, const char *name, EVP_PKEY *key, const char *kid, const struct dpp_curve_params *curve); +EVP_PKEY * dpp_parse_jwk(struct json_token *jwk, + const struct dpp_curve_params **key_curve); int dpp_prepare_channel_list(struct dpp_authentication *auth, unsigned int neg_freq, struct hostapd_hw_modes *own_modes, u16 num_modes); +void dpp_auth_fail(struct dpp_authentication *auth, const char *txt); /* dpp_crypto.c */ @@ -116,6 +119,10 @@ int dpp_pkex_derive_z(const u8 *mac_init, const u8 *mac_resp, const char *code, const u8 *Kx, size_t Kx_len, u8 *z, unsigned int hash_len); +int dpp_reconfig_derive_ke_responder(struct dpp_authentication *auth, + const u8 *net_access_key, + size_t net_access_key_len, + struct json_token *peer_net_access_key); char * dpp_sign_connector(struct dpp_configurator *conf, const struct wpabuf *dppcon); int dpp_test_gen_invalid_key(struct wpabuf *msg, diff --git a/src/common/dpp_reconfig.c b/src/common/dpp_reconfig.c index 5a11b1e8b..ba3d6eace 100644 --- a/src/common/dpp_reconfig.c +++ b/src/common/dpp_reconfig.c @@ -7,11 +7,15 @@ */ #include "utils/includes.h" +#include +#include #include "utils/common.h" #include "utils/json.h" #include "crypto/crypto.h" #include "crypto/random.h" +#include "crypto/aes.h" +#include "crypto/aes_siv.h" #include "dpp.h" #include "dpp_i.h" @@ -207,4 +211,254 @@ fail: goto out; } + +static int dpp_reconfig_build_resp(struct dpp_authentication *auth, + const char *own_connector, + struct wpabuf *conn_status) +{ + struct wpabuf *msg = NULL, *clear, *pr = NULL; + u8 *attr_start, *attr_end; + size_t clear_len, attr_len, len[2]; + const u8 *addr[2]; + u8 *wrapped; + int res = -1; + + /* Build DPP Reconfig Authentication Response frame attributes */ + clear_len = 2 * (4 + auth->curve->nonce_len) + + 4 + wpabuf_len(conn_status); + clear = wpabuf_alloc(clear_len); + if (!clear) + goto fail; + + /* I-nonce (wrapped) */ + wpabuf_put_le16(clear, DPP_ATTR_I_NONCE); + wpabuf_put_le16(clear, auth->curve->nonce_len); + wpabuf_put_data(clear, auth->i_nonce, auth->curve->nonce_len); + + /* R-nonce (wrapped) */ + wpabuf_put_le16(clear, DPP_ATTR_R_NONCE); + wpabuf_put_le16(clear, auth->curve->nonce_len); + wpabuf_put_data(clear, auth->r_nonce, auth->curve->nonce_len); + + /* Connection Status (wrapped) */ + wpabuf_put_le16(clear, DPP_ATTR_CONN_STATUS); + wpabuf_put_le16(clear, wpabuf_len(conn_status)); + wpabuf_put_buf(clear, conn_status); + + pr = dpp_get_pubkey_point(auth->own_protocol_key, 0); + if (!pr) + goto fail; + + attr_len = 4 + 1 + 4 + 1 + + 4 + os_strlen(own_connector) + + 4 + wpabuf_len(pr) + + 4 + wpabuf_len(clear) + AES_BLOCK_SIZE; + msg = dpp_alloc_msg(DPP_PA_RECONFIG_AUTH_RESP, attr_len); + if (!msg) + goto fail; + + attr_start = wpabuf_put(msg, 0); + + /* Transaction ID */ + wpabuf_put_le16(msg, DPP_ATTR_TRANSACTION_ID); + wpabuf_put_le16(msg, 1); + wpabuf_put_u8(msg, auth->transaction_id); + + /* Protocol Version */ + wpabuf_put_le16(msg, DPP_ATTR_PROTOCOL_VERSION); + wpabuf_put_le16(msg, 1); + wpabuf_put_u8(msg, DPP_VERSION); + + /* R-Connector */ + wpabuf_put_le16(msg, DPP_ATTR_CONNECTOR); + wpabuf_put_le16(msg, os_strlen(own_connector)); + wpabuf_put_str(msg, own_connector); + + /* Responder Protocol Key (Pr) */ + wpabuf_put_le16(msg, DPP_ATTR_R_PROTOCOL_KEY); + wpabuf_put_le16(msg, wpabuf_len(pr)); + wpabuf_put_buf(msg, pr); + + attr_end = wpabuf_put(msg, 0); + + /* OUI, OUI type, Crypto Suite, DPP frame type */ + addr[0] = wpabuf_head_u8(msg) + 2; + len[0] = 3 + 1 + 1 + 1; + wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]); + + /* Attributes before Wrapped Data */ + addr[1] = attr_start; + len[1] = attr_end - attr_start; + wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]); + + /* Wrapped Data: {I-nonce, R-nonce, Connection Status}ke */ + wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA); + wpabuf_put_le16(msg, wpabuf_len(clear) + AES_BLOCK_SIZE); + wrapped = wpabuf_put(msg, wpabuf_len(clear) + AES_BLOCK_SIZE); + + wpa_hexdump_buf(MSG_DEBUG, "DPP: AES-SIV cleartext", clear); + if (aes_siv_encrypt(auth->ke, auth->curve->hash_len, + wpabuf_head(clear), wpabuf_len(clear), + 2, addr, len, wrapped) < 0) + goto fail; + + wpa_hexdump_buf(MSG_DEBUG, + "DPP: Reconfig Authentication Response frame attributes", + msg); + + wpabuf_free(auth->reconfig_resp_msg); + auth->reconfig_resp_msg = msg; + + res = 0; +out: + wpabuf_free(clear); + wpabuf_free(pr); + return res; +fail: + wpabuf_free(msg); + goto out; +} + + +struct dpp_authentication * +dpp_reconfig_auth_req_rx(struct dpp_global *dpp, void *msg_ctx, + const char *own_connector, + const u8 *net_access_key, size_t net_access_key_len, + const u8 *csign_key, size_t csign_key_len, + unsigned int freq, const u8 *hdr, + const u8 *attr_start, size_t attr_len) +{ + struct dpp_authentication *auth = NULL; + const u8 *trans_id, *version, *i_connector, *i_nonce; + u16 trans_id_len, version_len, i_connector_len, i_nonce_len; + struct dpp_signed_connector_info info; + enum dpp_status_error res; + struct json_token *root = NULL, *own_root = NULL, *token; + unsigned char *own_conn = NULL; + struct wpabuf *conn_status = NULL; + + os_memset(&info, 0, sizeof(info)); + + trans_id = dpp_get_attr(attr_start, attr_len, DPP_ATTR_TRANSACTION_ID, + &trans_id_len); + if (!trans_id || trans_id_len != 1) { + wpa_printf(MSG_DEBUG, + "DPP: Peer did not include Transaction ID"); + goto fail; + } + + version = dpp_get_attr(attr_start, attr_len, DPP_ATTR_PROTOCOL_VERSION, + &version_len); + if (!version || version_len < 1 || version[0] < 2) { + wpa_printf(MSG_DEBUG, + "DPP: Missing or invalid Protocol Version attribute"); + goto fail; + } + + i_connector = dpp_get_attr(attr_start, attr_len, DPP_ATTR_CONNECTOR, + &i_connector_len); + if (!i_connector) { + wpa_printf(MSG_DEBUG, "DPP: Missing I-Connector attribute"); + goto fail; + } + wpa_hexdump_ascii(MSG_DEBUG, "DPP: I-Connector", + i_connector, i_connector_len); + + i_nonce = dpp_get_attr(attr_start, attr_len, DPP_ATTR_I_NONCE, + &i_nonce_len); + if (!i_nonce || i_nonce_len > DPP_MAX_NONCE_LEN) { + wpa_printf(MSG_DEBUG, + "DPP: Missing or invalid I-Nonce attribute"); + goto fail; + } + wpa_hexdump(MSG_DEBUG, "DPP: I-Nonce", i_nonce, i_nonce_len); + + res = dpp_check_signed_connector(&info, csign_key, csign_key_len, + i_connector, i_connector_len); + if (res != DPP_STATUS_OK) { + wpa_printf(MSG_DEBUG, "DPP: Invalid I-Connector"); + goto fail; + } + + root = json_parse((const char *) info.payload, info.payload_len); + own_root = dpp_parse_own_connector(own_connector); + if (!root || !own_root || + !dpp_connector_match_groups(own_root, root, true)) { + wpa_printf(MSG_DEBUG, + "DPP: I-Connector does not include compatible group netrole with own connector"); + goto fail; + } + + token = json_get_member(root, "expiry"); + if (token && token->type == JSON_STRING && + dpp_key_expired(token->string, NULL)) { + wpa_printf(MSG_DEBUG, + "DPP: I-Connector (netAccessKey) has expired"); + goto fail; + } + + token = json_get_member(root, "netAccessKey"); + if (!token || token->type != JSON_OBJECT) { + wpa_printf(MSG_DEBUG, "DPP: No netAccessKey object found"); + goto fail; + } + + auth = dpp_alloc_auth(dpp, msg_ctx); + if (!auth) + return NULL; + + auth->reconfig = 1; + auth->allowed_roles = DPP_CAPAB_ENROLLEE; + if (dpp_prepare_channel_list(auth, freq, NULL, 0) < 0) + goto fail; + + auth->transaction_id = trans_id[0]; + + auth->peer_version = version[0]; + wpa_printf(MSG_DEBUG, "DPP: Peer protocol version %u", + auth->peer_version); + + os_memcpy(auth->i_nonce, i_nonce, i_nonce_len); + + if (dpp_reconfig_derive_ke_responder(auth, net_access_key, + net_access_key_len, token) < 0) + goto fail; + + if (i_nonce_len != auth->curve->nonce_len) { + wpa_printf(MSG_DEBUG, + "DPP: Unexpected I-nonce length %u (curve nonce len %zu)", + i_nonce_len, auth->curve->nonce_len); + goto fail; + } + + if (random_get_bytes(auth->r_nonce, auth->curve->nonce_len)) { + wpa_printf(MSG_ERROR, "DPP: Failed to generate R-nonce"); + goto fail; + } + wpa_hexdump_key(MSG_DEBUG, "DPP: R-nonce", + auth->r_nonce, auth->curve->nonce_len); + + /* Build Connection Status object */ + /* TODO: Get appropriate result value */ + /* TODO: ssid64 and channelList */ + conn_status = dpp_build_conn_status(DPP_STATUS_NO_AP, NULL, 0, NULL); + if (!conn_status) + goto fail; + + if (dpp_reconfig_build_resp(auth, own_connector, conn_status) < 0) + goto fail; + +out: + os_free(info.payload); + os_free(own_conn); + json_free(root); + json_free(own_root); + wpabuf_free(conn_status); + return auth; +fail: + dpp_auth_deinit(auth); + auth = NULL; + goto out; +} + #endif /* CONFIG_DPP2 */ diff --git a/wpa_supplicant/dpp_supplicant.c b/wpa_supplicant/dpp_supplicant.c index 4a32d57c6..3f88f372c 100644 --- a/wpa_supplicant/dpp_supplicant.c +++ b/wpa_supplicant/dpp_supplicant.c @@ -1915,6 +1915,53 @@ wpas_dpp_rx_reconfig_announcement(struct wpa_supplicant *wpa_s, const u8 *src, } } + +static void +wpas_dpp_rx_reconfig_auth_req(struct wpa_supplicant *wpa_s, const u8 *src, + const u8 *hdr, const u8 *buf, size_t len, + unsigned int freq) +{ + struct wpa_ssid *ssid; + struct dpp_authentication *auth; + + wpa_printf(MSG_DEBUG, "DPP: Reconfig Authentication Request from " + MACSTR, MAC2STR(src)); + + if (!wpa_s->dpp || wpa_s->dpp_auth || + !wpa_s->dpp_reconfig_announcement || !wpa_s->dpp_reconfig_ssid) + return; + for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) { + if (ssid == wpa_s->dpp_reconfig_ssid && + ssid->id == wpa_s->dpp_reconfig_ssid_id) + break; + } + if (!ssid || !ssid->dpp_connector || !ssid->dpp_netaccesskey || + !ssid->dpp_csign) + return; + + auth = dpp_reconfig_auth_req_rx(wpa_s->dpp, wpa_s, ssid->dpp_connector, + ssid->dpp_netaccesskey, + ssid->dpp_netaccesskey_len, + ssid->dpp_csign, ssid->dpp_csign_len, + freq, hdr, buf, len); + if (!auth) + return; + os_memcpy(auth->peer_mac_addr, src, ETH_ALEN); + wpa_s->dpp_auth = auth; + + wpas_dpp_chirp_stop(wpa_s); + + wpa_msg(wpa_s, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR " freq=%u type=%d", + MAC2STR(src), freq, DPP_PA_RECONFIG_AUTH_RESP); + if (offchannel_send_action(wpa_s, freq, src, wpa_s->own_addr, broadcast, + wpabuf_head(auth->reconfig_resp_msg), + wpabuf_len(auth->reconfig_resp_msg), + 500, wpas_dpp_tx_status, 0) < 0) { + dpp_auth_deinit(wpa_s->dpp_auth); + wpa_s->dpp_auth = NULL; + } +} + #endif /* CONFIG_DPP2 */ @@ -2480,6 +2527,9 @@ void wpas_dpp_rx_action(struct wpa_supplicant *wpa_s, const u8 *src, wpas_dpp_rx_reconfig_announcement(wpa_s, src, hdr, buf, len, freq); break; + case DPP_PA_RECONFIG_AUTH_REQ: + wpas_dpp_rx_reconfig_auth_req(wpa_s, src, hdr, buf, len, freq); + break; #endif /* CONFIG_DPP2 */ default: wpa_printf(MSG_DEBUG,