diff --git a/src/dpp.c b/src/dpp.c index 7a7301e2..d4327ff4 100644 --- a/src/dpp.c +++ b/src/dpp.c @@ -57,6 +57,7 @@ #define DPP_FRAME_MAX_RETRIES 5 #define DPP_FRAME_RETRY_TIMEOUT 1 #define DPP_AUTH_PROTO_TIMEOUT 10 +#define DPP_PKEX_PROTO_TIMEOUT 120 static uint32_t netdev_watch; static struct l_genl_family *nl80211; @@ -70,6 +71,8 @@ static uint8_t dpp_prefix[] = { 0x04, 0x09, 0x50, 0x6f, 0x9a, 0x1a, 0x01 }; enum dpp_state { DPP_STATE_NOTHING, DPP_STATE_PRESENCE, + DPP_STATE_PKEX_EXCHANGE, + DPP_STATE_PKEX_COMMIT_REVEAL, DPP_STATE_AUTHENTICATING, DPP_STATE_CONFIGURING, }; @@ -89,6 +92,7 @@ struct dpp_sm { struct netdev *netdev; char *uri; uint8_t role; + int refcount; uint64_t wdev_id; @@ -154,12 +158,73 @@ struct dpp_sm { struct l_dbus_message *pending; + /* PKEX-specific values */ + char *pkex_id; + char *pkex_key; + uint8_t pkex_version; + struct l_ecc_point *pkex_m; + /* Ephemeral key Y' or X' for enrollee or configurator */ + struct l_ecc_point *y_or_x; + /* Ephemeral key pair y/Y or x/X */ + struct l_ecc_point *pkex_public; + struct l_ecc_scalar *pkex_private; + uint8_t z[L_ECC_SCALAR_MAX_BYTES]; + size_t z_len; + uint8_t u[L_ECC_SCALAR_MAX_BYTES]; + size_t u_len; + bool mcast_support : 1; bool roc_started : 1; bool channel_switch : 1; bool mutual_auth : 1; }; +static const char *dpp_role_to_string(enum dpp_capability role) +{ + switch (role) { + case DPP_CAPABILITY_ENROLLEE: + return "enrollee"; + case DPP_CAPABILITY_CONFIGURATOR: + return "configurator"; + default: + return NULL; + } +} + +static bool dpp_pkex_get_started(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + struct dpp_sm *dpp = user_data; + bool started = (dpp->state != DPP_STATE_NOTHING && + dpp->interface == DPP_INTERFACE_PKEX); + + l_dbus_message_builder_append_basic(builder, 'b', &started); + + return true; +} + +static bool dpp_pkex_get_role(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + struct dpp_sm *dpp = user_data; + const char *role; + + if (dpp->state == DPP_STATE_NOTHING || + dpp->interface != DPP_INTERFACE_PKEX) + return false; + + role = dpp_role_to_string(dpp->role); + if (L_WARN_ON(!role)) + return false; + + l_dbus_message_builder_append_basic(builder, 's', role); + return true; +} + static bool dpp_get_started(struct l_dbus *dbus, struct l_dbus_message *message, struct l_dbus_message_builder *builder, @@ -186,16 +251,9 @@ static bool dpp_get_role(struct l_dbus *dbus, dpp->interface != DPP_INTERFACE_DPP) return false; - switch (dpp->role) { - case DPP_CAPABILITY_ENROLLEE: - role = "enrollee"; - break; - case DPP_CAPABILITY_CONFIGURATOR: - role = "configurator"; - break; - default: + role = dpp_role_to_string(dpp->role); + if (L_WARN_ON(!role)) return false; - } l_dbus_message_builder_append_basic(builder, 's', role); return true; @@ -229,6 +287,14 @@ static void dpp_property_changed_notify(struct dpp_sm *dpp) l_dbus_property_changed(dbus_get_bus(), path, IWD_DPP_INTERFACE, "URI"); break; + case DPP_INTERFACE_PKEX: + l_dbus_property_changed(dbus_get_bus(), path, + IWD_DPP_PKEX_INTERFACE, + "Started"); + l_dbus_property_changed(dbus_get_bus(), path, + IWD_DPP_PKEX_INTERFACE, + "Role"); + break; default: break; } @@ -258,6 +324,21 @@ static void *dpp_serialize_iovec(struct iovec *iov, size_t iov_len, return ret; } +static void dpp_free_pending_pkex_data(struct dpp_sm *dpp) +{ + if (dpp->pkex_id) { + l_free(dpp->pkex_id); + dpp->pkex_id = NULL; + } + + if (dpp->pkex_key) { + l_free(dpp->pkex_key); + dpp->pkex_key = NULL; + } + + memset(dpp->peer_addr, 0, sizeof(dpp->peer_addr)); +} + static void dpp_free_auth_data(struct dpp_sm *dpp) { if (dpp->own_proto_public) { @@ -284,6 +365,27 @@ static void dpp_free_auth_data(struct dpp_sm *dpp) l_ecc_scalar_free(dpp->m); dpp->m = NULL; } + + if (dpp->pkex_m) { + l_ecc_point_free(dpp->pkex_m); + dpp->pkex_m = NULL; + } + + if (dpp->y_or_x) { + l_ecc_point_free(dpp->y_or_x); + dpp->y_or_x = NULL; + } + + if (dpp->pkex_public) { + l_ecc_point_free(dpp->pkex_public); + dpp->pkex_public = NULL; + } + + if (dpp->pkex_private) { + l_ecc_scalar_free(dpp->pkex_private); + dpp->pkex_private = NULL; + } + } static void dpp_reset(struct dpp_sm *dpp) @@ -337,6 +439,7 @@ static void dpp_reset(struct dpp_sm *dpp) dpp->new_freq = 0; dpp->frame_retry = 0; dpp->frame_cookie = 0; + dpp->pkex_version = 0; explicit_bzero(dpp->r_nonce, dpp->nonce_len); explicit_bzero(dpp->i_nonce, dpp->nonce_len); @@ -345,6 +448,10 @@ static void dpp_reset(struct dpp_sm *dpp) explicit_bzero(dpp->k1, dpp->key_len); explicit_bzero(dpp->k2, dpp->key_len); explicit_bzero(dpp->auth_tag, dpp->key_len); + explicit_bzero(dpp->z, dpp->key_len); + explicit_bzero(dpp->u, dpp->u_len); + + dpp_free_pending_pkex_data(dpp); dpp_free_auth_data(dpp); @@ -1530,6 +1637,71 @@ static bool dpp_send_authenticate_request(struct dpp_sm *dpp) return true; } +static void dpp_send_pkex_exchange_request(struct dpp_sm *dpp) +{ + uint8_t hdr[32]; + uint8_t attrs[256]; + uint8_t *ptr = attrs; + uint64_t m_data[L_ECC_MAX_DIGITS * 2]; + uint16_t group; + struct iovec iov[2]; + const uint8_t *own_mac = netdev_get_address(dpp->netdev); + + l_put_le16(l_ecc_curve_get_ike_group(dpp->curve), &group); + + iov[0].iov_len = dpp_build_header(own_mac, broadcast, + DPP_FRAME_PKEX_VERSION1_XCHG_REQUEST, hdr); + iov[0].iov_base = hdr; + + ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION, + &dpp->pkex_version, 1); + ptr += dpp_append_attr(ptr, DPP_ATTR_FINITE_CYCLIC_GROUP, + &group, 2); + + if (dpp->pkex_id) + ptr += dpp_append_attr(ptr, DPP_ATTR_CODE_IDENTIFIER, + dpp->pkex_id, strlen(dpp->pkex_id)); + + l_ecc_point_get_data(dpp->pkex_m, m_data, sizeof(m_data)); + + ptr += dpp_append_attr(ptr, DPP_ATTR_ENCRYPTED_KEY, + m_data, dpp->key_len * 2); + + iov[1].iov_base = attrs; + iov[1].iov_len = ptr - attrs; + + dpp_send_frame(dpp, iov, 2, dpp->current_freq); +} + +static void dpp_send_commit_reveal_request(struct dpp_sm *dpp) +{ + struct iovec iov[2]; + uint8_t hdr[41]; + uint8_t attrs[512]; + uint8_t *ptr = attrs; + uint8_t zero = 0; + uint8_t a_pub[L_ECC_POINT_MAX_BYTES]; + ssize_t a_len; + + a_len = l_ecc_point_get_data(dpp->boot_public, a_pub, sizeof(a_pub)); + + iov[0].iov_len = dpp_build_header(netdev_get_address(dpp->netdev), + dpp->peer_addr, + DPP_FRAME_PKEX_COMMIT_REVEAL_REQUEST, + hdr); + iov[0].iov_base = hdr; + + ptr += dpp_append_wrapped_data(hdr + 26, 6, &zero, 1, ptr, + sizeof(attrs), dpp->z, dpp->z_len, 2, + DPP_ATTR_BOOTSTRAPPING_KEY, a_len, a_pub, + DPP_ATTR_INITIATOR_AUTH_TAG, dpp->u_len, dpp->u); + + iov[1].iov_base = attrs; + iov[1].iov_len = ptr - attrs; + + dpp_send_frame(dpp, iov, 2, dpp->current_freq); +} + static void dpp_roc_started(void *user_data) { struct dpp_sm *dpp = user_data; @@ -1593,6 +1765,16 @@ static void dpp_roc_started(void *user_data) send_authenticate_response(dpp); } + break; + case DPP_STATE_PKEX_EXCHANGE: + if (dpp->role == DPP_CAPABILITY_ENROLLEE) + dpp_send_pkex_exchange_request(dpp); + + break; + case DPP_STATE_PKEX_COMMIT_REVEAL: + if (dpp->role == DPP_CAPABILITY_ENROLLEE) + dpp_send_commit_reveal_request(dpp); + break; default: break; @@ -1621,6 +1803,7 @@ static void dpp_offchannel_timeout(int error, void *user_data) goto protocol_failed; switch (dpp->state) { + case DPP_STATE_PKEX_EXCHANGE: case DPP_STATE_PRESENCE: break; case DPP_STATE_NOTHING: @@ -1628,6 +1811,7 @@ static void dpp_offchannel_timeout(int error, void *user_data) return; case DPP_STATE_AUTHENTICATING: case DPP_STATE_CONFIGURING: + case DPP_STATE_PKEX_COMMIT_REVEAL: goto next_roc; } @@ -2190,6 +2374,382 @@ static void dpp_handle_presence_announcement(struct dpp_sm *dpp, dpp->channel_switch = true; } +static void dpp_pkex_bad_group(struct dpp_sm *dpp, uint16_t group) +{ + uint16_t own_group = l_ecc_curve_get_ike_group(dpp->curve); + + /* + * TODO: The spec allows group negotiation, but it is not yet + * implemented. + */ + if (!group) + return; + /* + * Section 5.6.2 + * "If the Responder's offered group offers less security + * than the Initiator's offered group, then the Initiator should + * ignore this message" + */ + if (group < own_group) { + l_debug("Offered group %u is less secure, ignoring", + group); + return; + } + /* + * Section 5.6.2 + * "If the Responder's offered group offers equivalent or better + * security than the Initiator's offered group, then the + * Initiator may choose to abort its original request and try + * another exchange using the group offered by the Responder" + */ + if (group >= own_group) { + l_debug("Offered group %u is the same or more secure, " + " but group negotiation is not supported", group); + return; + } +} + +static void dpp_pkex_bad_code(struct dpp_sm *dpp) +{ + _auto_(l_ecc_point_free) struct l_ecc_point *qr = NULL; + + qr = dpp_derive_qr(dpp->curve, dpp->pkex_key, dpp->pkex_id, + netdev_get_address(dpp->netdev)); + if (!qr || l_ecc_point_is_infinity(qr)) { + l_debug("Qr computed to zero, new code should be provisioned"); + return; + } + + l_debug("Qr computed successfully but responder indicated otherwise"); +} + +static void dpp_handle_pkex_exchange_response(struct dpp_sm *dpp, + const uint8_t *from, + const uint8_t *body, size_t body_len) +{ + struct dpp_attr_iter iter; + enum dpp_attribute_type type; + size_t len; + const uint8_t *data; + const uint8_t *status = NULL; + uint8_t version = 0; + const void *identifier = NULL; + size_t identifier_len = 0; + const void *key = NULL; + size_t key_len = 0; + uint16_t group = 0; + _auto_(l_ecc_point_free) struct l_ecc_point *n = NULL; + _auto_(l_ecc_point_free) struct l_ecc_point *j = NULL; + _auto_(l_ecc_point_free) struct l_ecc_point *qr = NULL; + _auto_(l_ecc_point_free) struct l_ecc_point *k = NULL; + const uint8_t *own_addr = netdev_get_address(dpp->netdev); + + l_debug("PKEX response "MAC, MAC_STR(from)); + + if (dpp->state != DPP_STATE_PKEX_EXCHANGE) + return; + + if (dpp->role != DPP_CAPABILITY_ENROLLEE) + return; + + memcpy(dpp->peer_addr, from, 6); + + dpp_attr_iter_init(&iter, body + 8, body_len - 8); + + while (dpp_attr_iter_next(&iter, &type, &len, &data)) { + switch (type) { + case DPP_ATTR_STATUS: + if (len != 1) + return; + + status = data; + break; + case DPP_ATTR_PROTOCOL_VERSION: + if (len != 1) + return; + + version = l_get_u8(data); + break; + case DPP_ATTR_CODE_IDENTIFIER: + identifier = data; + identifier_len = len; + break; + case DPP_ATTR_ENCRYPTED_KEY: + if (len != dpp->key_len * 2) + return; + + key = data; + key_len = len; + break; + case DPP_ATTR_FINITE_CYCLIC_GROUP: + if (len != 2) + return; + + group = l_get_le16(data); + break; + default: + break; + } + } + + if (!status) { + l_debug("No status attribute, ignoring"); + return; + } + + if (!key) { + l_debug("No encrypted key, ignoring"); + return; + } + + if (*status != DPP_STATUS_OK) + goto handle_status; + + if (dpp->pkex_id) { + if (!identifier || identifier_len != strlen(dpp->pkex_id) || + memcmp(dpp->pkex_id, identifier, + identifier_len)) { + l_debug("mismatch identifier, ignoring"); + return; + } + } + + if (version && version != dpp->pkex_version) { + l_debug("PKEX version does not match, igoring"); + return; + } + + n = l_ecc_point_from_data(dpp->curve, L_ECC_POINT_TYPE_FULL, + key, key_len); + if (!n) { + l_debug("failed to parse peers encrypted key"); + goto failed; + } + + qr = dpp_derive_qr(dpp->curve, dpp->pkex_key, dpp->pkex_id, + dpp->peer_addr); + if (!qr) + goto failed; + + dpp->y_or_x = l_ecc_point_new(dpp->curve); + + /* Y' = N - Qr */ + l_ecc_point_inverse(qr); + l_ecc_point_add(dpp->y_or_x, n, qr); + + /* + * "The resulting ephemeral key, denoted Y’, is then checked whether it + * is the point-at-infinity. If it is not valid, the protocol ends + * unsuccessfully" + */ + if (l_ecc_point_is_infinity(dpp->y_or_x)) { + l_debug("Y' computed to infinity, failing"); + goto failed; + } + + k = l_ecc_point_new(dpp->curve); + + /* K = Y' * x */ + l_ecc_point_multiply(k, dpp->pkex_private, dpp->y_or_x); + + dpp_derive_z(own_addr, dpp->peer_addr, n, dpp->pkex_m, k, + dpp->pkex_key, dpp->pkex_id, + dpp->z, &dpp->z_len); + + /* J = a * Y' */ + j = l_ecc_point_new(dpp->curve); + + l_ecc_point_multiply(j, dpp->boot_private, dpp->y_or_x); + + if (!dpp_derive_u(j, own_addr, dpp->boot_public, dpp->y_or_x, + dpp->pkex_public, dpp->u, &dpp->u_len)) { + l_debug("failed to compute u"); + goto failed; + } + + /* + * Now that a response was successfully received, start another + * offchannel with more time for the remainder of the protocol. After + * PKEX, authentication will begin which handles the protocol timeout. + * If the remainder of PKEX (commit-reveal exchange) cannot complete in + * this time it will fail. + */ + dpp->dwell = (dpp->max_roc < 2000) ? dpp->max_roc : 2000; + dpp->state = DPP_STATE_PKEX_COMMIT_REVEAL; + + dpp_property_changed_notify(dpp); + + dpp_start_offchannel(dpp, dpp->current_freq); + + return; + +handle_status: + switch (*status) { + case DPP_STATUS_BAD_GROUP: + dpp_pkex_bad_group(dpp, group); + break; + case DPP_STATUS_BAD_CODE: + dpp_pkex_bad_code(dpp); + break; + default: + l_debug("Unhandled status %u", *status); + break; + } + +failed: + dpp_reset(dpp); +} + +static bool dpp_pkex_start_authentication(struct dpp_sm *dpp) +{ + dpp->uri = dpp_generate_uri(dpp->own_asn1, dpp->own_asn1_len, 2, + netdev_get_address(dpp->netdev), + &dpp->current_freq, 1, NULL, NULL); + + l_ecdh_generate_key_pair(dpp->curve, &dpp->proto_private, + &dpp->own_proto_public); + + l_getrandom(dpp->i_nonce, dpp->nonce_len); + + dpp->peer_asn1 = dpp_point_to_asn1(dpp->peer_boot_public, + &dpp->peer_asn1_len); + + dpp->m = dpp_derive_k1(dpp->peer_boot_public, dpp->proto_private, + dpp->k1); + + dpp_hash(L_CHECKSUM_SHA256, dpp->peer_boot_hash, 1, dpp->peer_asn1, + dpp->peer_asn1_len); + + dpp->state = DPP_STATE_AUTHENTICATING; + dpp->mutual_auth = true; + + dpp_property_changed_notify(dpp); + + if (dpp->role == DPP_CAPABILITY_ENROLLEE) { + dpp->new_freq = dpp->current_freq; + + return dpp_send_authenticate_request(dpp); + } + + return true; +} + +static void dpp_handle_pkex_commit_reveal_response(struct dpp_sm *dpp, + const uint8_t *from, + const uint8_t *body, size_t body_len) +{ + struct dpp_attr_iter iter; + enum dpp_attribute_type type; + size_t len; + const uint8_t *data; + const uint8_t *wrapped = NULL; + size_t wrapped_len = 0; + uint8_t one = 1; + _auto_(l_free) uint8_t *unwrapped = NULL; + size_t unwrapped_len = 0; + const uint8_t *boot_key = NULL; + size_t boot_key_len = 0; + const uint8_t *r_auth = NULL; + uint8_t v[L_ECC_SCALAR_MAX_BYTES]; + size_t v_len; + _auto_(l_ecc_point_free) struct l_ecc_point *l = NULL; + + l_debug("PKEX commit reveal "MAC, MAC_STR(from)); + + if (dpp->state != DPP_STATE_PKEX_COMMIT_REVEAL) + return; + + if (dpp->role != DPP_CAPABILITY_ENROLLEE) + return; + + /* + * The URI may not have contained a MAC address, if this announcement + * verifies set peer_addr then. + */ + if (memcmp(from, dpp->peer_addr, 6)) { + l_debug("Unexpected source "MAC" expected "MAC, MAC_STR(from), + MAC_STR(dpp->peer_addr)); + return; + } + + dpp_attr_iter_init(&iter, body + 8, body_len - 8); + + while (dpp_attr_iter_next(&iter, &type, &len, &data)) { + switch (type) { + case DPP_ATTR_WRAPPED_DATA: + wrapped = data; + wrapped_len = len; + break; + default: + break; + } + } + + if (!wrapped) { + l_debug("No wrapped data"); + return; + } + + unwrapped = dpp_unwrap_attr(body + 2, 6, &one, 1, dpp->z, dpp->z_len, + wrapped, wrapped_len, &unwrapped_len); + if (!unwrapped) { + l_debug("Failed to unwrap Reveal-Commit message"); + return; + } + + dpp_attr_iter_init(&iter, unwrapped, unwrapped_len); + + while (dpp_attr_iter_next(&iter, &type, &len, &data)) { + switch (type) { + case DPP_ATTR_BOOTSTRAPPING_KEY: + if (len != dpp->key_len * 2) + return; + + boot_key = data; + boot_key_len = len; + break; + case DPP_ATTR_RESPONDER_AUTH_TAG: + if (len != 32) + return; + + r_auth = data; + break; + default: + break; + } + } + + dpp->peer_boot_public = l_ecc_point_from_data(dpp->curve, + L_ECC_POINT_TYPE_FULL, + boot_key, boot_key_len); + if (!dpp->peer_boot_public) { + l_debug("Peer public bootstrapping key was invalid"); + goto failed; + } + + /* L = b * X' */ + l = l_ecc_point_new(dpp->curve); + + l_ecc_point_multiply(l, dpp->pkex_private, dpp->peer_boot_public); + + if (!dpp_derive_v(l, dpp->peer_addr, dpp->peer_boot_public, + dpp->pkex_public, dpp->y_or_x, v, &v_len)) { + l_debug("Failed to derive v"); + goto failed; + } + + if (memcmp(v, r_auth, v_len)) { + l_debug("Bootstrapping data did not verify"); + goto failed; + } + + if (dpp_pkex_start_authentication(dpp)) + return; + +failed: + dpp_reset(dpp); +} + static void dpp_handle_frame(struct dpp_sm *dpp, const struct mmpdu_header *frame, const void *body, size_t body_len) @@ -2224,6 +2784,14 @@ static void dpp_handle_frame(struct dpp_sm *dpp, dpp_handle_presence_announcement(dpp, frame->address_2, body, body_len); break; + case DPP_FRAME_PKEX_XCHG_RESPONSE: + dpp_handle_pkex_exchange_response(dpp, frame->address_2, body, + body_len); + break; + case DPP_FRAME_PKEX_COMMIT_REVEAL_RESPONSE: + dpp_handle_pkex_commit_reveal_response(dpp, frame->address_2, + body, body_len); + break; default: l_debug("Unhandled DPP frame %u", *ptr); break; @@ -2280,10 +2848,16 @@ static void dpp_mlme_notify(struct l_genl_msg *msg, void *user_data) if (!dpp) return; - if (dpp->state <= DPP_STATE_PRESENCE) + /* + * Don't retransmit for presence or PKEX exchange if an enrollee, both + * are broadcast frames which don't expect an ack. + */ + if (dpp->state == DPP_STATE_NOTHING || + dpp->state == DPP_STATE_PRESENCE || + (dpp->state == DPP_STATE_PKEX_EXCHANGE && + dpp->role == DPP_CAPABILITY_ENROLLEE)) return; - if (dpp->frame_cookie != cookie) return; @@ -2454,6 +3028,16 @@ static void dpp_create(struct netdev *netdev) l_dbus_object_add_interface(dbus, netdev_get_path(netdev), IWD_DPP_INTERFACE, dpp); + l_dbus_object_add_interface(dbus, netdev_get_path(netdev), + IWD_DPP_PKEX_INTERFACE, dpp); + /* + * Since both interfaces share the dpp_sm set this to 2. Currently both + * interfaces are added/removed in unison so we _could_ simply omit the + * destroy callback on one of them. But for consistency and future + * proofing use a reference count and the final interface being removed + * will destroy the dpp_sm. + */ + dpp->refcount = 2; dpp_frame_watch(dpp, 0x00d0, dpp_prefix, sizeof(dpp_prefix)); @@ -2484,6 +3068,9 @@ static void dpp_netdev_watch(struct netdev *netdev, l_dbus_object_remove_interface(dbus_get_bus(), netdev_get_path(netdev), IWD_DPP_INTERFACE); + l_dbus_object_remove_interface(dbus_get_bus(), + netdev_get_path(netdev), + IWD_DPP_PKEX_INTERFACE); break; default: break; @@ -2756,9 +3343,182 @@ static struct l_dbus_message *dpp_dbus_stop(struct l_dbus *dbus, if (dpp->interface != DPP_INTERFACE_DPP) return dbus_error_not_found(message); + return l_dbus_message_new_method_return(message); +} + +/* + * Section 5.6.1 + * In lieu of specific channel information obtained in a manner outside + * the scope of this specification, PKEX responders shall select one of + * the following channels: + * - 2.4 GHz: Channel 6 (2.437 GHz) + * - 5 GHz: Channel 44 (5.220 GHz) if local regulations permit + * operation only in the 5.150 - 5.250 GHz band and Channel + * 149 (5.745 GHz) otherwise + */ +static uint32_t *dpp_default_freqs(struct dpp_sm *dpp, size_t *out_len) +{ + struct wiphy *wiphy = wiphy_find_by_wdev(dpp->wdev_id); + uint32_t default_channels[3] = { 2437, 5220, 5745 }; + uint32_t *freqs_out; + size_t len = 0; + + if ((wiphy_get_supported_bands(wiphy) & BAND_FREQ_2_4_GHZ) && + scan_get_band_rank_modifier(BAND_FREQ_2_4_GHZ) != 0) + default_channels[len++] = 2437; + + if ((wiphy_get_supported_bands(wiphy) & BAND_FREQ_5_GHZ) && + scan_get_band_rank_modifier(BAND_FREQ_5_GHZ) != 0) { + default_channels[len++] = 5220; + default_channels[len++] = 5745; + } + + if (!len) { + l_warn("No bands are allowed, check BandModifier* settings!"); + return NULL; + } + + freqs_out = l_memdup(default_channels, sizeof(uint32_t) * len); + *out_len = len; + + return freqs_out; +} + +static bool dpp_start_pkex_enrollee(struct dpp_sm *dpp, const char *key, + const char *identifier) +{ + struct station *station = station_find(netdev_get_ifindex(dpp->netdev)); + _auto_(l_ecc_point_free) struct l_ecc_point *qi = NULL; + + if (station && station_get_connected_network(station)) { + l_debug("Already connected, disconnect before enrolling"); + return false; + } + + if (identifier) + dpp->pkex_id = l_strdup(identifier); + + dpp->pkex_key = l_strdup(key); + memcpy(dpp->peer_addr, broadcast, 6); + dpp->role = DPP_CAPABILITY_ENROLLEE; + dpp->state = DPP_STATE_PKEX_EXCHANGE; + dpp->interface = DPP_INTERFACE_PKEX; + /* + * In theory a driver could support a lesser duration than 200ms. This + * complicates things since we would need to tack on additional + * offchannel requests to meet the 200ms requirement. This could be done + * but for now use max_roc or 200ms, whichever is less. + */ + dpp->dwell = (dpp->max_roc < 200) ? dpp->max_roc : 200; + /* "DPP R2 devices are expected to use PKEXv1 by default" */ + dpp->pkex_version = 1; + + if (!l_ecdh_generate_key_pair(dpp->curve, &dpp->pkex_private, + &dpp->pkex_public)) + goto failed; + + /* + * "If Qi is the point-at-infinity, the code shall be deleted and the + * user should be notified to provision a new code" + */ + qi = dpp_derive_qi(dpp->curve, dpp->pkex_key, dpp->pkex_id, + netdev_get_address(dpp->netdev)); + if (!qi || l_ecc_point_is_infinity(qi)) { + l_debug("Cannot derive Qi, provision a new code"); + goto failed; + } + + dpp->pkex_m = l_ecc_point_new(dpp->curve); + + if (!l_ecc_point_add(dpp->pkex_m, dpp->pkex_public, qi)) + goto failed; + + dpp_property_changed_notify(dpp); + + dpp->freqs = dpp_default_freqs(dpp, &dpp->freqs_len); + if (!dpp->freqs) + goto failed; + + dpp->current_freq = dpp->freqs[dpp->freqs_idx]; + + dpp_reset_protocol_timer(dpp, DPP_PKEX_PROTO_TIMEOUT); + + l_debug("PKEX start enrollee (id=%s)", dpp->pkex_id ?: "unset"); + + dpp_start_offchannel(dpp, dpp->current_freq); + + return true; + +failed: dpp_reset(dpp); + return false; +} + +static bool dpp_parse_pkex_args(struct l_dbus_message *message, + const char **key_out, + const char **id_out) +{ + struct l_dbus_message_iter iter; + struct l_dbus_message_iter variant; + const char *dict_key; + const char *key = NULL; + const char *id = NULL; + + if (!l_dbus_message_get_arguments(message, "a{sv}", &iter)) + return false; + + while (l_dbus_message_iter_next_entry(&iter, &dict_key, &variant)) { + if (!strcmp(dict_key, "Code")) { + if (!l_dbus_message_iter_get_variant(&variant, "s", + &key)) + return false; + } else if (!strcmp(dict_key, "Identifier")) { + if (!l_dbus_message_iter_get_variant(&variant, "s", + &id)) + return false; + } + } + + if (!key) + return false; + + if (id && strlen(id) > 80) + return false; + + *key_out = key; + *id_out = id; + + return true; +} + +static struct l_dbus_message *dpp_dbus_pkex_start_enrollee(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + struct dpp_sm *dpp = user_data; + const char *key; + const char *id; + struct station *station = station_find(netdev_get_ifindex(dpp->netdev)); + + l_debug(""); + + if (dpp->state != DPP_STATE_NOTHING || + dpp->interface != DPP_INTERFACE_UNBOUND) + return dbus_error_busy(message); + + if (station_get_connected_network(station)) + return dbus_error_busy(message); + + if (!dpp_parse_pkex_args(message, &key, &id)) + goto invalid_args; + + if (!dpp_start_pkex_enrollee(dpp, key, id)) + goto invalid_args; return l_dbus_message_new_method_return(message); + +invalid_args: + return dbus_error_invalid_args(message); } static void dpp_setup_interface(struct l_dbus_interface *interface) @@ -2779,10 +3539,37 @@ static void dpp_setup_interface(struct l_dbus_interface *interface) l_dbus_interface_property(interface, "URI", 0, "s", dpp_get_uri, NULL); } +static struct l_dbus_message *dpp_dbus_pkex_stop(struct l_dbus *dbus, + struct l_dbus_message *message, void *user_data) +{ + struct dpp_sm *dpp = user_data; + + if (dpp->interface != DPP_INTERFACE_PKEX) + return dbus_error_not_found(message); + + return l_dbus_message_new_method_return(message); +} + +static void dpp_setup_pkex_interface(struct l_dbus_interface *interface) +{ + l_dbus_interface_method(interface, "StartEnrollee", 0, + dpp_dbus_pkex_start_enrollee, "", "a{sv}", "args"); + l_dbus_interface_method(interface, "Stop", 0, + dpp_dbus_pkex_stop, "", ""); + + l_dbus_interface_property(interface, "Started", 0, "b", + dpp_pkex_get_started, NULL); + l_dbus_interface_property(interface, "Role", 0, "s", + dpp_pkex_get_role, NULL); +} + static void dpp_destroy_interface(void *user_data) { struct dpp_sm *dpp = user_data; + if (--dpp->refcount) + return; + l_queue_remove(dpp_list, dpp); dpp_free(dpp); @@ -2801,6 +3588,9 @@ static int dpp_init(void) l_dbus_register_interface(dbus_get_bus(), IWD_DPP_INTERFACE, dpp_setup_interface, dpp_destroy_interface, false); + l_dbus_register_interface(dbus_get_bus(), IWD_DPP_PKEX_INTERFACE, + dpp_setup_pkex_interface, + dpp_destroy_interface, false); mlme_watch = l_genl_family_register(nl80211, "mlme", dpp_mlme_notify, NULL, NULL); @@ -2820,6 +3610,7 @@ static void dpp_exit(void) l_debug(""); l_dbus_unregister_interface(dbus_get_bus(), IWD_DPP_INTERFACE); + l_dbus_unregister_interface(dbus_get_bus(), IWD_DPP_PKEX_INTERFACE); netdev_watch_remove(netdev_watch);