mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 00:14:32 +09:00
homed: add apis for managing home signing keys
This makes it easier to actually migrate home directories between systems.
This commit is contained in:
@@ -112,6 +112,15 @@ node /org/freedesktop/home1 {
|
||||
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")
|
||||
@@ -185,6 +194,14 @@ node /org/freedesktop/home1 {
|
||||
|
||||
<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()"/>
|
||||
@@ -426,6 +443,23 @@ node /org/freedesktop/home1 {
|
||||
<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>
|
||||
@@ -599,6 +633,9 @@ node /org/freedesktop/home1/home {
|
||||
<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>
|
||||
|
||||
@@ -6,12 +6,15 @@
|
||||
#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"
|
||||
@@ -753,6 +756,274 @@ static int method_rebalance(sd_bus_message *message, void *userdata, sd_bus_erro
|
||||
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),
|
||||
|
||||
@@ -934,6 +1205,27 @@ static const sd_bus_vtable manager_vtable[] = {
|
||||
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),
|
||||
|
||||
@@ -1446,7 +1446,7 @@ int manager_sign_user_record(Manager *m, UserRecord *u, UserRecord **ret, sd_bus
|
||||
return user_record_sign(u, m->private_key, ret);
|
||||
}
|
||||
|
||||
DEFINE_PRIVATE_HASH_OPS_FULL(public_key_hash_ops, char, string_hash_func, string_compare_func, free, EVP_PKEY, EVP_PKEY_free);
|
||||
DEFINE_HASH_OPS_FULL(public_key_hash_ops, char, string_hash_func, string_compare_func, free, EVP_PKEY, EVP_PKEY_free);
|
||||
|
||||
static int manager_load_public_key_one(Manager *m, const char *path) {
|
||||
_cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
|
||||
@@ -1482,15 +1482,11 @@ static int manager_load_public_key_one(Manager *m, const char *path) {
|
||||
if (st.st_uid != 0 || (st.st_mode & 0022) != 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Public key file %s is writable by more than the root user, refusing.", path);
|
||||
|
||||
r = hashmap_ensure_allocated(&m->public_keys, &public_key_hash_ops);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
pkey = PEM_read_PUBKEY(f, &pkey, NULL, NULL);
|
||||
if (!pkey)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key file %s.", path);
|
||||
|
||||
r = hashmap_put(m->public_keys, fn, pkey);
|
||||
r = hashmap_ensure_put(&m->public_keys, &public_key_hash_ops, fn, pkey);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add public key to set: %m");
|
||||
|
||||
|
||||
@@ -93,3 +93,5 @@ int manager_sign_user_record(Manager *m, UserRecord *u, UserRecord **ret, sd_bus
|
||||
int bus_manager_emit_auto_login_changed(Manager *m);
|
||||
|
||||
int manager_get_home_by_name(Manager *m, const char *user_name, Home **ret);
|
||||
|
||||
extern const struct hash_ops public_key_hash_ops;
|
||||
|
||||
@@ -149,6 +149,22 @@
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="Rebalance"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="ListSigningKeys"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="GetSigningKey"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="RemoveSigningKey"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="AddSigningKey"/>
|
||||
|
||||
<!-- Home object -->
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
|
||||
@@ -88,4 +88,14 @@
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="org.freedesktop.home1.manage-signing-keys">
|
||||
<description gettext-domain="systemd">Manage Home Directory Signing Keys</description>
|
||||
<message gettext-domain="systemd">Authentication is required to manage signing keys for home directories.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
</policyconfig>
|
||||
|
||||
@@ -150,6 +150,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_IN_USE, EADDRINUSE),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_REBALANCE_NOT_NEEDED, EALREADY),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_NOT_REFERENCED, EBADR),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_KEY, ENOKEY),
|
||||
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_NO_UPDATE_CANDIDATE, EALREADY),
|
||||
|
||||
|
||||
@@ -156,6 +156,7 @@
|
||||
#define BUS_ERROR_HOME_IN_USE "org.freedesktop.home1.HomeInUse"
|
||||
#define BUS_ERROR_REBALANCE_NOT_NEEDED "org.freedesktop.home1.RebalanceNotNeeded"
|
||||
#define BUS_ERROR_HOME_NOT_REFERENCED "org.freedesktop.home1.HomeNotReferenced"
|
||||
#define BUS_ERROR_NO_SUCH_KEY "org.freedesktop.home1.NoSuchKey"
|
||||
|
||||
#define BUS_ERROR_NO_UPDATE_CANDIDATE "org.freedesktop.sysupdate1.NoCandidate"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user