diff --git a/src/common/dpp.c b/src/common/dpp.c index 30c47e4ba..4b21254cd 100644 --- a/src/common/dpp.c +++ b/src/common/dpp.c @@ -4146,3 +4146,501 @@ fail: conf = NULL; goto out; } + + +static int dpp_compatible_netrole(const char *role1, const char *role2) +{ + return (os_strcmp(role1, "sta") == 0 && os_strcmp(role2, "ap") == 0) || + (os_strcmp(role1, "ap") == 0 && os_strcmp(role2, "sta") == 0); +} + + +static int dpp_connector_compatible_group(struct json_token *root, + const char *group_id, + const char *net_role) +{ + struct json_token *groups, *token; + + groups = json_get_member(root, "groups"); + if (!groups || groups->type != JSON_ARRAY) + return 0; + + for (token = groups->child; token; token = token->sibling) { + struct json_token *id, *role; + + id = json_get_member(token, "groupId"); + if (!id || id->type != JSON_STRING) + continue; + + role = json_get_member(token, "netRole"); + if (!role || role->type != JSON_STRING) + continue; + + if (os_strcmp(id->string, "*") != 0 && + os_strcmp(group_id, "*") != 0 && + os_strcmp(id->string, group_id) != 0) + continue; + + if (dpp_compatible_netrole(role->string, net_role)) + return 1; + } + + return 0; +} + + +static int dpp_connector_match_groups(struct json_token *own_root, + struct json_token *peer_root) +{ + struct json_token *groups, *token; + + groups = json_get_member(peer_root, "groups"); + if (!groups || groups->type != JSON_ARRAY) { + wpa_printf(MSG_DEBUG, "DPP: No peer groups array found"); + return 0; + } + + for (token = groups->child; token; token = token->sibling) { + struct json_token *id, *role; + + id = json_get_member(token, "groupId"); + if (!id || id->type != JSON_STRING) { + wpa_printf(MSG_DEBUG, + "DPP: Missing peer groupId string"); + continue; + } + + role = json_get_member(token, "netRole"); + if (!role || role->type != JSON_STRING) { + wpa_printf(MSG_DEBUG, + "DPP: Missing peer groups::netRole string"); + continue; + } + wpa_printf(MSG_DEBUG, + "DPP: peer connector group: groupId='%s' netRole='%s'", + id->string, role->string); + if (dpp_connector_compatible_group(own_root, id->string, + role->string)) { + wpa_printf(MSG_DEBUG, + "DPP: Compatible group/netRole in own connector"); + return 1; + } + } + + return 0; +} + + +static int dpp_connector_compatible_device(struct json_token *root, + const char *device_id, + const char *net_role) +{ + struct json_token *groups, *token; + + groups = json_get_member(root, "devices"); + if (!groups || groups->type != JSON_ARRAY) + return 0; + + for (token = groups->child; token; token = token->sibling) { + struct json_token *id, *role; + + id = json_get_member(token, "deviceId"); + if (!id || id->type != JSON_STRING) + continue; + + role = json_get_member(token, "netRole"); + if (!role || role->type != JSON_STRING) + continue; + + if (os_strcmp(id->string, device_id) != 0) + continue; + + if (dpp_compatible_netrole(role->string, net_role)) + return 1; + } + + return 0; +} + + +static int dpp_connector_match_devices(struct json_token *own_root, + struct json_token *peer_root, + const char *own_deviceid) +{ + struct json_token *devices, *token; + + devices = json_get_member(peer_root, "devices"); + if (!devices || devices->type != JSON_ARRAY) { + wpa_printf(MSG_DEBUG, "DPP: No peer devices array found"); + return 0; + } + + for (token = devices->child; token; token = token->sibling) { + struct json_token *id, *role; + + id = json_get_member(token, "deviceId"); + if (!id || id->type != JSON_STRING) { + wpa_printf(MSG_DEBUG, + "DPP: Missing or invalid deviceId string"); + continue; + } + + role = json_get_member(token, "netRole"); + if (!role || role->type != JSON_STRING) { + wpa_printf(MSG_DEBUG, "DPP: Missing netRole string"); + continue; + } + wpa_printf(MSG_DEBUG, + "DPP: connector device deviceId='%s' netRole='%s'", + id->string, role->string); + if (os_strcmp(id->string, own_deviceid) != 0) + continue; + + wpa_printf(MSG_DEBUG, + "DPP: Listed deviceId matches own deviceId"); + /* TODO: Is this next step required? */ + if (dpp_connector_compatible_device(own_root, id->string, + role->string)) { + wpa_printf(MSG_DEBUG, + "DPP: Compatible device/netRole in own connector"); + return 1; + } + /* TODO: For now, accept this for interop testing purposes based + * on a simple match of deviceId while ignoring netRole. Once + * the spec is clearer on the expected behavior, either this + * comment or the following return 1 statement needs to be + * removed. + */ + return 1; + } + + return 0; +} + + +static int dpp_connector_match(struct json_token *own_root, + struct json_token *peer_root, + const char *own_deviceid) +{ + return dpp_connector_match_groups(own_root, peer_root) || + dpp_connector_match_devices(own_root, peer_root, own_deviceid); +} + + +static int dpp_derive_pmk(const u8 *Nx, size_t Nx_len, u8 *pmk, + unsigned int hash_len) +{ + u8 salt[DPP_MAX_HASH_LEN], prk[DPP_MAX_HASH_LEN]; + const char *info = "DPP PMK"; + int res = -1; + + /* PMK = HKDF(<>, "DPP PMK", N.x) */ + + /* HKDF-Extract(<>, N.x) */ + os_memset(salt, 0, hash_len); + if (hash_len == 32) { + if (hmac_sha256(salt, SHA256_MAC_LEN, Nx, Nx_len, prk) < 0) + return -1; + } else if (hash_len == 48) { + if (hmac_sha384(salt, SHA384_MAC_LEN, Nx, Nx_len, prk) < 0) + return -1; + } else if (hash_len == 64) { + if (hmac_sha512(salt, SHA512_MAC_LEN, Nx, Nx_len, prk) < 0) + return -1; + } else { + return -1; + } + wpa_hexdump_key(MSG_DEBUG, "DPP: PRK = HKDF-Extract(<>, IKM=N.x)", + prk, hash_len); + + /* HKDF-Expand(PRK, info, L) */ + if (hash_len == 32) + res = hmac_sha256_kdf(prk, SHA256_MAC_LEN, NULL, + (const u8 *) info, os_strlen(info), + pmk, SHA256_MAC_LEN); + else if (hash_len == 48) + res = hmac_sha384_kdf(prk, SHA384_MAC_LEN, NULL, + (const u8 *) info, os_strlen(info), + pmk, SHA384_MAC_LEN); + else if (hash_len == 64) + res = hmac_sha512_kdf(prk, SHA512_MAC_LEN, NULL, + (const u8 *) info, os_strlen(info), + pmk, SHA512_MAC_LEN); + os_memset(prk, 0, hash_len); + if (res < 0) + return -1; + + wpa_hexdump_key(MSG_DEBUG, "DPP: PMK = HKDF-Expand(PRK, info, L)", + pmk, hash_len); + return 0; +} + + +static int dpp_derive_pmkid(const struct dpp_curve_params *curve, + EVP_PKEY *own_key, EVP_PKEY *peer_key, u8 *pmkid) +{ + struct wpabuf *nkx, *pkx; + int ret = -1, res; + const u8 *addr[2]; + size_t len[2]; + u8 hash[DPP_MAX_HASH_LEN]; + + /* PMKID = Truncate-128(H(min(NK.x, PK.x) | max(NK.x, PK.x))) */ + nkx = dpp_get_pubkey_point(own_key, 0); + pkx = dpp_get_pubkey_point(peer_key, 0); + if (!nkx || !pkx) + goto fail; + addr[0] = wpabuf_head(nkx); + len[0] = wpabuf_len(nkx) / 2; + addr[1] = wpabuf_head(pkx); + len[1] = wpabuf_len(pkx) / 2; + if (len[0] != len[1]) + goto fail; + if (os_memcmp(addr[0], addr[1], len[0]) > 0) { + addr[0] = wpabuf_head(pkx); + addr[1] = wpabuf_head(nkx); + } + wpa_printf(MSG_DEBUG, "DPP: PMKID H=SHA%u", + (unsigned int) curve->hash_len * 8); + wpa_hexdump(MSG_DEBUG, "DPP: PMKID hash payload 1", addr[0], len[0]); + wpa_hexdump(MSG_DEBUG, "DPP: PMKID hash payload 2", addr[1], len[1]); + if (curve->hash_len == 32) + res = sha256_vector(2, addr, len, hash); + else if (curve->hash_len == 48) + res = sha384_vector(2, addr, len, hash); + else if (curve->hash_len == 64) + res = sha512_vector(2, addr, len, hash); + else + res = -1; + if (res < 0) + goto fail; + wpa_hexdump(MSG_DEBUG, "DPP: PMKID hash output", + hash, curve->hash_len); + os_memcpy(pmkid, hash, PMKID_LEN); + wpa_hexdump(MSG_DEBUG, "DPP: PMKID", pmkid, PMKID_LEN); + ret = 0; +fail: + wpabuf_free(nkx); + wpabuf_free(pkx); + return ret; +} + + +static int dpp_netkey_hash(EVP_PKEY *key, u8 *hash) +{ + EC_KEY *eckey; + unsigned char *der = NULL; + int ret, der_len; + const u8 *addr[1]; + size_t len[1]; + + eckey = EVP_PKEY_get1_EC_KEY(key); + if (!eckey) + return -1; + EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED); + der_len = i2d_EC_PUBKEY(eckey, &der); + EC_KEY_free(eckey); + if (der_len <= 0) + return -1; + addr[0] = der; + len[0] = der_len; + ret = sha256_vector(1, addr, len, hash); + OPENSSL_free(der); + return ret; +} + + +int dpp_peer_intro(struct dpp_introduction *intro, const char *own_connector, + const u8 *net_access_key, size_t net_access_key_len, + const u8 *csign_key, size_t csign_key_len, + const u8 *peer_connector, size_t peer_connector_len) +{ + struct json_token *root = NULL, *netkey, *token; + struct json_token *own_root = NULL; + int ret = -1; + EVP_PKEY *own_key = NULL, *peer_key = NULL; + struct wpabuf *own_key_pub = NULL; + char *own_deviceid = NULL; + const struct dpp_curve_params *curve, *own_curve; + struct dpp_signed_connector_info info; + const unsigned char *p; + EVP_PKEY *csign = NULL; + char *signed_connector = NULL; + const char *pos, *end; + unsigned char *own_conn = NULL; + size_t own_conn_len; + EVP_PKEY_CTX *ctx = NULL; + size_t Nx_len; + u8 Nx[DPP_MAX_SHARED_SECRET_LEN]; + u8 hash[SHA256_MAC_LEN]; + const u8 *addr[1]; + size_t len[1]; + + os_memset(intro, 0, sizeof(*intro)); + os_memset(&info, 0, sizeof(info)); + + p = csign_key; + csign = d2i_PUBKEY(NULL, &p, csign_key_len); + if (!csign) { + wpa_printf(MSG_ERROR, + "DPP: Failed to parse local C-sign-key information"); + goto fail; + } + + own_key = dpp_set_keypair(&own_curve, net_access_key, + net_access_key_len); + if (!own_key) { + wpa_printf(MSG_ERROR, "DPP: Failed to parse own netAccessKey"); + goto fail; + } + /* deviceId = SHA256(ANSI X9.63 uncompressed netAccessKey) */ + own_key_pub = dpp_get_pubkey_point(own_key, 1); + if (!own_key_pub) + goto fail; + wpa_hexdump_buf(MSG_DEBUG, + "DPP: ANSI X9.63 uncompressed public key of own netAccessKey", + own_key_pub); + addr[0] = wpabuf_head(own_key_pub); + len[0] = wpabuf_len(own_key_pub); + if (sha256_vector(1, addr, len, hash) < 0) + goto fail; + wpa_hexdump(MSG_DEBUG, + "DPP: SHA256 hash of ANSI X9.63 uncompressed form", + hash, SHA256_MAC_LEN); + + own_deviceid = (char *) base64_url_encode(hash, sizeof(hash), NULL, 0); + if (!own_deviceid) + goto fail; + wpa_printf(MSG_DEBUG, + "DPP: Own deviceId (base64url encoded hash value): %s", + own_deviceid); + + pos = os_strchr(own_connector, '.'); + if (!pos) + goto fail; + pos++; + end = os_strchr(pos, '.'); + if (!end) + goto fail; + own_conn = base64_url_decode((const unsigned char *) pos, + end - pos, &own_conn_len); + if (!own_conn) { + wpa_printf(MSG_DEBUG, + "DPP: Failed to base64url decode own signedConnector JWS Payload"); + goto fail; + } + + own_root = json_parse((const char *) own_conn, own_conn_len); + if (!own_root) { + wpa_printf(MSG_DEBUG, "DPP: Failed to parse local connector"); + goto fail; + } + + wpa_hexdump_ascii(MSG_DEBUG, "DPP: Peer signedConnector", + peer_connector, peer_connector_len); + signed_connector = os_malloc(peer_connector_len + 1); + if (!signed_connector) + goto fail; + os_memcpy(signed_connector, peer_connector, peer_connector_len); + signed_connector[peer_connector_len] = '\0'; + + if (dpp_process_signed_connector(&info, csign, signed_connector) < 0) + goto fail; + + root = json_parse((const char *) info.payload, info.payload_len); + if (!root) { + wpa_printf(MSG_DEBUG, "DPP: JSON parsing of connector failed"); + goto fail; + } + + if (!dpp_connector_match(own_root, root, own_deviceid)) { + wpa_printf(MSG_DEBUG, + "DPP: Peer connector does not include compatible group/device netrole with own connector"); + goto fail; + } + + token = json_get_member(root, "expiry"); + if (!token || token->type != JSON_STRING) { + wpa_printf(MSG_DEBUG, + "DPP: No expiry string found - connector does not expire"); + } else { + wpa_printf(MSG_DEBUG, "DPP: expiry = %s", token->string); + if (dpp_key_expired(token->string, NULL)) { + wpa_printf(MSG_DEBUG, + "DPP: Connector (netAccessKey) has expired"); + goto fail; + } + } + + netkey = json_get_member(root, "netAccessKey"); + if (!netkey || netkey->type != JSON_OBJECT) { + wpa_printf(MSG_DEBUG, "DPP: No netAccessKey object found"); + goto fail; + } + + peer_key = dpp_parse_jwk(netkey, &curve); + if (!peer_key) + goto fail; + dpp_debug_print_key("DPP: Received netAccessKey", peer_key); + + if (own_curve != curve) { + wpa_printf(MSG_DEBUG, + "DPP: Mismatching netAccessKey curves (%s != %s)", + own_curve->name, curve->name); + goto fail; + } + + /* ECDH: N = nk * PK */ + ctx = EVP_PKEY_CTX_new(own_key, NULL); + if (!ctx || + EVP_PKEY_derive_init(ctx) != 1 || + EVP_PKEY_derive_set_peer(ctx, peer_key) != 1 || + EVP_PKEY_derive(ctx, NULL, &Nx_len) != 1 || + Nx_len > DPP_MAX_SHARED_SECRET_LEN || + EVP_PKEY_derive(ctx, Nx, &Nx_len) != 1) { + wpa_printf(MSG_ERROR, + "DPP: Failed to derive ECDH shared secret: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (N.x)", + Nx, Nx_len); + + /* PMK = HKDF(<>, "DPP PMK", N.x) */ + if (dpp_derive_pmk(Nx, Nx_len, intro->pmk, curve->hash_len) < 0) { + wpa_printf(MSG_ERROR, "DPP: Failed to derive PMK"); + goto fail; + } + intro->pmk_len = curve->hash_len; + + /* PMKID = Truncate-128(H(min(NK.x, PK.x) | max(NK.x, PK.x))) */ + if (dpp_derive_pmkid(curve, own_key, peer_key, intro->pmkid) < 0) { + wpa_printf(MSG_ERROR, "DPP: Failed to derive PMKID"); + goto fail; + } + + if (dpp_netkey_hash(own_key, intro->nk_hash) < 0 || + dpp_netkey_hash(peer_key, intro->pk_hash) < 0) { + wpa_printf(MSG_ERROR, "DPP: Failed to derive NK/PK hash"); + goto fail; + } + + ret = 0; +fail: + if (ret < 0) + os_memset(intro, 0, sizeof(*intro)); + os_memset(Nx, 0, sizeof(Nx)); + EVP_PKEY_CTX_free(ctx); + os_free(own_conn); + os_free(signed_connector); + os_free(info.payload); + EVP_PKEY_free(own_key); + wpabuf_free(own_key_pub); + os_free(own_deviceid); + EVP_PKEY_free(peer_key); + EVP_PKEY_free(csign); + json_free(root); + json_free(own_root); + return ret; +} diff --git a/src/common/dpp.h b/src/common/dpp.h index 2f831bc4c..9cf4a13fe 100644 --- a/src/common/dpp.h +++ b/src/common/dpp.h @@ -12,6 +12,7 @@ #include #include "utils/list.h" +#include "common/wpa_common.h" #include "crypto/sha256.h" /* DPP Public Action frame identifiers - OUI_WFA */ @@ -176,6 +177,14 @@ struct dpp_configurator { os_time_t csign_expiry; }; +struct dpp_introduction { + u8 pmkid[PMKID_LEN]; + u8 pmk[PMK_LEN_MAX]; + size_t pmk_len; + u8 pk_hash[SHA256_MAC_LEN]; + u8 nk_hash[SHA256_MAC_LEN]; +}; + void dpp_bootstrap_info_free(struct dpp_bootstrap_info *info); int dpp_parse_uri_chan_list(struct dpp_bootstrap_info *bi, const char *chan_list); @@ -219,5 +228,9 @@ void dpp_configurator_free(struct dpp_configurator *conf); struct dpp_configurator * dpp_keygen_configurator(const char *curve, const u8 *privkey, size_t privkey_len); +int dpp_peer_intro(struct dpp_introduction *intro, const char *own_connector, + const u8 *net_access_key, size_t net_access_key_len, + const u8 *csign_key, size_t csign_key_len, + const u8 *peer_connector, size_t peer_connector_len); #endif /* DPP_H */