]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
creds: add varlink API for encrypting/decrypting credentials
authorLennart Poettering <lennart@poettering.net>
Thu, 23 Nov 2023 21:22:27 +0000 (22:22 +0100)
committerLennart Poettering <lennart@poettering.net>
Thu, 21 Dec 2023 18:19:12 +0000 (19:19 +0100)
src/creds/creds.c
src/shared/meson.build
src/shared/varlink-io.systemd.Credentials.c [new file with mode: 0644]
src/shared/varlink-io.systemd.Credentials.h [new file with mode: 0644]
src/test/test-varlink-idl.c
units/meson.build
units/systemd-creds.socket [new file with mode: 0644]
units/systemd-creds@.service [new file with mode: 0644]

index 10d117118fbca592f2ba761ed85462c108142da7..c9bf2e1e36cee4dba373f5db14977bea47a1a775 100644 (file)
@@ -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);
 }
 
index b24a541de5d0bf64b1d6e09dc9b06549746e5411..1ff2a2d99522ca168d82b49399ed7322892b3772 100644 (file)
@@ -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 (file)
index 0000000..b827337
--- /dev/null
@@ -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 (file)
index 0000000..c0ecc3d
--- /dev/null
@@ -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;
index cbdb9c64fbb35cc764e815a7f85bdf49b3df9684..6aa1fa6b1df679f81b6160996db682a5a2570d58 100644 (file)
@@ -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);
 }
 
index 8542245239ac8bd42d9ed2bdb89e09591d8eeafd..9d3604951d56bf6a1b6d1e450520bdb25759ae6c 100644 (file)
@@ -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 (file)
index 0000000..794fac1
--- /dev/null
@@ -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 (file)
index 0000000..37cdd31
--- /dev/null
@@ -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