Skip to content

Commit

Permalink
Merge pull request #2921 from alexlarsson/composefs-sign-v2
Browse files Browse the repository at this point in the history
ostree-prepare-root: Validate ed25519 signatures when requested
  • Loading branch information
cgwalters authored Jul 11, 2023
2 parents a6d9c71 + c29f419 commit 57fe33f
Show file tree
Hide file tree
Showing 12 changed files with 364 additions and 78 deletions.
9 changes: 2 additions & 7 deletions Makefile-libostree.am
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,12 @@ EXTRA_DIST += \
$(top_srcdir)/src/libostree/libostree-released.sym \
$(NULL)

libostree_1_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/bsdiff -I$(srcdir)/libglnx -I$(srcdir)/composefs -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -I$(builddir)/src/libostree \
libostree_1_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/bsdiff -I$(srcdir)/libglnx -I$(srcdir)/composefs -I$(srcdir)/src/libotutil -I$(srcdir)/src/libotcore -I$(srcdir)/src/libostree -I$(builddir)/src/libostree \
$(OT_INTERNAL_GIO_UNIX_CFLAGS) $(OT_INTERNAL_GPGME_CFLAGS) $(OT_DEP_LZMA_CFLAGS) $(OT_DEP_ZLIB_CFLAGS) $(OT_DEP_CRYPTO_CFLAGS) \
-fvisibility=hidden '-D_OSTREE_PUBLIC=__attribute__((visibility("default"))) extern' \
-DPKGLIBEXECDIR=\"$(pkglibexecdir)\"
libostree_1_la_LDFLAGS = -version-number 1:0:0 -Bsymbolic-functions $(addprefix $(wl_versionscript_arg),$(symbol_files))
libostree_1_la_LIBADD = libotutil.la libglnx.la libbsdiff.la $(OT_INTERNAL_GIO_UNIX_LIBS) $(OT_INTERNAL_GPGME_LIBS) \
libostree_1_la_LIBADD = libotutil.la libotcore.la libglnx.la libbsdiff.la $(OT_INTERNAL_GIO_UNIX_LIBS) $(OT_INTERNAL_GPGME_LIBS) \
$(OT_DEP_LZMA_LIBS) $(OT_DEP_ZLIB_LIBS) $(OT_DEP_CRYPTO_LIBS)
# Some change between rust-1.21.0-1.fc27 and rust-1.22.1-1.fc27.x86_64
libostree_1_la_LIBADD += $(bupsplitpath)
Expand Down Expand Up @@ -262,11 +262,6 @@ libostree_1_la_SOURCES += \
src/libostree/ostree-sign-private.h \
$(NULL)

if USE_LIBSODIUM
libostree_1_la_CFLAGS += $(OT_DEP_LIBSODIUM_CFLAGS)
libostree_1_la_LIBADD += $(OT_DEP_LIBSODIUM_LIBS)
endif # USE_LIBSODIUM

if USE_COMPOSEFS
libostree_1_la_LIBADD += libcomposefs.la
endif # USE_COMPOSEFS
Expand Down
5 changes: 0 additions & 5 deletions Makefile-ostree.am
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,3 @@ if USE_LIBARCHIVE
ostree_CFLAGS += $(OT_DEP_LIBARCHIVE_CFLAGS)
ostree_LDADD += $(OT_DEP_LIBARCHIVE_LIBS)
endif

if USE_LIBSODIUM
ostree_CFLAGS += $(OT_DEP_LIBSODIUM_CFLAGS)
ostree_LDADD += $(OT_DEP_LIBSODIUM_LIBS)
endif # USE_LIBSODIUM
24 changes: 24 additions & 0 deletions Makefile-otcore.am
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# SPDX-License-Identifier: LGPL-2.0+
#
# This library 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 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <https://www.gnu.org/licenses/>.

noinst_LTLIBRARIES += libotcore.la

libotcore_la_SOURCES = \
src/libotcore/otcore.h \
src/libotcore/otcore-ed25519-verify.c \
$(NULL)

libotcore_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/libglnx -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_INTERNAL_GIO_UNIX_CFLAGS) $(OT_INTERNAL_GPGME_CFLAGS) $(OT_DEP_CRYPTO_LIBS) $(LIBSYSTEMD_CFLAGS)
libotcore_la_LIBADD = $(OT_INTERNAL_GIO_UNIX_LIBS) $(OT_INTERNAL_GPGME_LIBS) $(LIBSYSTEMD_LIBS) $(OT_DEP_CRYPTO_LIBS)
6 changes: 3 additions & 3 deletions Makefile-switchroot.am
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ ostree-prepare-root : $(ostree_prepare_root_SOURCES)
CLEANFILES += ostree-prepare-root
else
ostree_boot_PROGRAMS += ostree-prepare-root
ostree_prepare_root_CFLAGS += $(AM_CFLAGS) -Isrc/switchroot -I$(srcdir)/composefs
ostree_prepare_root_CFLAGS += $(AM_CFLAGS) -Isrc/switchroot -I$(srcdir)/composefs -I$(srcdir)/src/libostree -I$(srcdir)/src/libotcore -I$(srcdir)/src/libotutil
ostree_prepare_root_SOURCES += src/switchroot/ostree-prepare-root.c
ostree_prepare_root_CPPFLAGS += $(OT_INTERNAL_GIO_UNIX_CFLAGS) -I $(srcdir)/libglnx
ostree_prepare_root_LDADD += $(AM_LDFLAGS) $(OT_INTERNAL_GIO_UNIX_LIBS) libglnx.la
ostree_prepare_root_CPPFLAGS += $(OT_INTERNAL_GIO_UNIX_CFLAGS) $(OT_DEP_CRYPTO_CFLAGS) -I $(srcdir)/libglnx
ostree_prepare_root_LDADD += $(AM_LDFLAGS) $(OT_INTERNAL_GIO_UNIX_LIBS) $(OT_DEP_CRYPTO_LIBS) libotcore.la libotutil.la libglnx.la
endif # BUILDOPT_USE_STATIC_COMPILER


Expand Down
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ noinst_LTLIBRARIES += libcomposefs.la
endif

include Makefile-otutil.am
include Makefile-otcore.am
include Makefile-libostree.am
include Makefile-ostree.am
include Makefile-switchroot.am
Expand Down
4 changes: 4 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ AS_IF([ test $with_crypto = gnutls ], [
AM_CONDITIONAL(USE_GNUTLS, test $with_crypto = gnutls)
dnl end gnutls

dnl we always inject libsodium into our crypto deps in addition to openssl/gnutls
OT_DEP_CRYPTO_CFLAGS="${OT_DEP_CRYPTO_CFLAGS} ${OT_DEP_LIBSODIUM_CFLAGS}"
OT_DEP_CRYPTO_LIBS="${OT_DEP_CRYPTO_LIBS} ${OT_DEP_LIBSODIUM_LIBS}"

dnl Avahi dependency for finding repos
AVAHI_DEPENDENCY="avahi-client >= 0.6.31 avahi-glib >= 0.6.31"

Expand Down
38 changes: 30 additions & 8 deletions docs/composefs.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,46 @@ The possible values are:
- `maybe`: Use composefs if supported and there is a composefs image in the deployment directory
- `on`: Require composefs
- `digest=<sha256>`: Require the mounted composefs image to have a particular digest
- `signed`: This option will be documented in the future; don't use it right now
- `signed=<path>`: Require that the commit is signed as validated by the ed25519 public key specified
by `path` (the path is resolved in the initrd).

### Injecting composefs digests

When generating an OSTree commit, there is a CLI switch `--generate-composefs-metadata`
and a corresponding C API `ostree_repo_commit_add_composefs_metadata`. This will
inject the composefs digest as metadata into the ostree commit under a metadata
key `ostree.composefs.v0`. Because an OSTree commit can be signed, this allows
covering the composefs fsverity digest with a signature.

At the current time, ostree does not directly support verifying the signature on
the commit object before mounting, but that is in progress.
covering the composefs fsverity digest with a signature.

### Signatures

If a commit is signed with a ed25519 private key (see `ostree
--sign`), and `signed=/path/to/public.key` is specified on the
commandline, then the initrd will find the commit being booted in the
system repo and validate its signature against the public key. It will
then ensure that the composefs digest being booted has an fs-verity
digest matching the one in the commit. This allows a fully trusted
read-only /usr.

The exact usage of the signature is up to the user, but a common way
to use it with transien keys. This is done like this:
* Generate a new keypair before each build
* Embed the public key in the initrd that is part of the commit.
* Ensure the kernel commandline has `ot-signed=/path/to/key`
* After commiting, run `ostree --sign` with the private key.
* Throw away the private key.

When a transient key is used this way, that ties the initrd with the
userspace part from the commit. This means each initrd can only boot
the very same userspace it was made for. For example, if an older
version of the OS has a security flaw, you can't boot a new fixed
(signed) initrd and have it boot the older userspace with the flaw.

## Requirements

The current default composefs integration in ostree does not have any requirements
from the underlying kernel and filesystem other than having the following
kernel options set:
The current default composefs integration in ostree does not have any
requirements from the underlying kernel and filesystem other than
having the following kernel options set:

- `CONFIG_OVERLAY_FS`
- `CONFIG_BLK_DEV_LOOP`
Expand Down
61 changes: 11 additions & 50 deletions src/libostree/ostree-sign-ed25519.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,15 @@
#include "config.h"

#include "ostree-sign-ed25519.h"
#include "otcore.h"
#include <libglnx.h>
#include <ot-checksum-utils.h>

#ifdef HAVE_LIBSODIUM
#include <sodium.h>
#define USE_LIBSODIUM
#else

#if defined(HAVE_OPENSSL)
#include <openssl/evp.h>
#define USE_OPENSSL
#endif

#endif

#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "OSTreeSign"

#define OSTREE_SIGN_ED25519_NAME "ed25519"

#define OSTREE_SIGN_METADATA_ED25519_KEY "ostree.sign.ed25519"
#define OSTREE_SIGN_METADATA_ED25519_TYPE "aay"

#define OSTREE_SIGN_ED25519_SIG_SIZE 64U
#define OSTREE_SIGN_ED25519_PUBKEY_SIZE 32U
#define OSTREE_SIGN_ED25519_SEED_SIZE 32U
#define OSTREE_SIGN_ED25519_SECKEY_SIZE \
(OSTREE_SIGN_ED25519_SEED_SIZE + OSTREE_SIGN_ED25519_PUBKEY_SIZE)
Expand Down Expand Up @@ -108,12 +92,11 @@ _ostree_sign_ed25519_init (OstreeSignEd25519 *self)
self->public_keys = NULL;
self->revoked_keys = NULL;

#if defined(USE_LIBSODIUM)
if (sodium_init () < 0)
self->state = ED25519_FAILED_INITIALIZATION;
#elif defined(USE_OPENSSL)
#else
#if !(defined(USE_OPENSSL) || defined(USE_LIBSODIUM))
self->state = ED25519_NOT_SUPPORTED;
#else
if (!otcore_ed25519_init ())
self->state = ED25519_FAILED_INITIALIZATION;
#endif
}

Expand Down Expand Up @@ -232,7 +215,6 @@ ostree_sign_ed25519_data_verify (OstreeSign *self, GBytes *data, GVariant *signa
{
g_autoptr (GVariant) child = g_variant_get_child_value (signatures, i);
g_autoptr (GBytes) signature = g_variant_get_data_as_bytes (child);
gboolean valid = FALSE;

if (g_bytes_get_size (signature) != OSTREE_SIGN_ED25519_SIG_SIZE)
return glnx_throw (
Expand All @@ -246,7 +228,6 @@ ostree_sign_ed25519_data_verify (OstreeSign *self, GBytes *data, GVariant *signa

for (GList *public_key = sign->public_keys; public_key != NULL; public_key = public_key->next)
{

/* TODO: use non-list for tons of revoked keys? */
if (g_list_find_custom (sign->revoked_keys, public_key->data, _compare_ed25519_keys)
!= NULL)
Expand All @@ -256,32 +237,12 @@ ostree_sign_ed25519_data_verify (OstreeSign *self, GBytes *data, GVariant *signa
continue;
}

#if defined(USE_LIBSODIUM)
valid = crypto_sign_verify_detached ((guchar *)g_variant_get_data (child),
g_bytes_get_data (data, NULL),
g_bytes_get_size (data), public_key->data)
== 0;
#elif defined(USE_OPENSSL)
EVP_MD_CTX *ctx = EVP_MD_CTX_new ();
if (!ctx)
return glnx_throw (error, "openssl: failed to allocate context");
EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, public_key->data,
OSTREE_SIGN_ED25519_PUBKEY_SIZE);
if (!pkey)
{
EVP_MD_CTX_free (ctx);
return glnx_throw (error, "openssl: Failed to initialize ed5519 key");
}

valid = EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, pkey) != 0
&& EVP_DigestVerify (ctx, g_bytes_get_data (signature, NULL),
g_bytes_get_size (signature), g_bytes_get_data (data, NULL),
g_bytes_get_size (data))
!= 0;

EVP_PKEY_free (pkey);
EVP_MD_CTX_free (ctx);
#endif
bool valid = false;
// Wrap the pubkey in a GBytes as that's what this API wants
g_autoptr (GBytes) public_key_bytes
= g_bytes_new_static (public_key->data, OSTREE_SIGN_ED25519_PUBKEY_SIZE);
if (!otcore_validate_ed25519_signature (data, public_key_bytes, signature, &valid, error))
return FALSE;
if (!valid)
{
/* Incorrect signature! */
Expand Down
1 change: 1 addition & 0 deletions src/libotcore/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This library is (will be) shared between `libostree-1.so` and `ostree-prepare-root`.
115 changes: 115 additions & 0 deletions src/libotcore/otcore-ed25519-verify.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* SPDX-License-Identifier: LGPL-2.0+
*
* This library 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 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <https://www.gnu.org/licenses/>.
*/

#include "config.h"

#include "otcore.h"

/* Initialize global state; may be called multiple times and is idempotent. */
bool
otcore_ed25519_init (void)
{
#if defined(HAVE_LIBSODIUM)
static gssize initstate;
if (g_once_init_enter (&initstate))
{
int val = sodium_init () >= 0 ? 1 : -1;
g_once_init_leave (&initstate, val);
}
switch (initstate)
{
case 1:
return true;
case -1:
return false;
default:
g_assert_not_reached ();
}
#else
return true;
#endif
}

/* Validate a single ed25519 signature. If there is an unexpected state, such
* as an ill-forumed public key or signature, a hard error will be returned.
*
* If the signature is not correct, this function will return successfully, but
* `out_valid` will be set to `false`.
*
* If the signature is correct, `out_valid` will be `true`.
* */
gboolean
otcore_validate_ed25519_signature (GBytes *data, GBytes *public_key, GBytes *signature,
bool *out_valid, GError **error)
{
// Since this is signature verification code, let's verify preconditions.
g_assert (data);
g_assert (public_key);
g_assert (signature);
g_assert (out_valid);
// It is OK for error to be NULL, though according to GError rules.

#if defined(HAVE_LIBSODIUM) || defined(HAVE_OPENSSL)
// And strictly verify pubkey and signature lengths
if (g_bytes_get_size (public_key) != OSTREE_SIGN_ED25519_PUBKEY_SIZE)
return glnx_throw (error, "Invalid public key of %" G_GSIZE_FORMAT " expected %" G_GSIZE_FORMAT,
(gsize)g_bytes_get_size (public_key),
(gsize)OSTREE_SIGN_ED25519_PUBKEY_SIZE);
const guint8 *public_key_buf = g_bytes_get_data (public_key, NULL);
if (g_bytes_get_size (signature) != OSTREE_SIGN_ED25519_SIG_SIZE)
return glnx_throw (
error, "Invalid signature length of %" G_GSIZE_FORMAT " bytes, expected %" G_GSIZE_FORMAT,
(gsize)g_bytes_get_size (signature), (gsize)OSTREE_SIGN_ED25519_SIG_SIZE);
const guint8 *signature_buf = g_bytes_get_data (signature, NULL);

#endif

#if defined(HAVE_LIBSODIUM)
// Note that libsodium assumes the passed byte arrays for the signature and public key
// have at least the expected length, but we checked that above.
if (crypto_sign_verify_detached (signature_buf, g_bytes_get_data (data, NULL),
g_bytes_get_size (data), public_key_buf)
== 0)
{
*out_valid = true;
}
return TRUE;
#elif defined(HAVE_OPENSSL)
EVP_MD_CTX *ctx = EVP_MD_CTX_new ();
if (!ctx)
return glnx_throw (error, "openssl: failed to allocate context");
EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, public_key_buf,
OSTREE_SIGN_ED25519_PUBKEY_SIZE);
if (!pkey)
{
EVP_MD_CTX_free (ctx);
return glnx_throw (error, "openssl: Failed to initialize ed5519 key");
}
if (EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, pkey) != 0
&& EVP_DigestVerify (ctx, signature_buf, OSTREE_SIGN_ED25519_SIG_SIZE,
g_bytes_get_data (data, NULL), g_bytes_get_size (data))
!= 0)
{
*out_valid = true;
}
EVP_PKEY_free (pkey);
EVP_MD_CTX_free (ctx);
return TRUE;
#else
return glnx_throw (error, "ed25519 signature validation requested, but support not compiled in");
#endif
}
Loading

0 comments on commit 57fe33f

Please sign in to comment.