]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
openssl-util: Query engine/provider pin via ask-password
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 30 Oct 2024 14:47:58 +0000 (15:47 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Sun, 3 Nov 2024 09:46:14 +0000 (10:46 +0100)
In mkosi, we want to support signing via a hardware token. We already
support this in systemd-repart and systemd-measure. However, if the
hardware token is protected by a pin, the pin is asked as many as 20
times when building an image as the pin is not cached and thus requested
again for every operation.

Let's introduce a custom openssl ui when we use engines and providers
and plug systemd-ask-password into the process. With systemd-ask-password,
the pin can be cached in the kernel keyring, allowing us to reuse it without
querying the user again every time to enter the pin.

We use the private key URI as the keyring identifier so that the cached pin
can be shared across multiple tools.

Note that if the private key is pin protected, openssl will prompt both when
loading the private key using the pkcs11 engine and when actually signing the
roothash. To make sure our custom UI is used when signing the roothash, we have
to also configure it with ENGINE_ctrl() which takes a non-owning pointer to
the UI_METHOD object and its userdata object which we have to keep alive so we
introduce a new AskPasswordUserInterface struct which we use to keep both objects
alive together with the EVP_PKEY object.

Because the AskPasswordRequest struct stores non-owning pointers to its fields,
we change repart to store the private key URI as a global variable again instead
of the EVP_PKEY object so that we can use the private key argument as the keyring
field of the AskPasswordRequest instance without running into lifetime issues.

src/boot/measure.c
src/partition/repart.c
src/shared/openssl-util.c
src/shared/openssl-util.h

index 557e06b094cc6397c11cda805346cb9af2c9ef36..3c409f8bd9ad4706eea063b3b77c0cbf35b24437 100644 (file)
@@ -6,6 +6,7 @@
 #include "sd-json.h"
 
 #include "alloc-util.h"
+#include "ask-password-api.h"
 #include "build.h"
 #include "efi-loader.h"
 #include "fd-util.h"
@@ -803,6 +804,7 @@ static int verb_calculate(int argc, char *argv[], void *userdata) {
 static int verb_sign(int argc, char *argv[], void *userdata) {
         _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
         _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
+        _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
         _cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL;
         _cleanup_(X509_freep) X509 *certificate = NULL;
         size_t n;
@@ -834,54 +836,31 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
 
         /* This must be done before openssl_load_key_from_token() otherwise it will get stuck */
         if (arg_certificate) {
-                _cleanup_(BIO_freep) BIO *cb = NULL;
-                _cleanup_free_ char *crt = NULL;
-
-                r = read_full_file_full(
-                                AT_FDCWD, arg_certificate, UINT64_MAX, SIZE_MAX,
-                                READ_FULL_FILE_CONNECT_SOCKET,
-                                /* bind_name= */ NULL,
-                                &crt, &n);
+                r = openssl_load_x509_certificate(arg_certificate, &certificate);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to read certificate file '%s': %m", arg_certificate);
-
-                cb = BIO_new_mem_buf(crt, n);
-                if (!cb)
-                        return log_oom();
-
-                certificate = PEM_read_bio_X509(cb, NULL, NULL, NULL);
-                if (!certificate)
-                        return log_error_errno(
-                                        SYNTHETIC_ERRNO(EBADMSG),
-                                        "Failed to parse X.509 certificate: %s",
-                                        ERR_error_string(ERR_get_error(), NULL));
+                        return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate);
         }
 
-        if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) {
-                _cleanup_fclose_ FILE *privkeyf = NULL;
-                _cleanup_free_ char *resolved_pkey = NULL;
+        if (arg_private_key) {
+                if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) {
+                        r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key);
+                }
 
-                r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &resolved_pkey);
+                r = openssl_load_private_key(
+                                arg_private_key_source_type,
+                                arg_private_key_source,
+                                arg_private_key,
+                                &(AskPasswordRequest) {
+                                        .id = "measure-private-key-pin",
+                                        .keyring = arg_private_key,
+                                        .credential = "measure.private-key-pin",
+                                },
+                                &privkey,
+                                &ui);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key);
-
-                privkeyf = fopen(resolved_pkey, "re");
-                if (!privkeyf)
-                        return log_error_errno(errno, "Failed to open private key file '%s': %m", resolved_pkey);
-
-                privkey = PEM_read_PrivateKey(privkeyf, NULL, NULL, NULL);
-                if (!privkey)
-                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse private key '%s'.", resolved_pkey);
-        } else if (arg_private_key_source &&
-                   IN_SET(arg_private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER)) {
-                r = openssl_load_key_from_token(
-                                arg_private_key_source_type, arg_private_key_source, arg_private_key, &privkey);
-                if (r < 0)
-                        return log_error_errno(
-                                        r,
-                                        "Failed to load key '%s' from OpenSSL key source %s: %m",
-                                        arg_private_key,
-                                        arg_private_key_source);
+                        return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key);
         }
 
         if (arg_public_key) {
index 8070b02cabda381800e1a9149a4708eba5c44d4a..be5171d5e18e58c7cc3abab4b3fc42e26adab8f0 100644 (file)
@@ -17,6 +17,7 @@
 #include "sd-json.h"
 
 #include "alloc-util.h"
+#include "ask-password-api.h"
 #include "blkid-util.h"
 #include "blockdev-list.h"
 #include "blockdev-util.h"
@@ -149,10 +150,10 @@ static PagerFlags arg_pager_flags = 0;
 static bool arg_legend = true;
 static void *arg_key = NULL;
 static size_t arg_key_size = 0;
-static EVP_PKEY *arg_private_key = NULL;
+static char *arg_private_key = NULL;
 static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE;
 static char *arg_private_key_source = NULL;
-static X509 *arg_certificate = NULL;
+static char *arg_certificate = NULL;
 static char *arg_tpm2_device = NULL;
 static uint32_t arg_tpm2_seal_key_handle = 0;
 static char *arg_tpm2_device_key = NULL;
@@ -182,9 +183,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep);
-STATIC_DESTRUCTOR_REGISTER(arg_private_key, EVP_PKEY_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep);
-STATIC_DESTRUCTOR_REGISTER(arg_certificate, X509_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device_key, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep);
@@ -429,6 +430,9 @@ typedef struct Context {
         int backing_fd;
 
         bool from_scratch;
+
+        X509 *certificate;
+        EVP_PKEY *private_key;
 } Context;
 
 static const char *empty_mode_table[_EMPTY_MODE_MAX] = {
@@ -709,9 +713,11 @@ static Partition* partition_unlink_and_free(Context *context, Partition *p) {
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(Partition*, partition_free);
 
-static Context* context_new(sd_id128_t seed) {
+static Context* context_new(sd_id128_t seed, X509 *certificate, EVP_PKEY *private_key) {
         Context *context;
 
+        /* Note: This function takes ownership of the certificate and private_key arguments. */
+
         context = new(Context, 1);
         if (!context)
                 return NULL;
@@ -721,6 +727,8 @@ static Context* context_new(sd_id128_t seed) {
                 .end = UINT64_MAX,
                 .total = UINT64_MAX,
                 .seed = seed,
+                .certificate = certificate,
+                .private_key = private_key,
         };
 
         return context;
@@ -755,6 +763,9 @@ static Context* context_free(Context *context) {
         else
                 free(context->node);
 
+        X509_free(context->certificate);
+        EVP_PKEY_free(context->private_key);
+
         return mfree(context);
 }
 
@@ -4979,6 +4990,8 @@ static int partition_format_verity_hash(
 
 static int sign_verity_roothash(
                 const struct iovec *roothash,
+                X509 *certificate,
+                EVP_PKEY *private_key,
                 struct iovec *ret_signature) {
 
 #if HAVE_OPENSSL
@@ -4989,6 +5002,7 @@ static int sign_verity_roothash(
         int sigsz;
 
         assert(roothash);
+        assert(private_key);
         assert(iovec_is_set(roothash));
         assert(ret_signature);
 
@@ -5000,7 +5014,7 @@ static int sign_verity_roothash(
         if (!rb)
                 return log_oom();
 
-        p7 = PKCS7_sign(arg_certificate, arg_private_key, NULL, rb, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY);
+        p7 = PKCS7_sign(certificate, private_key, NULL, rb, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY);
         if (!p7)
                 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to calculate PKCS7 signature: %s",
                                        ERR_error_string(ERR_get_error(), NULL));
@@ -5039,15 +5053,13 @@ static int partition_format_verity_sig(Context *context, Partition *p) {
         assert_se(hp = p->siblings[VERITY_HASH]);
         assert(!hp->dropped);
 
-        assert(arg_certificate);
-
         assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
 
-        r = sign_verity_roothash(&hp->roothash, &sig);
+        r = sign_verity_roothash(&hp->roothash, context->certificate, context->private_key, &sig);
         if (r < 0)
                 return r;
 
-        r = x509_fingerprint(arg_certificate, fp);
+        r = x509_fingerprint(context->certificate, fp);
         if (r < 0)
                 return log_error_errno(r, "Unable to calculate X509 certificate fingerprint: %m");
 
@@ -6040,60 +6052,6 @@ static int context_mkfs(Context *context) {
         return 0;
 }
 
-static int parse_x509_certificate(const char *certificate, size_t certificate_size, X509 **ret) {
-#if HAVE_OPENSSL
-        _cleanup_(X509_freep) X509 *cert = NULL;
-        _cleanup_(BIO_freep) BIO *cb = NULL;
-
-        assert(certificate);
-        assert(certificate_size > 0);
-        assert(ret);
-
-        cb = BIO_new_mem_buf(certificate, certificate_size);
-        if (!cb)
-                return log_oom();
-
-        cert = PEM_read_bio_X509(cb, NULL, NULL, NULL);
-        if (!cert)
-                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate: %s",
-                                       ERR_error_string(ERR_get_error(), NULL));
-
-        if (ret)
-                *ret = TAKE_PTR(cert);
-
-        return 0;
-#else
-        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot parse X509 certificate.");
-#endif
-}
-
-static int parse_private_key(const char *key, size_t key_size, EVP_PKEY **ret) {
-#if HAVE_OPENSSL
-        _cleanup_(BIO_freep) BIO *kb = NULL;
-        _cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL;
-
-        assert(key);
-        assert(key_size > 0);
-        assert(ret);
-
-        kb = BIO_new_mem_buf(key, key_size);
-        if (!kb)
-                return log_oom();
-
-        pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL);
-        if (!pk)
-                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM private key: %s",
-                                       ERR_error_string(ERR_get_error(), NULL));
-
-        if (ret)
-                *ret = TAKE_PTR(pk);
-
-        return 0;
-#else
-        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot parse private key.");
-#endif
-}
-
 static int partition_acquire_uuid(Context *context, Partition *p, sd_id128_t *ret) {
         struct {
                 sd_id128_t type_uuid;
@@ -7898,9 +7856,7 @@ static int help(void) {
         return 0;
 }
 
-static int parse_argv(int argc, char *argv[]) {
-        _cleanup_free_ char *private_key = NULL;
-
+static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY **ret_private_key, OpenSSLAskPasswordUI **ret_ui) {
         enum {
                 ARG_VERSION = 0x100,
                 ARG_NO_PAGER,
@@ -7989,11 +7945,17 @@ static int parse_argv(int argc, char *argv[]) {
                 {}
         };
 
+        _cleanup_(X509_freep) X509 *certificate = NULL;
+        _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
+        _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL;
         bool auto_hash_pcr_values = true, auto_public_key_pcr_mask = true, auto_pcrlock = true;
         int c, r;
 
         assert(argc >= 0);
         assert(argv);
+        assert(ret_certificate);
+        assert(ret_private_key);
+        assert(ret_ui);
 
         while ((c = getopt_long(argc, argv, "hs:SCP", options, NULL)) >= 0)
 
@@ -8153,7 +8115,7 @@ static int parse_argv(int argc, char *argv[]) {
                 }
 
                 case ARG_PRIVATE_KEY: {
-                        r = free_and_strdup_warn(&private_key, optarg);
+                        r = free_and_strdup_warn(&arg_private_key, optarg);
                         if (r < 0)
                                 return r;
                         break;
@@ -8169,20 +8131,7 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_CERTIFICATE: {
-                        _cleanup_free_ char *cert = NULL;
-                        size_t n = 0;
-
-                        r = read_full_file_full(
-                                        AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
-                                        READ_FULL_FILE_CONNECT_SOCKET,
-                                        NULL,
-                                        &cert, &n);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to read certificate file '%s': %m", optarg);
-
-                        X509_free(arg_certificate);
-                        arg_certificate = NULL;
-                        r = parse_x509_certificate(cert, n, &arg_certificate);
+                        r = parse_path_argument(optarg, /*suppress_root=*/ false, &arg_certificate);
                         if (r < 0)
                                 return r;
                         break;
@@ -8518,39 +8467,38 @@ static int parse_argv(int argc, char *argv[]) {
                         *p = gpt_partition_type_override_architecture(*p, arg_architecture);
         }
 
-        if (private_key && arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) {
-                _cleanup_(erase_and_freep) char *k = NULL;
-                size_t n = 0;
-
-                r = read_full_file_full(
-                                AT_FDCWD, private_key, UINT64_MAX, SIZE_MAX,
-                                READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
-                                NULL,
-                                &k, &n);
+        if (arg_certificate) {
+                r = openssl_load_x509_certificate(arg_certificate, &certificate);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to read key file '%s': %m", private_key);
+                        return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate);
+        }
 
-                r = parse_private_key(k, n, &arg_private_key);
-                if (r < 0)
-                        return r;
-        } else if (private_key &&
-                   IN_SET(arg_private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER)) {
-                /* This must happen after parse_x509_certificate() is called above, otherwise
-                 * signing later will get stuck as the parsed private key won't have the
-                 * certificate, so this block cannot be inline in ARG_PRIVATE_KEY. */
-                r = openssl_load_key_from_token(
+        if (arg_private_key) {
+                if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) {
+                        r = parse_path_argument(arg_private_key, /*suppress_root=*/ false, &arg_private_key);
+                        if (r < 0)
+                                return r;
+                }
+
+                r = openssl_load_private_key(
                                 arg_private_key_source_type,
                                 arg_private_key_source,
-                                private_key,
-                                &arg_private_key);
+                                arg_private_key,
+                                &(AskPasswordRequest) {
+                                        .id = "repart-private-key-pin",
+                                        .keyring = arg_private_key,
+                                        .credential = "repart.private-key-pin",
+                                },
+                                &private_key,
+                                &ui);
                 if (r < 0)
-                        return log_error_errno(
-                                        r,
-                                        "Failed to load key '%s' from OpenSSL private key source %s: %m",
-                                        private_key,
-                                        arg_private_key_source);
+                        return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key);
         }
 
+        *ret_certificate = TAKE_PTR(certificate);
+        *ret_private_key = TAKE_PTR(private_key);
+        *ret_ui = TAKE_PTR(ui);
+
         return 1;
 }
 
@@ -8967,6 +8915,9 @@ static int determine_auto_size(Context *c) {
 }
 
 static int run(int argc, char *argv[]) {
+        _cleanup_(X509_freep) X509 *certificate = NULL;
+        _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
+        _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL;
         _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
         _cleanup_(umount_and_freep) char *mounted_dir = NULL;
         _cleanup_(context_freep) Context* context = NULL;
@@ -8975,7 +8926,7 @@ static int run(int argc, char *argv[]) {
 
         log_setup();
 
-        r = parse_argv(argc, argv);
+        r = parse_argv(argc, argv, &certificate, &private_key, &ui);
         if (r <= 0)
                 return r;
 
@@ -9034,10 +8985,13 @@ static int run(int argc, char *argv[]) {
                         return log_oom();
         }
 
-        context = context_new(arg_seed);
+        context = context_new(arg_seed, certificate, private_key);
         if (!context)
                 return log_oom();
 
+        TAKE_PTR(certificate);
+        TAKE_PTR(private_key);
+
         r = context_read_seed(context, arg_root);
         if (r < 0)
                 return r;
index 2ab89c76399a97e3c9ba834ce0fac3e39f6af175..818f4a5f191d92f55435fc0a0d9aef1e0050f780 100644 (file)
@@ -3,12 +3,15 @@
 #include <endian.h>
 
 #include "alloc-util.h"
+#include "ask-password-api.h"
 #include "fd-util.h"
+#include "fileio.h"
 #include "hexdecoct.h"
 #include "memory-util.h"
 #include "openssl-util.h"
 #include "random-util.h"
 #include "string-util.h"
+#include "strv.h"
 
 #if HAVE_OPENSSL
 #  include <openssl/rsa.h>
@@ -1309,10 +1312,15 @@ int pkey_generate_volume_keys(
         }
 }
 
-static int load_key_from_provider(const char *provider, const char *private_key_uri, EVP_PKEY **ret) {
+static int load_key_from_provider(
+                const char *provider,
+                const char *private_key_uri,
+                OpenSSLAskPasswordUI *ui,
+                EVP_PKEY **ret) {
 
         assert(provider);
         assert(private_key_uri);
+        assert(ui);
         assert(ret);
 
 #if OPENSSL_VERSION_MAJOR >= 3
@@ -1325,8 +1333,8 @@ static int load_key_from_provider(const char *provider, const char *private_key_
 
         _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open(
                         private_key_uri,
-                        /* ui_method= */ NULL,
-                        /* ui_data= */ NULL,
+                        ui->method,
+                        &ui->request,
                         /* post_process= */ NULL,
                         /* post_process_data= */ NULL);
         if (!store)
@@ -1348,10 +1356,10 @@ static int load_key_from_provider(const char *provider, const char *private_key_
 #endif
 }
 
-static int load_key_from_engine(const char *engine, const char *private_key_uri, EVP_PKEY **ret) {
-
+static int load_key_from_engine(const char *engine, const char *private_key_uri, OpenSSLAskPasswordUI *ui, EVP_PKEY **ret) {
         assert(engine);
         assert(private_key_uri);
+        assert(ui);
         assert(ret);
 
 #if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
@@ -1363,11 +1371,13 @@ static int load_key_from_engine(const char *engine, const char *private_key_uri,
         if (ENGINE_init(e) == 0)
                 return log_openssl_errors("Failed to initialize signing engine '%s'", engine);
 
-        _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key(
-                        e,
-                        private_key_uri,
-                        /* ui_method= */ NULL,
-                        /* callback_data= */ NULL);
+        if (ENGINE_ctrl(e, ENGINE_CTRL_SET_USER_INTERFACE, /*i=*/ 0, ui->method, /*f=*/ NULL) <= 0)
+                return log_openssl_errors("Failed to set engine user interface");
+
+        if (ENGINE_ctrl(e, ENGINE_CTRL_SET_CALLBACK_DATA, /*i=*/ 0, &ui->request, /*f=*/ NULL) <= 0)
+                return log_openssl_errors("Failed to set engine user interface data");
+
+        _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key(e, private_key_uri, ui->method, &ui->request);
         if (!private_key)
                 return log_openssl_errors("Failed to load private key from '%s'", private_key_uri);
         REENABLE_WARNING;
@@ -1384,24 +1394,88 @@ int openssl_load_key_from_token(
                 KeySourceType private_key_source_type,
                 const char *private_key_source,
                 const char *private_key,
-                EVP_PKEY **ret) {
+                OpenSSLAskPasswordUI *ui,
+                EVP_PKEY **ret_private_key) {
 
         assert(IN_SET(private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER));
         assert(private_key_source);
+        assert(ui);
         assert(private_key);
 
         switch (private_key_source_type) {
 
         case OPENSSL_KEY_SOURCE_ENGINE:
-                return load_key_from_engine(private_key_source, private_key, ret);
+                return load_key_from_engine(private_key_source, private_key, ui, ret_private_key);
         case OPENSSL_KEY_SOURCE_PROVIDER:
-                return load_key_from_provider(private_key_source, private_key, ret);
+                return load_key_from_provider(private_key_source, private_key, ui, ret_private_key);
         default:
                 assert_not_reached();
         }
 }
+
+static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) {
+        int r;
+
+        switch(UI_get_string_type(uis)) {
+        case UIT_PROMPT: {
+                /* If no ask password request was configured use the default openssl UI. */
+                AskPasswordRequest *req = UI_get0_user_data(ui);
+                if (!req)
+                        return (UI_method_get_reader(UI_OpenSSL()))(ui, uis);
+
+                req->message = UI_get0_output_string(uis);
+
+                _cleanup_(strv_freep) char **l = NULL;
+                r = ask_password_auto(req, /*until=*/ 0, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, &l);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to query for PIN: %m");
+                        return 0;
+                }
+
+                if (strv_length(l) != 1) {
+                        log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected only a single password/pin.");
+                        return 0;
+                }
+
+                if (UI_set_result(ui, uis, *l) != 0) {
+                        log_openssl_errors("Failed to set user interface result");
+                        return 0;
+                }
+
+                return 1;
+        }
+        default:
+                return (UI_method_get_reader(UI_OpenSSL()))(ui, uis);
+        }
+}
 #endif
 
+int openssl_ask_password_ui_new(OpenSSLAskPasswordUI **ret) {
+#if HAVE_OPENSSL
+        assert(ret);
+
+        _cleanup_(UI_destroy_methodp) UI_METHOD *method = UI_create_method("systemd-ask-password");
+        if (!method)
+                return log_openssl_errors("Failed to initialize openssl user interface");
+
+        if (UI_method_set_reader(method, openssl_ask_password_ui_read) != 0)
+                return log_openssl_errors("Failed to set openssl user interface reader");
+
+        OpenSSLAskPasswordUI *ui = new(OpenSSLAskPasswordUI, 1);
+        if (!ui)
+                return log_oom_debug();
+
+        *ui = (OpenSSLAskPasswordUI) {
+                .method = TAKE_PTR(method),
+        };
+
+        *ret = TAKE_PTR(ui);
+        return 0;
+#else
+        return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot create ask-password user interface.");
+#endif
+}
+
 int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) {
 #if HAVE_OPENSSL
         _cleanup_free_ uint8_t *der = NULL;
@@ -1420,6 +1494,126 @@ int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) {
 #endif
 }
 
+int openssl_load_x509_certificate(const char *path, X509 **ret) {
+#if HAVE_OPENSSL
+        _cleanup_free_ char *rawcert = NULL;
+        _cleanup_(X509_freep) X509 *cert = NULL;
+        _cleanup_(BIO_freep) BIO *cb = NULL;
+        size_t rawcertsz;
+        int r;
+
+        assert(path);
+        assert(ret);
+
+        r = read_full_file_full(
+                        AT_FDCWD, path, UINT64_MAX, SIZE_MAX,
+                        READ_FULL_FILE_CONNECT_SOCKET,
+                        NULL,
+                        &rawcert, &rawcertsz);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to read certificate file '%s': %m", path);
+
+        cb = BIO_new_mem_buf(rawcert, rawcertsz);
+        if (!cb)
+                return log_oom_debug();
+
+        cert = PEM_read_bio_X509(cb, NULL, NULL, NULL);
+        if (!cert)
+                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate: %s",
+                                       ERR_error_string(ERR_get_error(), NULL));
+
+        if (ret)
+                *ret = TAKE_PTR(cert);
+
+        return 0;
+#else
+        return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot load X509 certificate.");
+#endif
+}
+
+static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) {
+#if HAVE_OPENSSL
+        _cleanup_(erase_and_freep) char *rawkey = NULL;
+        _cleanup_(BIO_freep) BIO *kb = NULL;
+        _cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL;
+        size_t rawkeysz;
+        int r;
+
+        assert(path);
+        assert(ret);
+
+        r = read_full_file_full(
+                        AT_FDCWD, path, UINT64_MAX, SIZE_MAX,
+                        READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
+                        NULL,
+                        &rawkey, &rawkeysz);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to read key file '%s': %m", path);
+
+        kb = BIO_new_mem_buf(rawkey, rawkeysz);
+        if (!kb)
+                return log_oom_debug();
+
+        pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL);
+        if (!pk)
+                return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM private key: %s",
+                                       ERR_error_string(ERR_get_error(), NULL));
+
+        if (ret)
+                *ret = TAKE_PTR(pk);
+
+        return 0;
+#else
+        return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot load private key.");
+#endif
+}
+
+int openssl_load_private_key(
+                KeySourceType private_key_source_type,
+                const char *private_key_source,
+                const char *private_key,
+                const AskPasswordRequest *request,
+                EVP_PKEY **ret_private_key,
+                OpenSSLAskPasswordUI **ret_user_interface) {
+
+        int r;
+
+        assert(private_key);
+        assert(request);
+
+        if (private_key_source_type == OPENSSL_KEY_SOURCE_FILE) {
+                r = openssl_load_private_key_from_file(private_key, ret_private_key);
+                if (r < 0)
+                        return r;
+
+                *ret_user_interface = NULL;
+        } else {
+                _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
+                r = openssl_ask_password_ui_new(&ui);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to allocate ask-password user interface: %m");
+
+                ui->request = *request;
+
+                r = openssl_load_key_from_token(
+                                private_key_source_type,
+                                private_key_source,
+                                private_key,
+                                ui,
+                                ret_private_key);
+                if (r < 0)
+                        return log_debug_errno(
+                                        r,
+                                        "Failed to load key '%s' from OpenSSL private key source %s: %m",
+                                        private_key,
+                                        private_key_source);
+
+                *ret_user_interface = TAKE_PTR(ui);
+        }
+
+        return 0;
+}
+
 int parse_openssl_key_source_argument(
                 const char *argument,
                 char **private_key_source,
index 1a89fcc2bddc4f86d8947fa443b3ec5a7e278831..c9acd40f226fb40fcd92614fafb7e3a48281d86a 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include "ask-password-api.h"
 #include "iovec-util.h"
 #include "macro.h"
 #include "sha256.h"
@@ -13,6 +14,8 @@ typedef enum KeySourceType {
         _OPENSSL_KEY_SOURCE_INVALID = -EINVAL,
 } KeySourceType;
 
+typedef struct OpenSSLAskPasswordUI OpenSSLAskPasswordUI;
+
 int parse_openssl_key_source_argument(const char *argument, char **private_key_source, KeySourceType *private_key_source_type);
 
 #define X509_FINGERPRINT_SIZE SHA256_DIGEST_SIZE
@@ -26,6 +29,7 @@ int parse_openssl_key_source_argument(const char *argument, char **private_key_s
 #  include <openssl/opensslv.h>
 #  include <openssl/pkcs7.h>
 #  include <openssl/ssl.h>
+#  include <openssl/ui.h>
 #  include <openssl/x509v3.h>
 #  ifndef OPENSSL_VERSION_MAJOR
 /* OPENSSL_VERSION_MAJOR macro was added in OpenSSL 3. Thus, if it doesn't exist,  we must be before OpenSSL 3. */
@@ -130,12 +134,13 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s
 
 int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size);
 
-int openssl_load_key_from_token(KeySourceType private_key_source_type, const char *private_key_source, const char *private_key, EVP_PKEY **ret);
+int openssl_load_key_from_token(KeySourceType private_key_source_type, const char *private_key_source, const char *private_key, OpenSSLAskPasswordUI *ui, EVP_PKEY **ret_private_key);
 
 #else
 
 typedef struct X509 X509;
 typedef struct EVP_PKEY EVP_PKEY;
+typedef struct UI_METHOD UI_METHOD;
 
 static inline void *X509_free(X509 *p) {
         assert(p == NULL);
@@ -147,11 +152,17 @@ static inline void *EVP_PKEY_free(EVP_PKEY *p) {
         return NULL;
 }
 
+static inline void* UI_destroy_method(UI_METHOD *p) {
+        assert(p == NULL);
+        return NULL;
+}
+
 static inline int openssl_load_key_from_token(
                 KeySourceType private_key_source_type,
                 const char *private_key_source,
                 const char *private_key,
-                EVP_PKEY **ret) {
+                OpenSSLAskPasswordUI *ui,
+                EVP_PKEY **ret_private_key) {
 
         return -EOPNOTSUPP;
 }
@@ -160,9 +171,37 @@ static inline int openssl_load_key_from_token(
 
 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL);
 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY*, EVP_PKEY_free, NULL);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL);
+
+struct OpenSSLAskPasswordUI {
+        AskPasswordRequest request;
+        UI_METHOD *method;
+};
+
+int openssl_ask_password_ui_new(OpenSSLAskPasswordUI **ret);
+
+static inline OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) {
+        if (!ui)
+                return NULL;
+
+        UI_destroy_method(ui->method);
+        return mfree(ui);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OpenSSLAskPasswordUI*, openssl_ask_password_ui_free, NULL);
 
 int x509_fingerprint(X509 *cert, uint8_t buffer[static X509_FINGERPRINT_SIZE]);
 
+int openssl_load_x509_certificate(const char *path, X509 **ret);
+
+int openssl_load_private_key(
+                KeySourceType private_key_source_type,
+                const char *private_key_source,
+                const char *private_key,
+                const AskPasswordRequest *request,
+                EVP_PKEY **ret_private_key,
+                OpenSSLAskPasswordUI **ret_user_interface);
+
 #if PREFER_OPENSSL
 /* The openssl definition */
 typedef const EVP_MD* hash_md_t;