diff options
-rw-r--r-- | src/creds/creds.c | 180 | ||||
-rw-r--r-- | src/shared/meson.build | 1 | ||||
-rw-r--r-- | src/shared/varlink-io.systemd.Credentials.c | 34 | ||||
-rw-r--r-- | src/shared/varlink-io.systemd.Credentials.h | 6 | ||||
-rw-r--r-- | src/test/test-varlink-idl.c | 3 | ||||
-rw-r--r-- | units/meson.build | 5 | ||||
-rw-r--r-- | units/systemd-creds.socket | 23 | ||||
-rw-r--r-- | units/systemd-creds@.service | 19 |
8 files changed, 271 insertions, 0 deletions
diff --git a/src/creds/creds.c b/src/creds/creds.c index 10d117118f..c9bf2e1e36 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -24,6 +24,9 @@ #include "terminal-util.h" #include "tpm2-pcr.h" #include "tpm2-util.h" +#include "user-util.h" +#include "varlink.h" +#include "varlink-io.systemd.Credentials.h" #include "verbs.h" typedef enum TranscodeMode { @@ -54,6 +57,7 @@ static usec_t arg_timestamp = USEC_INFINITY; static usec_t arg_not_after = USEC_INFINITY; static bool arg_pretty = false; static bool arg_quiet = false; +static bool arg_varlink = false; STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep); @@ -933,6 +937,11 @@ static int parse_argv(int argc, char *argv[]) { if (arg_tpm2_public_key_pcr_mask == UINT32_MAX) arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_BOOT; + r = varlink_invocation(VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + arg_varlink = r; + return 1; } @@ -952,6 +961,150 @@ static int creds_main(int argc, char *argv[]) { return dispatch_verb(argc, argv, verbs, NULL); } +typedef struct MethodEncryptParameters { + const char *name; + const char *text; + struct iovec data; + uint64_t timestamp; + uint64_t not_after; +} MethodEncryptParameters; + +static void method_encrypt_parameters_done(MethodEncryptParameters *p) { + assert(p); + + iovec_done_erase(&p->data); +} + +static int vl_method_encrypt(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + + static const JsonDispatch dispatch_table[] = { + { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodEncryptParameters, name), 0 }, + { "text", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodEncryptParameters, text), 0 }, + { "data", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodEncryptParameters, data), 0 }, + { "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodEncryptParameters, timestamp), 0 }, + { "notAfter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodEncryptParameters, not_after), 0 }, + {} + }; + _cleanup_(method_encrypt_parameters_done) MethodEncryptParameters p = { + .timestamp = UINT64_MAX, + .not_after = UINT64_MAX, + }; + _cleanup_(iovec_done) struct iovec output = {}; + int r; + + assert(link); + + json_variant_sensitive(parameters); + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.name && !credential_name_valid(p.name)) + return varlink_error_invalid_parameter_name(link, "name"); + /* Specifying both or neither the text string and the binary data is not allowed */ + if (!!p.text == !!p.data.iov_base) + return varlink_error_invalid_parameter_name(link, "data"); + if (p.timestamp == UINT64_MAX) + p.timestamp = now(CLOCK_REALTIME); + if (p.not_after != UINT64_MAX && p.not_after < p.timestamp) + return varlink_error_invalid_parameter_name(link, "notAfter"); + + r = encrypt_credential_and_warn( + arg_with_key, + p.name, + p.timestamp, + p.not_after, + arg_tpm2_device, + arg_tpm2_pcr_mask, + arg_tpm2_public_key, + arg_tpm2_public_key_pcr_mask, + p.text ?: p.data.iov_base, p.text ? strlen(p.text) : p.data.iov_len, + &output.iov_base, &output.iov_len); + if (r < 0) + return r; + + _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL; + + r = json_build(&reply, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_IOVEC_BASE64("blob", &output))); + if (r < 0) + return r; + + /* Let's also mark the (theoretically encrypted) reply as sensitive, in case the NULL encryption scheme was used. */ + json_variant_sensitive(reply); + + return varlink_reply(link, reply); +} + +typedef struct MethodDecryptParameters { + const char *name; + struct iovec blob; + uint64_t timestamp; +} MethodDecryptParameters; + +static void method_decrypt_parameters_done(MethodDecryptParameters *p) { + assert(p); + + iovec_done_erase(&p->blob); +} + +static int vl_method_decrypt(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + + static const JsonDispatch dispatch_table[] = { + { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodDecryptParameters, name), 0 }, + { "blob", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodDecryptParameters, blob), 0 }, + { "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodDecryptParameters, timestamp), 0 }, + {} + }; + _cleanup_(method_decrypt_parameters_done) MethodDecryptParameters p = { + .timestamp = UINT64_MAX, + }; + _cleanup_(iovec_done_erase) struct iovec output = {}; + int r; + + assert(link); + + /* Let's also mark the (theoretically encrypted) input as sensitive, in case the NULL encryption scheme was used. */ + json_variant_sensitive(parameters); + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.name && !credential_name_valid(p.name)) + return varlink_error_invalid_parameter_name(link, "name"); + if (!p.blob.iov_base) + return varlink_error_invalid_parameter_name(link, "blob"); + if (p.timestamp == UINT64_MAX) + p.timestamp = now(CLOCK_REALTIME); + + r = decrypt_credential_and_warn( + p.name, + p.timestamp, + arg_tpm2_device, + arg_tpm2_signature, + p.blob.iov_base, p.blob.iov_len, + &output.iov_base, &output.iov_len); + if (r == -EBADMSG) + return varlink_error(link, "io.systemd.Credentials.BadFormat", NULL); + if (r == -EREMOTE) + return varlink_error(link, "io.systemd.Credentials.NameMismatch", NULL); + if (r == -ESTALE) + return varlink_error(link, "io.systemd.Credentials.TimeMismatch", NULL); + if (r < 0) + return r; + + _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL; + + r = json_build(&reply, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_IOVEC_BASE64("data", &output))); + if (r < 0) + return r; + + json_variant_sensitive(reply); + + return varlink_reply(link, reply); +} + static int run(int argc, char *argv[]) { int r; @@ -961,6 +1114,33 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_varlink) { + _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL; + + /* Invocation as Varlink service */ + + r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY|VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_Credentials); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = varlink_server_bind_method_many( + varlink_server, + "io.systemd.Credentials.Encrypt", vl_method_encrypt, + "io.systemd.Credentials.Decrypt", vl_method_decrypt); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); + + r = varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; + } + return creds_main(argc, argv); } diff --git a/src/shared/meson.build b/src/shared/meson.build index b24a541de5..1ff2a2d995 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -172,6 +172,7 @@ shared_sources = files( 'varlink.c', 'varlink-idl.c', 'varlink-io.systemd.c', + 'varlink-io.systemd.Credentials.c', 'varlink-io.systemd.Journal.c', 'varlink-io.systemd.ManagedOOM.c', 'varlink-io.systemd.PCRExtend.c', diff --git a/src/shared/varlink-io.systemd.Credentials.c b/src/shared/varlink-io.systemd.Credentials.c new file mode 100644 index 0000000000..b827337eed --- /dev/null +++ b/src/shared/varlink-io.systemd.Credentials.c @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Credentials.h" + +static VARLINK_DEFINE_METHOD( + Encrypt, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(text, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(data, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(timestamp, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(notAfter, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(blob, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_METHOD( + Decrypt, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(blob, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(timestamp, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(data, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_ERROR(BadFormat); +static VARLINK_DEFINE_ERROR(NameMismatch); +static VARLINK_DEFINE_ERROR(TimeMismatch); + +VARLINK_DEFINE_INTERFACE( + io_systemd_Credentials, + "io.systemd.Credentials", + &vl_method_Encrypt, + &vl_method_Decrypt, + &vl_error_BadFormat, + &vl_error_NameMismatch, + &vl_error_TimeMismatch); diff --git a/src/shared/varlink-io.systemd.Credentials.h b/src/shared/varlink-io.systemd.Credentials.h new file mode 100644 index 0000000000..c0ecc3d840 --- /dev/null +++ b/src/shared/varlink-io.systemd.Credentials.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_Credentials; diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index cbdb9c64fb..6aa1fa6b1d 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -8,6 +8,7 @@ #include "varlink.h" #include "varlink-idl.h" #include "varlink-io.systemd.h" +#include "varlink-io.systemd.Credentials.h" #include "varlink-io.systemd.Journal.h" #include "varlink-io.systemd.ManagedOOM.h" #include "varlink-io.systemd.PCRExtend.h" @@ -143,6 +144,8 @@ TEST(parse_format) { print_separator(); test_parse_format_one(&vl_interface_io_systemd_sysext); print_separator(); + test_parse_format_one(&vl_interface_io_systemd_Credentials); + print_separator(); test_parse_format_one(&vl_interface_xyz_test); } diff --git a/units/meson.build b/units/meson.build index 8542245239..9d3604951d 100644 --- a/units/meson.build +++ b/units/meson.build @@ -280,6 +280,11 @@ units = [ 'file' : 'systemd-coredump@.service.in', 'conditions' : ['ENABLE_COREDUMP'], }, + { + 'file' : 'systemd-creds.socket', + 'symlinks' : ['sockets.target.wants/'], + }, + { 'file' : 'systemd-creds@.service' }, { 'file' : 'systemd-exit.service' }, { 'file' : 'systemd-firstboot.service', diff --git a/units/systemd-creds.socket b/units/systemd-creds.socket new file mode 100644 index 0000000000..794fac19a8 --- /dev/null +++ b/units/systemd-creds.socket @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Credential Encryption/Decryption (Varlink) +Documentation=man:systemd-creds(1) +DefaultDependencies=no +Before=sockets.target + +[Socket] +ListenStream=/run/systemd/io.systemd.Credentials +FileDescriptorName=varlink +SocketMode=0600 +Accept=yes + +[Install] +WantedBy=sockets.target diff --git a/units/systemd-creds@.service b/units/systemd-creds@.service new file mode 100644 index 0000000000..37cdd319f6 --- /dev/null +++ b/units/systemd-creds@.service @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Credential Encryption/Decryption (Varlink) +Documentation=man:systemd-creds(1) +DefaultDependencies=no +Conflicts=shutdown.target initrd-switch-root.target +Before=shutdown.target initrd-switch-root.target + +[Service] +Environment=LISTEN_FDNAMES=varlink +ExecStart=-systemd-creds |