fsverity: Add _ostree_fsverity_sign helper
authorAlexander Larsson <alexl@redhat.com>
Tue, 16 May 2023 14:01:33 +0000 (16:01 +0200)
committerAlexander Larsson <alexl@redhat.com>
Wed, 31 May 2023 08:55:14 +0000 (10:55 +0200)
This code signs a fsverity digest (using openssl) such that the
resulting signature can be used with the FS_IOC_ENABLE_VERITY ioctl.

src/libostree/ostree-repo-private.h
src/libostree/ostree-repo-verity.c

index f38658548113caa85358b6fd02bc38fae3a53ca0..5395de40f4b3cfeb0ee89d9de0d6512d6aed7a3f 100644 (file)
@@ -393,6 +393,9 @@ gboolean _ostree_tmpf_fsverity_core (GLnxTmpfile *tmpf, _OstreeFeatureSupport fs
                                      gboolean *supported, GError **error);
 
 gboolean _ostree_tmpf_fsverity (OstreeRepo *self, GLnxTmpfile *tmpf, GError **error);
+gboolean _ostree_fsverity_sign (const char *certfile, const char *keyfile,
+                                const guchar *fsverity_digest, GBytes **data_out,
+                                GCancellable *cancellable, GError **error);
 
 gboolean _ostree_repo_verify_bindings (const char *collection_id, const char *ref_name,
                                        GVariant *commit, GError **error);
index 8c199e858e37ba4f099c60ac50247690d26d57a1..fcbdaccd9c1bef210761f4d3f70fbcfca3276276 100644 (file)
 #include <linux/fsverity.h>
 #endif
 
+#if defined(HAVE_OPENSSL)
+#include <openssl/bio.h>
+#include <openssl/engine.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs7.h>
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (X509, X509_free);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (EVP_PKEY, EVP_PKEY_free);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (BIO, BIO_free);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (PKCS7, PKCS7_free);
+#endif
+
 gboolean
 _ostree_repo_parse_fsverity_config (OstreeRepo *self, GError **error)
 {
@@ -167,3 +180,131 @@ _ostree_tmpf_fsverity (OstreeRepo *self, GLnxTmpfile *tmpf, GError **error)
 #endif
   return TRUE;
 }
+
+#if defined(HAVE_OPENSSL)
+static gboolean
+read_pem_x509_certificate (const char *certfile, X509 **cert_ret, GError **error)
+{
+  g_autoptr (BIO) bio = NULL;
+  X509 *cert;
+
+  errno = 0;
+  bio = BIO_new_file (certfile, "r");
+  if (!bio)
+    return glnx_throw_errno_prefix (error, "Error loading composefs certfile '%s'", certfile);
+
+  cert = PEM_read_bio_X509 (bio, NULL, NULL, NULL);
+  if (!cert)
+    return glnx_throw (error, "Error parsing composefs certfile '%s'", certfile);
+
+  *cert_ret = cert;
+  return TRUE;
+}
+
+static gboolean
+read_pem_pkcs8_private_key (const char *keyfile, EVP_PKEY **pkey_ret, GError **error)
+{
+  g_autoptr (BIO) bio;
+  EVP_PKEY *pkey;
+
+  errno = 0;
+  bio = BIO_new_file (keyfile, "r");
+  if (!bio)
+    return glnx_throw_errno_prefix (error, "Error loading composefs keyfile '%s'", keyfile);
+
+  pkey = PEM_read_bio_PrivateKey (bio, NULL, NULL, NULL);
+  if (!pkey)
+    return glnx_throw (error, "Error parsing composefs keyfile '%s'", keyfile);
+
+  *pkey_ret = pkey;
+  return TRUE;
+}
+
+static gboolean
+sign_pkcs7 (const void *data_to_sign, size_t data_size, EVP_PKEY *pkey, X509 *cert,
+            const EVP_MD *md, BIO **res, GError **error)
+{
+  int pkcs7_flags = PKCS7_BINARY | PKCS7_DETACHED | PKCS7_NOATTR | PKCS7_NOCERTS | PKCS7_PARTIAL;
+  g_autoptr (BIO) bio = NULL;
+  g_autoptr (BIO) bio_res = NULL;
+  g_autoptr (PKCS7) p7 = NULL;
+
+  bio = BIO_new_mem_buf ((void *)data_to_sign, data_size);
+  if (!bio)
+    return glnx_throw (error, "Can't allocate buffer");
+
+  p7 = PKCS7_sign (NULL, NULL, NULL, bio, pkcs7_flags);
+  if (!p7)
+    return glnx_throw (error, "Can't initialize PKCS#7");
+
+  if (!PKCS7_sign_add_signer (p7, cert, pkey, md, pkcs7_flags))
+    return glnx_throw (error, "Can't add signer to PKCS#7");
+
+  if (PKCS7_final (p7, bio, pkcs7_flags) != 1)
+    return glnx_throw (error, "Can't finalize PKCS#7");
+
+  bio_res = BIO_new (BIO_s_mem ());
+  if (!bio_res)
+    return glnx_throw (error, "Can't allocate buffer");
+
+  if (i2d_PKCS7_bio (bio_res, p7) != 1)
+    return glnx_throw (error, "Can't DER-encode PKCS#7 signature object");
+
+  *res = g_steal_pointer (&bio_res);
+  return TRUE;
+}
+
+gboolean
+_ostree_fsverity_sign (const char *certfile, const char *keyfile, const guchar *fsverity_digest,
+                       GBytes **data_out, GCancellable *cancellable, GError **error)
+{
+  g_autofree struct fsverity_formatted_digest *d = NULL;
+  gsize d_size;
+  g_autoptr (X509) cert = NULL;
+  g_autoptr (EVP_PKEY) pkey = NULL;
+  g_autoptr (BIO) bio_sig = NULL;
+  const EVP_MD *md;
+  guchar *sig;
+  long sig_size;
+
+  if (certfile == NULL)
+    return glnx_throw (error, "certfile not specified");
+
+  if (keyfile == NULL)
+    return glnx_throw (error, "keyfile not specified");
+
+  if (!read_pem_x509_certificate (certfile, &cert, error))
+    return FALSE;
+
+  if (!read_pem_pkcs8_private_key (keyfile, &pkey, error))
+    return FALSE;
+
+  md = EVP_sha256 ();
+  if (md == NULL)
+    return glnx_throw (error, "No sha256 support in openssl");
+
+  d_size = sizeof (struct fsverity_formatted_digest) + OSTREE_SHA256_DIGEST_LEN;
+  d = g_malloc0 (d_size);
+
+  memcpy (d->magic, "FSVerity", 8);
+  d->digest_algorithm = GUINT16_TO_LE (FS_VERITY_HASH_ALG_SHA256);
+  d->digest_size = GUINT16_TO_LE (OSTREE_SHA256_DIGEST_LEN);
+  memcpy (d->digest, fsverity_digest, OSTREE_SHA256_DIGEST_LEN);
+
+  if (!sign_pkcs7 (d, d_size, pkey, cert, md, &bio_sig, error))
+    return FALSE;
+
+  sig_size = BIO_get_mem_data (bio_sig, &sig);
+
+  *data_out = g_bytes_new (sig, sig_size);
+
+  return TRUE;
+}
+#else
+gboolean
+_ostree_fsverity_sign (const char *certfile, const char *keyfile, const guchar *fsverity_digest,
+                       GBytes **data_out, GCancellable *cancellable, GError **error)
+{
+  return glnx_throw (error, "fsverity signature support not built");
+}
+#endif