]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dissect-image: optionally, validate dm-verity signatures in userspace
authorLennart Poettering <lennart@poettering.net>
Thu, 9 Sep 2021 15:55:36 +0000 (17:55 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 28 Sep 2021 15:03:31 +0000 (17:03 +0200)
Getting certificates for dm-verity roothash signing into the trusted
kernel keychain is a royal PITA (means recompiling or rebooting with
shim), hence let's add a minimal userspace PKCS7 validation as well.

The mechanism is really simple and compatible with the verification the
kernel does. The only difference is that the certificates are searched
in /etc/verity.d/*.crt (and similar dirs in /usr/lib/, …).

We'll first try validation by passing the PKCS#7 data to the kernel, but
if that doesn't work we'll see if one of the certificates found that way
works and then attempt to attach the image without passing the PKCS#7
data to the kernel.

This makes it very easy to have fully validated GPT disk images. For
example, just copy the 'mkosi.secure-boot.crt' file you have in your
mkosi build dir to /etc/verity.d/ and things should just work.

src/resolve/resolved-dnstls-openssl.c
src/shared/dissect-image.c
src/shared/openssl-util.h

index 17af90d4cc0609e7843c9a5c0be83f6e67ebf057..cba3f14f2d93aca800f352f75807f4fe3f011c78 100644 (file)
@@ -9,13 +9,11 @@
 #include <openssl/x509v3.h>
 
 #include "io-util.h"
+#include "openssl-util.h"
 #include "resolved-dns-stream.h"
 #include "resolved-dnstls.h"
 #include "resolved-manager.h"
 
-DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL);
-DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL);
-
 static int dnstls_flush_write_buffer(DnsStream *stream) {
         ssize_t ss;
 
index 157ef262f170b2b1c543bb6f40502ac8a51b3ef1..de75340c9d657bf9d9f5b2a44620b1ade46a58cb 100644 (file)
 #include <sys/wait.h>
 #include <sysexits.h>
 
+#if HAVE_OPENSSL
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#endif
+
 #include "sd-device.h"
 #include "sd-id128.h"
 
@@ -18,6 +24,7 @@
 #include "ask-password-api.h"
 #include "blkid-util.h"
 #include "blockdev-util.h"
+#include "conf-files.h"
 #include "copy.h"
 #include "cryptsetup-util.h"
 #include "def.h"
@@ -43,6 +50,7 @@
 #include "mountpoint-util.h"
 #include "namespace-util.h"
 #include "nulstr-util.h"
+#include "openssl-util.h"
 #include "os-util.h"
 #include "path-util.h"
 #include "process-util.h"
@@ -2259,6 +2267,146 @@ static inline char* dm_deferred_remove_clean(char *name) {
 }
 DEFINE_TRIVIAL_CLEANUP_FUNC(char *, dm_deferred_remove_clean);
 
+static int validate_signature_userspace(const VeritySettings *verity) {
+#if HAVE_OPENSSL
+        _cleanup_(sk_X509_free_allp) STACK_OF(X509) *sk = NULL;
+        _cleanup_strv_free_ char **certs = NULL;
+        _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
+        _cleanup_free_ char *s = NULL;
+        _cleanup_(BIO_freep) BIO *bio = NULL; /* 'bio' must be freed first, 's' second, hence keep this order
+                                               * of declaration in place, please */
+        const unsigned char *d;
+        char **i;
+        int r;
+
+        assert(verity);
+        assert(verity->root_hash);
+        assert(verity->root_hash_sig);
+
+        /* Because installing a signature certificate into the kernel chain is so messy, let's optionally do
+         * userspace validation. */
+
+        r = conf_files_list_nulstr(&certs, ".crt", NULL, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, CONF_PATHS_NULSTR("verity.d"));
+        if (r < 0)
+                return log_debug_errno(r, "Failed to enumerate certificates: %m");
+        if (strv_isempty(certs)) {
+                log_debug("No userspace dm-verity certificates found.");
+                return 0;
+        }
+
+        d = verity->root_hash_sig;
+        p7 = d2i_PKCS7(NULL, &d, (long) verity->root_hash_sig_size);
+        if (!p7)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse PKCS7 DER signature data.");
+
+        s = hexmem(verity->root_hash, verity->root_hash_size);
+        if (!s)
+                return log_oom_debug();
+
+        bio = BIO_new_mem_buf(s, strlen(s));
+        if (!bio)
+                return log_oom_debug();
+
+        sk = sk_X509_new_null();
+        if (!sk)
+                return log_oom_debug();
+
+        STRV_FOREACH(i, certs) {
+                _cleanup_(X509_freep) X509 *c = NULL;
+                _cleanup_fclose_ FILE *f = NULL;
+
+                f = fopen(*i, "re");
+                if (!f) {
+                        log_debug_errno(errno, "Failed to open '%s', ignoring: %m", *i);
+                        continue;
+                }
+
+                c = PEM_read_X509(f, NULL, NULL, NULL);
+                if (!c) {
+                        log_debug("Failed to load X509 certificate '%s', ignoring.", *i);
+                        continue;
+                }
+
+                if (sk_X509_push(sk, c) == 0)
+                        return log_oom_debug();
+
+                TAKE_PTR(c);
+        }
+
+        r = PKCS7_verify(p7, sk, NULL, bio, NULL, PKCS7_NOINTERN|PKCS7_NOVERIFY);
+        if (r)
+                log_debug("Userspace PKCS#7 validation succeeded.");
+        else
+                log_debug("Userspace PKCS#7 validation failed: %s", ERR_error_string(ERR_get_error(), NULL));
+
+        return r;
+#else
+        log_debug("Not doing client-side validation of dm-verity root hash signatures, OpenSSL support disabled.");
+        return 0;
+#endif
+}
+
+static int do_crypt_activate_verity(
+                struct crypt_device *cd,
+                const char *name,
+                const VeritySettings *verity) {
+
+        bool check_signature;
+        int r;
+
+        assert(cd);
+        assert(name);
+        assert(verity);
+
+        if (verity->root_hash_sig) {
+                r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_SIGNATURE");
+                if (r < 0 && r != -ENXIO)
+                        log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_SIGNATURE");
+
+                check_signature = r != 0;
+        } else
+                check_signature = false;
+
+        if (check_signature) {
+
+#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
+                /* First, if we have support for signed keys in the kernel, then try that first. */
+                r = sym_crypt_activate_by_signed_key(
+                                cd,
+                                name,
+                                verity->root_hash,
+                                verity->root_hash_size,
+                                verity->root_hash_sig,
+                                verity->root_hash_sig_size,
+                                CRYPT_ACTIVATE_READONLY);
+                if (r >= 0)
+                        return r;
+
+                log_debug("Validation of dm-verity signature failed via the kernel, trying userspace validation instead.");
+#else
+                log_debug("Activation of verity device with signature requested, but not supported via the kernel by %s due to missing crypt_activate_by_signed_key(), trying userspace validation instead.",
+                          program_invocation_short_name);
+#endif
+
+                /* So this didn't work via the kernel, then let's try userspace validation instead. If that
+                 * works we'll try to activate without telling the kernel the signature. */
+
+                r = validate_signature_userspace(verity);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return log_debug_errno(SYNTHETIC_ERRNO(ENOKEY),
+                                               "Activation of signed Verity volume worked neither via the kernel nor in userspace, can't activate.");
+        }
+
+        return sym_crypt_activate_by_volume_key(
+                        cd,
+                        name,
+                        verity->root_hash,
+                        verity->root_hash_size,
+                        CRYPT_ACTIVATE_READONLY);
+}
+
 static int verity_partition(
                 PartitionDesignator designator,
                 DissectedPartition *m,
@@ -2330,27 +2478,8 @@ static int verity_partition(
          * In case of ENODEV/ENOENT, which can happen if another process is activating at the exact same time,
          * retry a few times before giving up. */
         for (unsigned i = 0; i < N_DEVICE_NODE_LIST_ATTEMPTS; i++) {
-                if (verity->root_hash_sig) {
-#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
-                        r = sym_crypt_activate_by_signed_key(
-                                        cd,
-                                        name,
-                                        verity->root_hash,
-                                        verity->root_hash_size,
-                                        verity->root_hash_sig,
-                                        verity->root_hash_sig_size,
-                                        CRYPT_ACTIVATE_READONLY);
-#else
-                        r = log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
-                                            "Activation of verity device with signature requested, but not supported by %s due to missing crypt_activate_by_signed_key().", program_invocation_short_name);
-#endif
-                } else
-                        r = sym_crypt_activate_by_volume_key(
-                                        cd,
-                                        name,
-                                        verity->root_hash,
-                                        verity->root_hash_size,
-                                        CRYPT_ACTIVATE_READONLY);
+
+                r = do_crypt_activate_verity(cd, name, verity);
                 /* libdevmapper can return EINVAL when the device is already in the activation stage.
                  * There's no way to distinguish this situation from a genuine error due to invalid
                  * parameters, so immediately fall back to activating the device with a unique name.
index e6c2bd9310b7359b51466cc789b08f26bda68ab9..66441c232c04e7e582ca349ff7816d219491f36a 100644 (file)
@@ -4,13 +4,26 @@
 #include "macro.h"
 
 #if HAVE_OPENSSL
+#  include <openssl/bio.h>
 #  include <openssl/evp.h>
-#  include <openssl/x509.h>
+#  include <openssl/pkcs7.h>
+#  include <openssl/ssl.h>
+#  include <openssl/x509v3.h>
 
 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL);
 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509_NAME*, X509_NAME_free, NULL);
 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY_CTX*, EVP_PKEY_CTX_free, NULL);
 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER_CTX*, EVP_CIPHER_CTX_free, NULL);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7*, PKCS7_free, NULL);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL);
+
+static inline void sk_X509_free_allp(STACK_OF(X509) **sk) {
+        if (!sk || !*sk)
+                return;
+
+        sk_X509_pop_free(*sk, X509_free);
+}
 
 int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size);