diff --git a/autotests/testEncryptedProfiles/connection_test.py b/autotests/testEncryptedProfiles/connection_test.py index eb7fd7c5..17c5ff11 100644 --- a/autotests/testEncryptedProfiles/connection_test.py +++ b/autotests/testEncryptedProfiles/connection_test.py @@ -21,6 +21,17 @@ def profile_is_encrypted(self, profile): return True + def profile_has_psk_secret(self, profile): + with open('/tmp/iwd/' + profile) as f: + contents = f.read() + + return any(secret in contents for secret in [ + 'Passphrase', + 'PreSharedKey', + 'SAE-PT-Group19', + 'SAE-PT-Group20', + ]) + def validate(self, wd): devices = wd.list_devices(1) device = devices[0] @@ -80,6 +91,22 @@ def test_agent_profile(self): wd.unregister_psk_agent(psk_agent) + # Tests that an agent can request one-time use of a passphrase. + def test_agent_transient_profile(self): + wd = IWD(True) + + psk_agent = PSKAgent("secret123", store=False) + wd.register_psk_agent(psk_agent) + + with self.assertRaises(FileNotFoundError): + self.profile_has_psk_secret('ssidCCMP.psk') + + self.validate(wd) + + self.assertFalse(self.profile_has_psk_secret('ssidCCMP.psk')) + + wd.unregister_psk_agent(psk_agent) + # Tests that an invalid profile gets re-written after an agent request def test_invalid_profile_rewritten(self): bad_config = '[Security]\nPassphrase=incorrect\n' diff --git a/autotests/util/iwd.py b/autotests/util/iwd.py index 37eb4943..54096b01 100755 --- a/autotests/util/iwd.py +++ b/autotests/util/iwd.py @@ -1153,7 +1153,7 @@ def __str__(self): class PSKAgent(dbus.service.Object): - def __init__(self, passphrases=[], users=[], namespace=ctx): + def __init__(self, passphrases=[], users=[], namespace=ctx, store=True): global agent_count if type(passphrases) != list: @@ -1162,6 +1162,7 @@ def __init__(self, passphrases=[], users=[], namespace=ctx): if type(users) != list: users = [users] self.users = users + self.store = store self._path = '/test/agent/%s' % agent_count self._bus = dbus.bus.BusConnection(address_or_type=namespace.dbus_address) @@ -1193,6 +1194,17 @@ def RequestPassphrase(self, path): return self.passphrases.pop(0) + @dbus.service.method(IWD_AGENT_INTERFACE, in_signature='o', + out_signature='sa{sv}') + def RequestPassphraseWithOptions(self, path): + print('Requested PSK with options for ' + path) + + if not self.passphrases: + raise CanceledEx("canceled") + + return (self.passphrases.pop(0), + {'Store': dbus.Boolean(self.store, variant_level=1)}) + @dbus.service.method(IWD_AGENT_INTERFACE, in_signature='o', out_signature='s') def RequestPrivateKeyPassphrase(self, path): diff --git a/doc/agent-api.txt b/doc/agent-api.txt index e9bb95ca..77e9bc6d 100644 --- a/doc/agent-api.txt +++ b/doc/agent-api.txt @@ -90,6 +90,18 @@ Methods void Release() [noreply] Possible Errors: net.connman.iwd.Agent.Error.Canceled + (string, dict) RequestPassphraseWithOptions(object network) + + This method gets called when trying to connect to + a network and passphrase is required. Agents + implementing this method can return options for the + passphrase. If the Store boolean option is present + and false, IWD will use the passphrase for this + connection attempt but will not save it to the + network profile. + + Possible Errors: net.connman.iwd.Agent.Error.Canceled + string RequestPrivateKeyPassphrase(object network) This method gets called when connecting to diff --git a/src/agent.c b/src/agent.c index 0f718b87..03538db1 100644 --- a/src/agent.c +++ b/src/agent.c @@ -44,11 +44,13 @@ enum agent_request_type { struct agent_request { enum agent_request_type type; struct l_dbus_message *message; + char *path; unsigned int id; void *user_data; void *user_callback; struct l_dbus_message *trigger; agent_request_destroy_func_t destroy; + bool passphrase_with_options; }; struct agent { @@ -126,6 +128,7 @@ static void agent_request_free(void *user_data) if (request->destroy) request->destroy(request->user_data); + l_free(request->path); l_free(request); } @@ -134,19 +137,37 @@ static void passphrase_reply(struct l_dbus_message *reply, { const char *error, *text; char *passphrase = NULL; + bool store = true; enum agent_result result = AGENT_RESULT_FAILED; agent_request_passphrase_func_t user_callback = request->user_callback; if (l_dbus_message_get_error(reply, &error, &text)) goto done; - if (!l_dbus_message_get_arguments(reply, "s", &passphrase)) + if (request->passphrase_with_options) { + struct l_dbus_message_iter options; + struct l_dbus_message_iter value; + const char *key; + + if (!l_dbus_message_get_arguments(reply, "sa{sv}", + &passphrase, &options)) + goto done; + + while (l_dbus_message_iter_next_entry(&options, &key, &value)) { + if (!strcmp(key, "Store")) { + if (!l_dbus_message_iter_get_variant(&value, + "b", &store)) + goto done; + } + } + } else if (!l_dbus_message_get_arguments(reply, "s", &passphrase)) goto done; result = AGENT_RESULT_OK; done: - user_callback(result, passphrase, request->trigger, request->user_data); + user_callback(result, passphrase, store, request->trigger, + request->user_data); } static void user_name_passwd_reply(struct l_dbus_message *reply, @@ -224,6 +245,57 @@ static void agent_free(void *data) } static void agent_send_next_request(struct agent *agent); +static void agent_receive_reply(struct l_dbus_message *message, + void *user_data); + +static struct l_dbus_message *agent_new_passphrase_message(struct agent *agent, + const char *path, bool with_options) +{ + struct l_dbus_message *message; + + message = l_dbus_message_new_method_call(dbus_get_bus(), + agent->owner, + agent->path, + IWD_AGENT_INTERFACE, + with_options ? + "RequestPassphraseWithOptions" : + "RequestPassphrase"); + + l_dbus_message_set_arguments(message, "o", path); + + return message; +} + +static bool agent_retry_passphrase_without_options(struct agent *agent, + struct agent_request *request, + struct l_dbus_message *reply) +{ + const char *error, *text; + + if (request->type != AGENT_REQUEST_TYPE_PASSPHRASE || + !request->passphrase_with_options) + return false; + + if (!l_dbus_message_get_error(reply, &error, &text)) + return false; + + if (strcmp(error, "org.freedesktop.DBus.Error.UnknownMethod") && + strcmp(error, "org.freedesktop.DBus.Error.UnknownInterface")) + return false; + + request->passphrase_with_options = false; + request->message = agent_new_passphrase_message(agent, request->path, + false); + + agent->pending_id = l_dbus_send_with_reply(dbus_get_bus(), + request->message, + agent_receive_reply, + agent, NULL); + + request->message = NULL; + + return true; +} static void request_timeout(struct l_timeout *timeout, void *user_data) { @@ -242,11 +314,17 @@ static void agent_receive_reply(struct l_dbus_message *message, void *user_data) { struct agent *agent = user_data; + struct agent_request *pending; l_debug("agent %p request id %u", agent, agent->pending_id); agent->pending_id = 0; + pending = l_queue_peek_head(agent->requests); + if (pending && agent_retry_passphrase_without_options(agent, pending, + message)) + return; + agent_finalize_pending(agent, message); if (!agent->pending_id) @@ -281,7 +359,9 @@ static unsigned int agent_queue_request(struct agent *agent, int timeout, void *callback, struct l_dbus_message *trigger, void *user_data, - agent_request_destroy_func_t destroy) + agent_request_destroy_func_t destroy, + const char *path, + bool passphrase_with_options) { struct agent_request *request; @@ -289,11 +369,13 @@ static unsigned int agent_queue_request(struct agent *agent, request->type = type; request->message = message; + request->path = l_strdup(path); request->id = ++next_request_id; request->user_data = user_data; request->user_callback = callback; request->trigger = l_dbus_message_ref(trigger); request->destroy = destroy; + request->passphrase_with_options = passphrase_with_options; agent->timeout_secs = timeout; @@ -365,17 +447,12 @@ unsigned int agent_request_passphrase(const char *path, l_debug("agent %p owner %s path %s", agent, agent->owner, agent->path); - message = l_dbus_message_new_method_call(dbus_get_bus(), - agent->owner, - agent->path, - IWD_AGENT_INTERFACE, - "RequestPassphrase"); - - l_dbus_message_set_arguments(message, "o", path); + message = agent_new_passphrase_message(agent, path, true); return agent_queue_request(agent, AGENT_REQUEST_TYPE_PASSPHRASE, message, agent_timeout_input_request(), - callback, trigger, user_data, destroy); + callback, trigger, user_data, destroy, + path, true); } unsigned int agent_request_pkey_passphrase(const char *path, @@ -401,7 +478,8 @@ unsigned int agent_request_pkey_passphrase(const char *path, return agent_queue_request(agent, AGENT_REQUEST_TYPE_PASSPHRASE, message, agent_timeout_input_request(), - callback, trigger, user_data, destroy); + callback, trigger, user_data, destroy, + NULL, false); } unsigned int agent_request_user_name_password(const char *path, @@ -427,7 +505,8 @@ unsigned int agent_request_user_name_password(const char *path, return agent_queue_request(agent, AGENT_REQUEST_TYPE_USER_NAME_PASSWD, message, agent_timeout_input_request(), - callback, trigger, user_data, destroy); + callback, trigger, user_data, destroy, + NULL, false); } unsigned int agent_request_user_password(const char *path, const char *user, @@ -452,7 +531,8 @@ unsigned int agent_request_user_password(const char *path, const char *user, return agent_queue_request(agent, AGENT_REQUEST_TYPE_PASSPHRASE, message, agent_timeout_input_request(), - callback, trigger, user_data, destroy); + callback, trigger, user_data, destroy, + NULL, false); } static bool find_request(const void *a, const void *b) diff --git a/src/agent.h b/src/agent.h index eb08c6b6..220339db 100644 --- a/src/agent.h +++ b/src/agent.h @@ -29,6 +29,7 @@ enum agent_result { typedef void (*agent_request_passphrase_func_t) (enum agent_result result, const char *passphrase, + bool store, struct l_dbus_message *message, void *user_data); typedef void (*agent_request_user_name_passwd_func_t) (enum agent_result result, diff --git a/src/network.c b/src/network.c index a5a2375a..1d14c04c 100644 --- a/src/network.c +++ b/src/network.c @@ -84,6 +84,7 @@ struct network { char **nai_realms; uint8_t *rc_ie; bool sync_settings:1; /* should settings be synced on connect? */ + bool agent_passphrase_transient:1; bool ask_passphrase:1; /* Whether we should force-ask agent */ bool is_hs20:1; bool anqp_pending:1; /* Set if there is a pending ANQP request */ @@ -324,6 +325,7 @@ static bool __network_set_passphrase(struct network *network, network_reset_passphrase(network); network->passphrase = l_strdup(passphrase); + network->agent_passphrase_transient = false; network->sae_pt_19 = network_generate_sae_pt(network, 19); network->sae_pt_20 = network_generate_sae_pt(network, 20); @@ -664,6 +666,7 @@ static int network_load_psk(struct network *network, struct scan_bss *bss) network_reset_passphrase(network); network_reset_psk(network); + network->agent_passphrase_transient = false; network->passphrase = l_steal_ptr(passphrase); network->password_identifier = l_steal_ptr(password_id); @@ -727,6 +730,9 @@ static void network_settings_save(struct network *network, /* We only update the [Security] bits here, wipe the group first */ l_settings_remove_group(settings, "Security"); + if (network->agent_passphrase_transient) + return; + if (network->psk) l_settings_set_bytes(settings, "Security", "PreSharedKey", network->psk, 32); @@ -1316,6 +1322,7 @@ struct scan_bss *network_bss_select(struct network *network, static void passphrase_callback(enum agent_result result, const char *passphrase, + bool store, struct l_dbus_message *message, void *user_data) { @@ -1355,6 +1362,8 @@ static void passphrase_callback(enum agent_result result, goto err; } + network->agent_passphrase_transient = !store; + station_connect_network(station, network, bss, message); l_dbus_message_unref(message); return; @@ -1424,11 +1433,14 @@ static bool eap_secret_info_match_local(const void *a, const void *b) } static void eap_password_callback(enum agent_result result, const char *value, + bool store, struct l_dbus_message *message, void *user_data) { struct eap_secret_request *req = user_data; + (void) store; + req->network->agent_request = 0; if (value) {