out h send_fd);
@org.freedesktop.systemd1.Privileged("true")
ReleaseHome(in s user_name);
+ ListSigningKeys(out a(sst) keys);
+ GetSigningKey(in s name,
+ out s der,
+ out t flags);
+ AddSigningKey(in s name,
+ in s pem,
+ in t flags);
+ RemoveSigningKey(in s name,
+ in t flags);
@org.freedesktop.systemd1.Privileged("true")
LockAllHomes();
@org.freedesktop.systemd1.Privileged("true")
<variablelist class="dbus-method" generated="True" extra-ref="ReleaseHome()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="ListSigningKeys()"/>
+
+ <variablelist class="dbus-method" generated="True" extra-ref="GetSigningKey()"/>
+
+ <variablelist class="dbus-method" generated="True" extra-ref="AddSigningKey()"/>
+
+ <variablelist class="dbus-method" generated="True" extra-ref="RemoveSigningKey()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="LockAllHomes()"/>
<variablelist class="dbus-method" generated="True" extra-ref="DeactivateAllHomes()"/>
<para><function>Rebalance()</function> synchronously rebalances free disk space between home
areas. This only executes an operation if at least one home area using the LUKS2 backend is active and
has rebalancing enabled, and is otherwise a NOP.</para>
+
+ <para><function>ListSigningKeys()</function> acquires a list of installed home area signing
+ keys. Returns an array of key names with their PEM encoded public key data. Each entry also comes with
+ a flags value which is currently unused and should be ignored by clients.</para>
+
+ <para><function>GetSigningKey()</function> acquires the PEM encoded public part of the specified home
+ area signing key of the specified name. Also returns a currently unused flags value that should be
+ ignored. The <varname>flags</varname> parameter must be set to zero, currently.</para>
+
+ <para><function>AddSigningKey()</function> adds a new key to the list of home area signing keys. Takes
+ a name string (free-form, suitable as filename, with suffix <literal>.public</literal>), the PEM
+ encoded public key data and a currently unused flags value that must be zero. The
+ <varname>flags</varname> parameter must be set to zero, currently.</para>
+
+ <para><function>RemoveSigningKey()</function> removes a key from the list of home area signing
+ keys. Takes the name of the key to remove and a currently unused flags value that must be zero. The
+ <varname>flags</varname> parameter must be set to zero, currently.</para>
</refsect2>
<refsect2>
<title>The Manager Object</title>
<para><function>ActivateHomeIfReferenced()</function>, <function>RefHomeUnrestricted()</function>,
<function>CreateHomeEx()</function>, and <function>UpdateHomeEx()</function> were added in version 256.</para>
+ <para><function>ListSigningKeys()</function>, <function>GetSigningKey()</function>,
+ <function>AddSigningKey()</function>, and <function>RemoveSigningKey()</function> were added in version
+ 258.</para>
</refsect2>
<refsect2>
<title>Home Objects</title>
#include "bus-common-errors.h"
#include "bus-message-util.h"
#include "bus-polkit.h"
+#include "fileio.h"
#include "format-util.h"
#include "home-util.h"
#include "homed-bus.h"
#include "homed-home-bus.h"
#include "homed-manager-bus.h"
#include "homed-manager.h"
+#include "openssl-util.h"
+#include "path-util.h"
#include "strv.h"
#include "user-record-sign.h"
#include "user-record-util.h"
return 1;
}
+static int method_list_signing_keys(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "(sst)");
+ if (r < 0)
+ return r;
+
+ /* Add our own key pair first */
+ r = manager_acquire_key_pair(m);
+ if (r < 0)
+ return r;
+
+ _cleanup_free_ char *pem = NULL;
+ r = openssl_pubkey_to_pem(m->private_key, &pem);
+ if (r < 0)
+ return log_error_errno(r, "Failed to convert public key to PEM: %m");
+
+ r = sd_bus_message_append(
+ reply,
+ "(sst)",
+ "local.public",
+ pem,
+ UINT64_C(0));
+ if (r < 0)
+ return r;
+
+ /* And then all public keys we recognize */
+ EVP_PKEY *pkey;
+ const char *fn;
+ HASHMAP_FOREACH_KEY(pkey, fn, m->public_keys) {
+ pem = mfree(pem);
+ r = openssl_pubkey_to_pem(pkey, &pem);
+ if (r < 0)
+ return log_error_errno(r, "Failed to convert public key to PEM: %m");
+
+ r = sd_bus_message_append(
+ reply,
+ "(sst)",
+ fn,
+ pem,
+ UINT64_C(0));
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(/* bus= */ NULL, reply, /* ret_cookie= */ NULL);
+}
+
+static int method_get_signing_key(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ const char *fn;
+ r = sd_bus_message_read(message, "s", &fn);
+ if (r < 0)
+ return r;
+
+ /* Make sure the local key is loaded. */
+ r = manager_acquire_key_pair(m);
+ if (r < 0)
+ return r;
+
+ EVP_PKEY *pkey;
+
+ if (streq(fn, "local.public"))
+ pkey = m->private_key;
+ else
+ pkey = hashmap_get(m->public_keys, fn);
+ if (!pkey)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_KEY, "No key with name: %s", fn);
+
+ _cleanup_free_ char *pem = NULL;
+ r = openssl_pubkey_to_pem(pkey, &pem);
+ if (r < 0)
+ return log_error_errno(r, "Failed to convert public key to PEM: %m");
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(
+ reply,
+ "st",
+ pem,
+ UINT64_C(0));
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(/* bus= */ NULL, reply, /* ret_cookie= */ NULL);
+}
+
+static bool valid_public_key_name(const char *fn) {
+ assert(fn);
+
+ /* Checks if the specified name is valid to export, i.e. is a filename, ends in ".public". */
+
+ if (!filename_is_valid(fn))
+ return false;
+
+ const char *e = endswith(fn, ".public");
+ if (!e)
+ return false;
+
+ return e != fn;
+}
+
+static bool manager_has_public_key(Manager *m, EVP_PKEY *needle) {
+ int r;
+
+ assert(m);
+
+ EVP_PKEY *pkey;
+ HASHMAP_FOREACH(pkey, m->public_keys) {
+ r = EVP_PKEY_eq(pkey, needle);
+ if (r > 0)
+ return true;
+
+ /* EVP_PKEY_eq() returns -1 and -2 too under some conditions, which we'll all treat as "not the same" */
+ }
+
+ r = EVP_PKEY_eq(m->private_key, needle);
+ if (r > 0)
+ return true;
+
+ return false;
+}
+
+static int method_add_signing_key(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ const char *fn, *pem;
+ uint64_t flags;
+ r = sd_bus_message_read(message, "sst", &fn, &pem, &flags);
+ if (r < 0)
+ return r;
+
+ if (flags != 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags parameter must be zero.");
+ if (!valid_public_key_name(fn))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Public key name not valid: %s", fn);
+ if (streq(fn, "local.public"))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Refusing to write local public key.");
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
+ r = openssl_pubkey_from_pem(pem, /* pem_size= */ SIZE_MAX, &pkey);
+ if (r == -EIO)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Public key invalid: %s", fn);
+ if (r < 0)
+ return r;
+
+ if (hashmap_contains(m->public_keys, fn))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Public key name already exists: %s", fn);
+
+ /* Make sure the local key is loaded before can detect conflicts */
+ r = manager_acquire_key_pair(m);
+ if (r < 0)
+ return r;
+
+ if (manager_has_public_key(m, pkey))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Public key already exists: %s", fn);
+
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.home1.manage-signing-keys",
+ /* details= */ NULL,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ _cleanup_free_ char *pem_reformatted = NULL;
+ r = openssl_pubkey_to_pem(pkey, &pem_reformatted);
+ if (r < 0)
+ return log_error_errno(r, "Failed to convert public key to PEM: %m");
+
+ _cleanup_free_ char *fn_copy = strdup(fn);
+ if (!fn)
+ return log_oom();
+
+ _cleanup_free_ char *p = path_join("/var/lib/systemd/home/", fn);
+ if (!p)
+ return log_oom();
+
+ r = write_string_file(p, pem_reformatted, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_MODE_0444);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write public key PEM to '%s': %m", p);
+
+ r = hashmap_ensure_put(&m->public_keys, &public_key_hash_ops, fn_copy, pkey);
+ if (r < 0) {
+ (void) unlink(p);
+ return log_error_errno(r, "Failed to add public key to set: %m");
+ }
+
+ TAKE_PTR(fn_copy);
+ TAKE_PTR(pkey);
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_remove_signing_key(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ const char *fn;
+ uint64_t flags;
+ r = sd_bus_message_read(message, "st", &fn, &flags);
+ if (r < 0)
+ return r;
+
+ if (flags != 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags parameter must be zero.");
+
+ if (!valid_public_key_name(fn))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Public key name not valid: %s", fn);
+
+ if (streq(fn, "local.public"))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Refusing to remove local key.");
+
+ if (!hashmap_contains(m->public_keys, fn))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Public key name does not exist: %s", fn);
+
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.home1.manage-signing-keys",
+ /* details= */ NULL,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ _cleanup_free_ char *p = path_join("/var/lib/systemd/home/", fn);
+ if (!p)
+ return log_oom();
+
+ if (unlink(p) < 0)
+ return log_error_errno(errno, "Failed to remove '%s': %m", p);
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
+ _cleanup_free_ char *fn_free = NULL;
+ pkey = ASSERT_PTR(hashmap_remove2(m->public_keys, fn, (void**) &fn_free));
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
static const sd_bus_vtable manager_vtable[] = {
SD_BUS_VTABLE_START(0),
method_release_home,
0),
+ SD_BUS_METHOD_WITH_ARGS("ListSigningKeys",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("a(sst)", keys),
+ method_list_signing_keys,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("GetSigningKey",
+ SD_BUS_RESULT("s", name),
+ SD_BUS_RESULT("s", der, "t", flags),
+ method_get_signing_key,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("AddSigningKey",
+ SD_BUS_RESULT("s", name, "s", pem, "t", flags),
+ SD_BUS_NO_RESULT,
+ method_add_signing_key,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("RemoveSigningKey",
+ SD_BUS_RESULT("s", name, "t", flags),
+ SD_BUS_NO_RESULT,
+ method_remove_signing_key,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+
/* An operation that acts on all homes that allow it */
SD_BUS_METHOD("LockAllHomes", NULL, NULL, method_lock_all_homes, 0),
SD_BUS_METHOD("DeactivateAllHomes", NULL, NULL, method_deactivate_all_homes, 0),