sign: Support spki signature type

The current "ed25519" signing type assumes raw Ed25519 key format for
both public and private keys. That requires custom processing of keys
after generated with openssl tools, and also lacks cryptographic
agility[1]; when Ed25519 becomes vulnerable, it would not be
straightforward to migrate to other algorithms, such as post-quantum
signature algorithms.

This patch adds a new signature type "spki" which uses the X.509
SubjectPublicKeyInfo format for public keys. Keys in this format can
easily be created with openssl tools and provide crypto agility as the
format embeds algorithm identifier.

Currently, the corresponding private keys shall be in the PKCS#8
format, while future extensions may support other format such as
opaque key handles on a hardware token.

The "spki" signature type prefers keys to be encoded in the PEM
format on disk, while it still accepts base64 encoded keys when given
through the command-line.

1. https://en.wikipedia.org/wiki/Cryptographic_agility

Signed-off-by: Daiki Ueno <dueno@redhat.com>
This commit is contained in:
Daiki Ueno 2024-07-12 12:54:13 +09:00
parent 5583563b8f
commit e83eda0aec
17 changed files with 1128 additions and 61 deletions

View File

@ -261,6 +261,8 @@ libostree_1_la_SOURCES += \
src/libostree/ostree-sign-dummy.h \
src/libostree/ostree-sign-ed25519.c \
src/libostree/ostree-sign-ed25519.h \
src/libostree/ostree-sign-spki.c \
src/libostree/ostree-sign-spki.h \
src/libostree/ostree-sign-private.h \
src/libostree/ostree-blob-reader.c \
src/libostree/ostree-blob-reader.h \

View File

@ -19,6 +19,7 @@ libotcore_la_SOURCES = \
src/libotcore/otcore.h \
src/libotcore/otcore-ed25519-verify.c \
src/libotcore/otcore-prepare-root.c \
src/libotcore/otcore-spki-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)

View File

@ -156,12 +156,24 @@ _installed_or_uninstalled_test_scripts = \
tests/test-summary-collections.sh \
tests/test-pull-collections.sh \
tests/test-config.sh \
tests/test-signed-commit.sh \
tests/test-signed-commit-dummy.sh \
tests/test-signed-pull.sh \
tests/test-pre-signed-pull.sh \
tests/test-signed-pull-summary.sh \
$(NULL)
if HAVE_ED25519
_installed_or_uninstalled_test_scripts += \
tests/test-signed-commit-ed25519.sh \
$(NULL)
endif
if HAVE_SPKI
_installed_or_uninstalled_test_scripts += \
tests/test-signed-commit-spki.sh \
$(NULL)
endif
if USE_GPGME
_installed_or_uninstalled_test_scripts += \
tests/test-remote-gpg-import.sh \

View File

@ -452,10 +452,19 @@ if test x$with_openssl != xno; then OSTREE_FEATURES="$OSTREE_FEATURES openssl";
AM_CONDITIONAL(USE_OPENSSL, test $with_openssl != no)
dnl end openssl
if test x$with_openssl != xno || test x$with_ed25519_libsodium != xno; then
AM_CONDITIONAL([HAVE_ED25519], [test x$with_openssl != xno || test x$with_ed25519_libsodium != xno])
AM_COND_IF([HAVE_ED25519], [
AC_DEFINE([HAVE_ED25519], 1, [Define if ed25519 is supported ])
OSTREE_FEATURES="$OSTREE_FEATURES sign-ed25519"
fi
])
AM_CONDITIONAL([HAVE_SPKI], [test x$with_openssl != xno])
AM_COND_IF([HAVE_SPKI], [
AC_DEFINE([HAVE_SPKI], 1, [Define if spki is supported ])
OSTREE_FEATURES="$OSTREE_FEATURES sign-spki"
])
dnl begin gnutls; in contrast to openssl this one only
dnl supports --with-crypto=gnutls
@ -697,7 +706,7 @@ echo "
systemd: $with_libsystemd
libmount: $with_libmount
libsodium (ed25519 signatures): $with_ed25519_libsodium
openssl (ed25519 signatures): $with_openssl
openssl (ed25519 and spki signatures): $with_openssl
libarchive (parse tar files directly): $with_libarchive
static deltas: yes (always enabled now)
O_TMPFILE: $enable_otmpfile

View File

@ -312,7 +312,7 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
<term><option>-s, --sign-type</option></term>
<listitem><para>
Use particular signature engine. Currently
available <arg choice="plain">ed25519</arg> and <arg choice="plain">dummy</arg>
available <arg choice="plain">ed25519</arg>, <arg choice="plain">spki</arg>, and <arg choice="plain">dummy</arg>
signature types.
The default is <arg choice="plain">ed25519</arg>.
@ -323,7 +323,8 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
<varlistentry>
<term><option>--sign-from-file</option>="PATH"</term>
<listitem><para>
This will read a key (corresponding to the provided <literal>--sign-type</literal> from the provided path. The key should be base64 encoded.
This will read a key (corresponding to the provided <literal>--sign-type</literal> from the provided path. The encoding of the key depends on
signature engine. For ed25519 the key should be base64 encoded, for spki it should be in PEM format, and for dummy it should be an ASCII-string.
</para></listitem>
</varlistentry>
@ -337,7 +338,7 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
The <literal>KEY-ID</literal> is:
<variablelist>
<varlistentry>
<term><option>for ed25519:</option></term>
<term><option>for ed25519 and spki:</option></term>
<listitem><para>
<literal>base64</literal>-encoded secret key for commit signing.
</para></listitem>

View File

@ -64,26 +64,28 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
</para>
<para>
There are several "well-known" system places for `ed25519` trusted and revoked public keys -- expected single <literal>base64</literal>-encoded key per line.
For `ed25519` and `spki`, there are several "well-known" system places for trusted and revoked public keys as listed below.
</para>
<para>Files:
<itemizedlist>
<listitem><para><filename>/etc/ostree/trusted.ed25519</filename></para></listitem>
<listitem><para><filename>/etc/ostree/revoked.ed25519</filename></para></listitem>
<listitem><para><filename>/usr/share/ostree/trusted.ed25519</filename></para></listitem>
<listitem><para><filename>/usr/share/ostree/revoked.ed25519</filename></para></listitem>
<listitem><para><filename>/etc/ostree/trusted.<replaceable>SIGN-TYPE</replaceable></filename></para></listitem>
<listitem><para><filename>/etc/ostree/revoked.<replaceable>SIGN-TYPE</replaceable></filename></para></listitem>
<listitem><para><filename>/usr/share/ostree/trusted.<replaceable>SIGN-TYPE</replaceable></filename></para></listitem>
<listitem><para><filename>/usr/share/ostree/revoked.<replaceable>SIGN-TYPE</replaceable></filename></para></listitem>
</itemizedlist>
</para>
<para>Directories containing files with keys:
<itemizedlist>
<listitem><para><filename>/etc/ostree/trusted.ed25519.d</filename></para></listitem>
<listitem><para><filename>/etc/ostree/revoked.ed25519.d</filename></para></listitem>
<listitem><para><filename>/usr/share/ostree/trusted.ed25519.d</filename></para></listitem>
<listitem><para><filename>/usr/share/ostree/revoked.ed25519.d</filename></para></listitem>
<listitem><para><filename>/etc/ostree/trusted.<replaceable>SIGN-TYPE</replaceable>.d</filename></para></listitem>
<listitem><para><filename>/etc/ostree/revoked.<replaceable>SIGN-TYPE</replaceable>.d</filename></para></listitem>
<listitem><para><filename>/usr/share/ostree/trusted.<replaceable>SIGN-TYPE</replaceable>.d</filename></para></listitem>
<listitem><para><filename>/usr/share/ostree/revoked.<replaceable>SIGN-TYPE</replaceable>.d</filename></para></listitem>
</itemizedlist>
</para>
<para>The format of those files depends on the signature mechanism; for `ed25519`, keys are stored in the <literal>base64</literal> encoding per line, while for `spki` they are stored in the PEM "PUBLIC KEY" encoding.</para>
</refsect1>
<refsect1>
@ -95,7 +97,7 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
<listitem><para>
<variablelist>
<varlistentry>
<term><option>for ed25519:</option></term>
<term><option>for ed25519 and spki:</option></term>
<listitem><para>
<literal>base64</literal>-encoded secret (for signing) or public key (for verifying).
</para></listitem>
@ -120,7 +122,7 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
<term><option>-s, --sign-type</option></term>
<listitem><para>
Use particular signature mechanism. Currently
available <arg choice="plain">ed25519</arg> and <arg choice="plain">dummy</arg>
available <arg choice="plain">ed25519</arg>, <arg choice="plain">spki</arg>, and <arg choice="plain">dummy</arg>
signature types.
The default is <arg choice="plain">ed25519</arg>.
@ -133,8 +135,8 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
</para></listitem>
<listitem><para>
Valid for <literal>ed25519</literal> signature type.
For <literal>ed25519</literal> this file must contain <literal>base64</literal>-encoded
Valid for <literal>ed25519</literal> and <literal>spki</literal> signature types.
This file must contain <literal>base64</literal>-encoded
secret key(s) (for signing) or public key(s) (for verifying) per line.
</para></listitem>
</varlistentry>

View File

@ -157,6 +157,7 @@ main ()
PRINT_CONSTANT (OSTREE_SHA256_DIGEST_LEN);
PRINT_CONSTANT (OSTREE_SHA256_STRING_LEN);
PRINT_CONSTANT (OSTREE_SIGN_NAME_ED25519);
PRINT_CONSTANT (OSTREE_SIGN_NAME_SPKI);
PRINT_CONSTANT ((gint)OSTREE_STATIC_DELTA_GENERATE_OPT_LOWLATENCY);
PRINT_CONSTANT ((gint)OSTREE_STATIC_DELTA_GENERATE_OPT_MAJOR);
PRINT_CONSTANT ((gint)OSTREE_STATIC_DELTA_INDEX_FLAGS_NONE);

View File

@ -0,0 +1,636 @@
/* vim:set et sw=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e2s: */
/*
* Copyright © 2019 Collabora Ltd.
* Copyright © 2024 Red Hat, Inc.
*
* 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 "ostree-sign-spki.h"
#include "otcore.h"
#include <libglnx.h>
#include <ot-checksum-utils.h>
#include <string.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "OSTreeSign"
#define OSTREE_SIGN_SPKI_NAME "spki"
typedef enum
{
SPKI_OK,
SPKI_NOT_SUPPORTED,
SPKI_FAILED_INITIALIZATION
} spki_state;
struct _OstreeSignSpki
{
GObject parent;
spki_state state;
GBytes *secret_key;
GList *public_keys; /* GBytes */
GList *revoked_keys; /* GBytes */
};
static void ostree_sign_spki_iface_init (OstreeSignInterface *self);
G_DEFINE_TYPE_WITH_CODE (OstreeSignSpki, _ostree_sign_spki, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (OSTREE_TYPE_SIGN, ostree_sign_spki_iface_init));
static void
ostree_sign_spki_iface_init (OstreeSignInterface *self)
{
self->data = ostree_sign_spki_data;
self->data_verify = ostree_sign_spki_data_verify;
self->get_name = ostree_sign_spki_get_name;
self->metadata_key = ostree_sign_spki_metadata_key;
self->metadata_format = ostree_sign_spki_metadata_format;
self->clear_keys = ostree_sign_spki_clear_keys;
self->set_sk = ostree_sign_spki_set_sk;
self->set_pk = ostree_sign_spki_set_pk;
self->add_pk = ostree_sign_spki_add_pk;
self->load_pk = ostree_sign_spki_load_pk;
}
static void
_ostree_sign_spki_class_init (OstreeSignSpkiClass *self)
{
}
static void
_ostree_sign_spki_init (OstreeSignSpki *self)
{
self->state = SPKI_OK;
self->secret_key = NULL;
self->public_keys = NULL;
self->revoked_keys = NULL;
#if !defined(USE_OPENSSL)
self->state = SPKI_NOT_SUPPORTED;
#else
if (!otcore_spki_init ())
self->state = SPKI_FAILED_INITIALIZATION;
#endif
}
static gboolean
_ostree_sign_spki_is_initialized (OstreeSignSpki *self, GError **error)
{
switch (self->state)
{
case SPKI_OK:
break;
case SPKI_NOT_SUPPORTED:
return glnx_throw (error, "spki: engine is not supported");
case SPKI_FAILED_INITIALIZATION:
return glnx_throw (error, "spki: crypto library isn't initialized properly");
}
return TRUE;
}
gboolean
ostree_sign_spki_data (OstreeSign *self, GBytes *data, GBytes **signature,
GCancellable *cancellable, GError **error)
{
#if defined(USE_OPENSSL)
g_assert (OSTREE_IS_SIGN (self));
OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
if (!_ostree_sign_spki_is_initialized (sign, error))
return FALSE;
if (sign->secret_key == NULL)
return glnx_throw (error, "Not able to sign: secret key is not set");
gsize secret_key_size;
const guint8 *secret_key_buf = g_bytes_get_data (sign->secret_key, &secret_key_size);
EVP_MD_CTX *ctx = EVP_MD_CTX_new ();
if (!ctx)
return glnx_throw (error, "openssl: failed to allocate context");
const unsigned char *p = secret_key_buf;
EVP_PKEY *pkey = d2i_AutoPrivateKey (NULL, &p, secret_key_size);
if (!pkey)
{
EVP_MD_CTX_free (ctx);
return glnx_throw (error, "openssl: Failed to initialize spki key");
}
unsigned long long sig_size = 0;
g_autofree guchar *sig = NULL;
size_t len;
if (EVP_DigestSignInit (ctx, NULL, NULL, NULL, pkey)
&& EVP_DigestSign (ctx, NULL, &len, g_bytes_get_data (data, NULL), g_bytes_get_size (data)))
{
sig = g_malloc0 (len);
if (EVP_DigestSign (ctx, sig, &len, g_bytes_get_data (data, NULL), g_bytes_get_size (data)))
sig_size = len;
}
EVP_PKEY_free (pkey);
EVP_MD_CTX_free (ctx);
if (sig_size == 0)
return glnx_throw (error, "Failed to sign");
*signature = g_bytes_new_take (g_steal_pointer (&sig), sig_size);
return TRUE;
#else
return glnx_throw (error, "spki signature validation requested, but support not compiled in");
#endif
}
gboolean
ostree_sign_spki_data_verify (OstreeSign *self, GBytes *data, GVariant *signatures,
char **out_success_message, GError **error)
{
g_assert (OSTREE_IS_SIGN (self));
if (data == NULL)
return glnx_throw (error, "spki: unable to verify NULL data");
OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
if (!_ostree_sign_spki_is_initialized (sign, error))
return FALSE;
if (signatures == NULL)
return glnx_throw (error, "spki: commit have no signatures of my type");
if (!g_variant_is_of_type (signatures, (GVariantType *)OSTREE_SIGN_METADATA_SPKI_TYPE))
return glnx_throw (error, "spki: wrong type passed for verification");
/* If no keys pre-loaded then,
* try to load public keys from storage(s) */
if (sign->public_keys == NULL)
{
g_autoptr (GVariantBuilder) builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
g_autoptr (GVariant) options = g_variant_builder_end (builder);
if (!ostree_sign_spki_load_pk (self, options, error))
return FALSE;
}
g_debug ("verify: data hash = 0x%x", g_bytes_hash (data));
g_autoptr (GString) invalid_signatures = NULL;
guint n_invalid_signatures = 0;
for (gsize i = 0; i < g_variant_n_children (signatures); i++)
{
g_autoptr (GVariant) child = g_variant_get_child_value (signatures, i);
g_autoptr (GBytes) signature = g_variant_get_data_as_bytes (child);
g_debug ("Read signature %d: %s", (gint)i, g_variant_print (child, TRUE));
for (GList *l = sign->public_keys; l != NULL; l = l->next)
{
GBytes *public_key = l->data;
/* TODO: use non-list for tons of revoked keys? */
if (g_list_find_custom (sign->revoked_keys, public_key, g_bytes_compare) != NULL)
{
g_autofree char *hex = g_malloc0 (g_bytes_get_size (public_key) * 2 + 1);
ot_bin2hex (hex, g_bytes_get_data (public_key, NULL), g_bytes_get_size (public_key));
g_debug ("Skip revoked key '%s'", hex);
continue;
}
bool valid = false;
if (!otcore_validate_spki_signature (data, public_key, signature, &valid, error))
return FALSE;
if (!valid)
{
/* Incorrect signature! */
if (invalid_signatures == NULL)
invalid_signatures = g_string_new ("");
else
g_string_append (invalid_signatures, "; ");
n_invalid_signatures++;
g_autofree char *hex = g_malloc0 (g_bytes_get_size (public_key) * 2 + 1);
ot_bin2hex (hex, g_bytes_get_data (public_key, NULL), g_bytes_get_size (public_key));
g_string_append_printf (invalid_signatures, "key '%s'", hex);
}
else
{
if (out_success_message)
{
g_autofree char *hex = g_malloc0 (g_bytes_get_size (public_key) * 2 + 1);
ot_bin2hex (hex, g_bytes_get_data (public_key, NULL),
g_bytes_get_size (public_key));
*out_success_message = g_strdup_printf (
"spki: Signature verified successfully with key '%s'", hex);
}
return TRUE;
}
}
}
if (invalid_signatures)
{
g_assert_cmpuint (n_invalid_signatures, >, 0);
/* The test suite has a key ring with 100 keys. This seems insane, let's
* cap a reasonable error message at 3.
*/
if (n_invalid_signatures > 3)
return glnx_throw (error, "spki: Signature couldn't be verified; tried %u keys",
n_invalid_signatures);
return glnx_throw (error, "spki: Signature couldn't be verified with: %s",
invalid_signatures->str);
}
return glnx_throw (error, "spki: no signatures found");
}
const gchar *
ostree_sign_spki_get_name (OstreeSign *self)
{
g_assert (OSTREE_IS_SIGN (self));
return OSTREE_SIGN_SPKI_NAME;
}
const gchar *
ostree_sign_spki_metadata_key (OstreeSign *self)
{
return OSTREE_SIGN_METADATA_SPKI_KEY;
}
const gchar *
ostree_sign_spki_metadata_format (OstreeSign *self)
{
return OSTREE_SIGN_METADATA_SPKI_TYPE;
}
gboolean
ostree_sign_spki_clear_keys (OstreeSign *self, GError **error)
{
g_assert (OSTREE_IS_SIGN (self));
OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
if (!_ostree_sign_spki_is_initialized (sign, error))
return FALSE;
/* Clear secret key */
if (sign->secret_key != NULL)
{
gsize size;
gpointer data = g_bytes_unref_to_data (sign->secret_key, &size);
explicit_bzero (data, size);
sign->secret_key = NULL;
}
/* Clear already loaded trusted keys */
if (sign->public_keys != NULL)
{
g_list_free_full (sign->public_keys, (GDestroyNotify)g_bytes_unref);
sign->public_keys = NULL;
}
/* Clear already loaded revoked keys */
if (sign->revoked_keys != NULL)
{
g_list_free_full (sign->revoked_keys, (GDestroyNotify)g_bytes_unref);
sign->revoked_keys = NULL;
}
return TRUE;
}
/* Support 2 representations:
* base64 ascii -- secret key is passed as string
* raw key -- key is passed as bytes array
* */
gboolean
ostree_sign_spki_set_sk (OstreeSign *self, GVariant *secret_key, GError **error)
{
g_assert (OSTREE_IS_SIGN (self));
if (!ostree_sign_spki_clear_keys (self, error))
return FALSE;
OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
gsize n_elements = 0;
g_autofree guchar *secret_key_buf = NULL;
if (g_variant_is_of_type (secret_key, G_VARIANT_TYPE_STRING))
{
const gchar *sk_ascii = g_variant_get_string (secret_key, NULL);
secret_key_buf = g_base64_decode (sk_ascii, &n_elements);
}
else if (g_variant_is_of_type (secret_key, G_VARIANT_TYPE_BYTESTRING))
{
secret_key_buf
= (guchar *)g_variant_get_fixed_array (secret_key, &n_elements, sizeof (guchar));
}
else
{
return glnx_throw (error, "Unknown spki secret key type");
}
sign->secret_key = g_bytes_new_take (g_steal_pointer (&secret_key_buf), n_elements);
return TRUE;
}
/* Support 2 representations:
* base64 ascii -- public key is passed as string
* raw key -- key is passed as bytes array
* */
gboolean
ostree_sign_spki_set_pk (OstreeSign *self, GVariant *public_key, GError **error)
{
g_assert (OSTREE_IS_SIGN (self));
if (!ostree_sign_spki_clear_keys (self, error))
return FALSE;
return ostree_sign_spki_add_pk (self, public_key, error);
}
/* Support 2 representations:
* base64 ascii -- public key is passed as string
* raw key -- key is passed as bytes array
* */
gboolean
ostree_sign_spki_add_pk (OstreeSign *self, GVariant *public_key, GError **error)
{
g_assert (OSTREE_IS_SIGN (self));
OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
if (!_ostree_sign_spki_is_initialized (sign, error))
return FALSE;
g_autofree guint8 *key_owned = NULL;
const guint8 *key = NULL;
gsize n_elements = 0;
if (g_variant_is_of_type (public_key, G_VARIANT_TYPE_STRING))
{
const gchar *pk_ascii = g_variant_get_string (public_key, NULL);
key = key_owned = g_base64_decode (pk_ascii, &n_elements);
}
else if (g_variant_is_of_type (public_key, G_VARIANT_TYPE_BYTESTRING))
{
key = g_variant_get_fixed_array (public_key, &n_elements, sizeof (guchar));
}
else
{
return glnx_throw (error, "Unknown spki public key type");
}
g_autofree char *hex = g_malloc0 (n_elements * 2 + 1);
ot_bin2hex (hex, key, n_elements);
g_debug ("Read spki public key = %s", hex);
g_autoptr (GBytes) key_bytes = g_bytes_new_static (key, n_elements);
if (g_list_find_custom (sign->public_keys, key_bytes, g_bytes_compare) == NULL)
{
GBytes *new_key_bytes = g_bytes_new (key, n_elements);
sign->public_keys = g_list_prepend (sign->public_keys, new_key_bytes);
}
return TRUE;
}
/* Add revoked public key */
static gboolean
_spki_add_revoked (OstreeSign *self, GVariant *revoked_key, GError **error)
{
g_assert (OSTREE_IS_SIGN (self));
OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
g_autofree guint8 *key_owned = NULL;
const guint8 *key = NULL;
gsize n_elements = 0;
if (g_variant_is_of_type (revoked_key, G_VARIANT_TYPE_STRING))
{
const gchar *rk_ascii = g_variant_get_string (revoked_key, NULL);
key = key_owned = g_base64_decode (rk_ascii, &n_elements);
}
else if (g_variant_is_of_type (revoked_key, G_VARIANT_TYPE_BYTESTRING))
{
key = g_variant_get_fixed_array (revoked_key, &n_elements, sizeof (guchar));
}
else
{
return glnx_throw (error, "Unknown spki revoked key type");
}
g_autofree char *hex = g_malloc0 (n_elements * 2 + 1);
ot_bin2hex (hex, key, n_elements);
g_debug ("Read spki revoked key = %s", hex);
g_autoptr (GBytes) key_bytes = g_bytes_new_static (key, n_elements);
if (g_list_find_custom (sign->revoked_keys, key, g_bytes_compare) == NULL)
{
GBytes *new_key_bytes = g_bytes_new (key, n_elements);
sign->revoked_keys = g_list_prepend (sign->revoked_keys, new_key_bytes);
}
return TRUE;
}
static gboolean
_load_pk_from_stream (OstreeSign *self, GInputStream *key_stream_in, gboolean trusted,
GError **error)
{
if (key_stream_in == NULL)
return glnx_throw (error, "spki: unable to read from NULL key-data input stream");
gboolean ret = FALSE;
g_autoptr (OstreeBlobReader) blob_reader = ostree_sign_read_pk (self, key_stream_in);
g_assert (blob_reader);
/* Use simple file format with just a list of base64 public keys per line */
while (TRUE)
{
gboolean added = FALSE;
g_autoptr (GError) local_error = NULL;
g_autoptr (GBytes) blob = ostree_blob_reader_read_blob (blob_reader, NULL, &local_error);
if (local_error != NULL)
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
if (blob == NULL)
return ret;
/* Read the key itself */
g_autoptr (GVariant) pk = g_variant_new_from_bytes (G_VARIANT_TYPE_BYTESTRING, blob, FALSE);
if (trusted)
added = ostree_sign_spki_add_pk (self, pk, error);
else
added = _spki_add_revoked (self, pk, error);
g_autofree gchar *pk_printable = g_variant_print (pk, FALSE);
g_debug ("%s %s key: %s", added ? "Added" : "Invalid", trusted ? "public" : "revoked",
pk_printable);
/* Mark what we load at least one key */
if (added)
ret = TRUE;
}
return ret;
}
static gboolean
_load_pk_from_file (OstreeSign *self, const gchar *filename, gboolean trusted, GError **error)
{
g_debug ("Processing file '%s'", filename);
if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR))
{
g_debug ("Can't open file '%s' with public keys", filename);
return glnx_throw (error, "File object '%s' is not a regular file", filename);
}
g_autoptr (GFile) keyfile = keyfile = g_file_new_for_path (filename);
g_autoptr (GFileInputStream) key_stream_in = g_file_read (keyfile, NULL, error);
if (key_stream_in == NULL)
return FALSE;
if (!_load_pk_from_stream (self, G_INPUT_STREAM (key_stream_in), trusted, error))
{
if (error == NULL || *error == NULL)
return glnx_throw (error, "signature: spki: no valid keys in file '%s'", filename);
else
return FALSE;
}
return TRUE;
}
static gboolean
_spki_load_pk (OstreeSign *self, GVariant *options, gboolean trusted, GError **error)
{
gboolean ret = FALSE;
const gchar *custom_dir = NULL;
g_autoptr (GPtrArray) base_dirs = g_ptr_array_new_with_free_func (g_free);
g_autoptr (GPtrArray) spki_files = g_ptr_array_new_with_free_func (g_free);
if (g_variant_lookup (options, "basedir", "&s", &custom_dir))
{
/* Add custom directory */
g_ptr_array_add (base_dirs, g_strdup (custom_dir));
}
else
{
/* Default paths where to find files with public keys */
g_ptr_array_add (base_dirs, g_strdup ("/etc/ostree"));
g_ptr_array_add (base_dirs, g_strdup (DATADIR "/ostree"));
}
/* Scan all well-known directories and construct the list with file names to scan keys */
for (gint i = 0; i < base_dirs->len; i++)
{
g_autofree gchar *base_name
= g_build_filename ((gchar *)g_ptr_array_index (base_dirs, i),
trusted ? "trusted.spki" : "revoked.spki", NULL);
g_debug ("Check spki keys from file: %s", base_name);
g_autofree gchar *base_dir = g_strconcat (base_name, ".d", NULL);
g_ptr_array_add (spki_files, g_steal_pointer (&base_name));
g_autoptr (GDir) dir = g_dir_open (base_dir, 0, error);
if (dir == NULL)
{
g_clear_error (error);
continue;
}
const gchar *entry = NULL;
while ((entry = g_dir_read_name (dir)) != NULL)
{
gchar *filename = g_build_filename (base_dir, entry, NULL);
g_debug ("Check spki keys from file: %s", filename);
g_ptr_array_add (spki_files, filename);
}
}
/* Scan all well-known files */
for (gint i = 0; i < spki_files->len; i++)
{
if (!_load_pk_from_file (self, (gchar *)g_ptr_array_index (spki_files, i), trusted, error))
{
g_debug ("Problem with loading spki %s keys from `%s`", trusted ? "public" : "revoked",
(gchar *)g_ptr_array_index (spki_files, i));
g_clear_error (error);
}
else
ret = TRUE;
}
if (!ret && (error == NULL || *error == NULL))
return glnx_throw (error, "signature: spki: no keys loaded");
return ret;
}
/*
* options argument should be a{sv}:
* - filename -- single file to use to load keys from;
* - basedir -- directory containing subdirectories
* 'trusted.spki.d' and 'revoked.spki.d' with appropriate
* public keys. Used for testing and re-definition of system-wide
* directories if defaults are not suitable for any reason.
*/
gboolean
ostree_sign_spki_load_pk (OstreeSign *self, GVariant *options, GError **error)
{
const gchar *filename = NULL;
OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
if (!_ostree_sign_spki_is_initialized (sign, error))
return FALSE;
/* Read keys only from single file provided */
if (g_variant_lookup (options, "filename", "&s", &filename))
return _load_pk_from_file (self, filename, TRUE, error);
/* Load public keys from well-known directories and files */
if (!_spki_load_pk (self, options, TRUE, error))
return FALSE;
/* Load untrusted keys from well-known directories and files
* Ignore the failure from this function -- it is expected to have
* empty list of revoked keys.
* */
if (!_spki_load_pk (self, options, FALSE, error))
g_clear_error (error);
return TRUE;
}

View File

@ -0,0 +1,54 @@
/* vim:set et sw=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e2s: */
/*
* Copyright © 2019 Collabora Ltd.
* Copyright © 2024 Red Hat, Inc.
*
* 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/>.
*/
#pragma once
#include "ostree-sign.h"
G_BEGIN_DECLS
#define OSTREE_TYPE_SIGN_SPKI (_ostree_sign_spki_get_type ())
_OSTREE_PUBLIC
G_DECLARE_FINAL_TYPE (OstreeSignSpki, _ostree_sign_spki, OSTREE, SIGN_SPKI, GObject)
gboolean ostree_sign_spki_data (OstreeSign *self, GBytes *data, GBytes **signature,
GCancellable *cancellable, GError **error);
gboolean ostree_sign_spki_data_verify (OstreeSign *self, GBytes *data, GVariant *signatures,
char **out_success_message, GError **error);
const gchar *ostree_sign_spki_get_name (OstreeSign *self);
const gchar *ostree_sign_spki_metadata_key (OstreeSign *self);
const gchar *ostree_sign_spki_metadata_format (OstreeSign *self);
gboolean ostree_sign_spki_clear_keys (OstreeSign *self, GError **error);
gboolean ostree_sign_spki_set_sk (OstreeSign *self, GVariant *secret_key, GError **error);
gboolean ostree_sign_spki_set_pk (OstreeSign *self, GVariant *public_key, GError **error);
gboolean ostree_sign_spki_add_pk (OstreeSign *self, GVariant *public_key, GError **error);
gboolean ostree_sign_spki_load_pk (OstreeSign *self, GVariant *options, GError **error);
G_END_DECLS

View File

@ -39,11 +39,13 @@
#include "ostree-autocleanups.h"
#include "ostree-blob-reader-base64.h"
#include "ostree-blob-reader-pem.h"
#include "ostree-blob-reader-raw.h"
#include "ostree-core.h"
#include "ostree-sign-dummy.h"
#include "ostree-sign-ed25519.h"
#include "ostree-sign-private.h"
#include "ostree-sign-spki.h"
#include "ostree-sign.h"
#include "ostree-autocleanups.h"
@ -61,6 +63,9 @@ typedef struct
_sign_type sign_types[] = {
#if defined(HAVE_ED25519)
{ OSTREE_SIGN_NAME_ED25519, 0 },
#endif
#if defined(HAVE_SPKI)
{ OSTREE_SIGN_NAME_SPKI, 0 },
#endif
{ "dummy", 0 }
};
@ -69,6 +74,9 @@ enum
{
#if defined(HAVE_ED25519)
SIGN_ED25519,
#endif
#if defined(HAVE_SPKI)
SIGN_SPKI,
#endif
SIGN_DUMMY
};
@ -538,6 +546,10 @@ ostree_sign_get_by_name (const gchar *name, GError **error)
#if defined(HAVE_ED25519)
if (sign_types[SIGN_ED25519].type == 0)
sign_types[SIGN_ED25519].type = OSTREE_TYPE_SIGN_ED25519;
#endif
#if defined(HAVE_SPKI)
if (sign_types[SIGN_SPKI].type == 0)
sign_types[SIGN_SPKI].type = OSTREE_TYPE_SIGN_SPKI;
#endif
if (sign_types[SIGN_DUMMY].type == 0)
sign_types[SIGN_DUMMY].type = OSTREE_TYPE_SIGN_DUMMY;
@ -661,6 +673,10 @@ ostree_sign_read_pk (OstreeSign *self, GInputStream *stream)
#if defined(HAVE_ED25519)
if (OSTREE_IS_SIGN_ED25519 (self))
return OSTREE_BLOB_READER (_ostree_blob_reader_base64_new (stream));
#endif
#if defined(HAVE_SPKI)
if (OSTREE_IS_SIGN_SPKI (self))
return OSTREE_BLOB_READER (_ostree_blob_reader_pem_new (stream, "PUBLIC KEY"));
#endif
if (OSTREE_IS_SIGN_DUMMY (self))
return OSTREE_BLOB_READER (_ostree_blob_reader_raw_new (stream));
@ -684,6 +700,10 @@ ostree_sign_read_sk (OstreeSign *self, GInputStream *stream)
#if defined(HAVE_ED25519)
if (OSTREE_IS_SIGN_ED25519 (self))
return OSTREE_BLOB_READER (_ostree_blob_reader_base64_new (stream));
#endif
#if defined(HAVE_SPKI)
if (OSTREE_IS_SIGN_SPKI (self))
return OSTREE_BLOB_READER (_ostree_blob_reader_pem_new (stream, "PRIVATE KEY"));
#endif
if (OSTREE_IS_SIGN_DUMMY (self))
return OSTREE_BLOB_READER (_ostree_blob_reader_raw_new (stream));

View File

@ -44,6 +44,14 @@ G_BEGIN_DECLS
*/
#define OSTREE_SIGN_NAME_ED25519 "ed25519"
/**
* OSTREE_SIGN_NAME_SPKI:
* The name of the spki signing type.
*
* Since: 2024.7
*/
#define OSTREE_SIGN_NAME_SPKI "spki"
_OSTREE_PUBLIC
G_DECLARE_INTERFACE (OstreeSign, ostree_sign, OSTREE, SIGN, GObject)

View File

@ -0,0 +1,89 @@
/*
* 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_spki_init (void)
{
return true;
}
/* Validate a single spki 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_spki_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_OPENSSL)
gsize public_key_size;
const guint8 *public_key_buf = g_bytes_get_data (public_key, &public_key_size);
gsize signature_size;
const guint8 *signature_buf = g_bytes_get_data (signature, &signature_size);
if (public_key_size > OSTREE_SIGN_MAX_METADATA_SIZE)
return glnx_throw (
error, "Invalid public key of %" G_GSIZE_FORMAT " bytes, expected <= %" G_GSIZE_FORMAT,
public_key_size, (gsize)OSTREE_SIGN_MAX_METADATA_SIZE);
if (signature_size > OSTREE_SIGN_MAX_METADATA_SIZE)
return glnx_throw (
error, "Invalid signature of %" G_GSIZE_FORMAT " bytes, expected <= %" G_GSIZE_FORMAT,
signature_size, (gsize)OSTREE_SIGN_MAX_METADATA_SIZE);
EVP_MD_CTX *ctx = EVP_MD_CTX_new ();
if (!ctx)
return glnx_throw (error, "openssl: failed to allocate context");
const unsigned char *p = public_key_buf;
EVP_PKEY *pkey = d2i_PUBKEY (NULL, &p, public_key_size);
if (!pkey)
{
EVP_MD_CTX_free (ctx);
return glnx_throw (error, "openssl: Failed to initialize spki key");
}
if (EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, pkey) != 0
&& EVP_DigestVerify (ctx, signature_buf, signature_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, "spki signature validation requested, but support not compiled in");
#endif
}

View File

@ -27,6 +27,7 @@
#define USE_LIBSODIUM
#elif defined(HAVE_OPENSSL)
#include <openssl/evp.h>
#include <openssl/x509.h>
#define USE_OPENSSL
#endif
@ -39,10 +40,22 @@
// The variant type
#define OSTREE_SIGN_METADATA_ED25519_TYPE "aay"
// This key is stored inside commit metadata.
#define OSTREE_SIGN_METADATA_SPKI_KEY "ostree.sign.spki"
// The variant type
#define OSTREE_SIGN_METADATA_SPKI_TYPE "aay"
// Maximum size of metadata in bytes, in sync with OSTREE_MAX_METADATA_SIZE
#define OSTREE_SIGN_MAX_METADATA_SIZE (128 * 1024 * 1024)
bool otcore_ed25519_init (void);
gboolean otcore_validate_ed25519_signature (GBytes *data, GBytes *pubkey, GBytes *signature,
bool *out_valid, GError **error);
bool otcore_spki_init (void);
gboolean otcore_validate_spki_signature (GBytes *data, GBytes *public_key, GBytes *signature,
bool *out_valid, GError **error);
char *otcore_find_proc_cmdline_key (const char *cmdline, const char *key);
gboolean otcore_get_ostree_target (const char *cmdline, gboolean *is_aboot, char **out_target,
GError **error);

View File

@ -780,6 +780,40 @@ gen_ed25519_random_public()
openssl genpkey -algorithm ED25519 | openssl pkey -outform DER | tail -c 32 | base64
}
# Keys for spki signing tests
SPKIPUBLICPEM=
SPKISECRETPEM=
SPKIPUBLIC=
SPKISECRET=
gen_spki_keys ()
{
# Generate private key in PEM format
SPKISECRETPEM="$(mktemp -p ${test_tmpdir} ed448_sk_XXXXXX.pem)"
openssl genpkey -algorithm ed448 -outform PEM -out "${SPKISECRETPEM}"
SPKIPUBLICPEM="$(mktemp -p ${test_tmpdir} ed448_pk_XXXXXX.pem)"
openssl pkey -outform PEM -pubout -in "${SPKISECRETPEM}" -out "${SPKIPUBLICPEM}"
SPKIPUBLIC="$(openssl pkey -inform PEM -outform DER -pubin -pubout -in ${SPKIPUBLICPEM} | base64 -w 0)"
SPKISECRET="$(openssl pkey -inform PEM -outform DER -in ${SPKISECRETPEM} | base64 -w 0)"
echo "Generated ed448 keys:"
echo "public: ${SPKIPUBLIC}"
echo "secret: ${SPKISECRET}"
}
gen_spki_random_public()
{
openssl genpkey -algorithm ed448 | openssl pkey -pubout -outform DER | base64 -w 0
echo
}
gen_spki_random_public_pem()
{
openssl genpkey -algorithm ed448 | openssl pkey -pubout -outform PEM
}
is_bare_user_only_repo () {
grep -q 'mode=bare-user-only' $1/config
}

View File

@ -0,0 +1,61 @@
#!/bin/bash
#
# Copyright (C) 2019 Collabora Ltd.
#
# 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/>.
set -euo pipefail
. $(dirname $0)/libtest.sh
# This is explicitly opt in for testing
export OSTREE_DUMMY_SIGN_ENABLED=1
mkdir ${test_tmpdir}/repo
ostree_repo_init repo --mode="archive"
echo "Unsigned commit" > file.txt
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Unsigned commit'
COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
# Test `ostree sign` with dummy module first
DUMMYSIGN="dummysign"
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy ${COMMIT} ${DUMMYSIGN}
# Ensure that detached metadata really contain expected string
EXPECTEDSIGN="$(echo $DUMMYSIGN | hexdump -n 9 -e '8/1 "0x%.2x, " 1/1 " 0x%.2x"')"
${CMD_PREFIX} ostree --repo=repo show ${COMMIT} --print-detached-metadata-key=ostree.sign.dummy | grep -q -e "${EXPECTEDSIGN}"
tap_ok "Detached dummy signature added"
# Verify vith sign mechanism
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN}
tap_ok "dummy signature verified"
echo "Signed commit with dummy key: ${DUMMYSIGN}" >> file.txt
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Signed with dummy module' --sign=${DUMMYSIGN} --sign-type=dummy
COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN}
tap_ok "commit with dummy signing"
if ${CMD_PREFIX} env -u OSTREE_DUMMY_SIGN_ENABLED ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN} 2>err.txt; then
fatal "verified dummy signature without env"
fi
# FIXME the error message here is broken
#assert_file_has_content_literal err.txt 'dummy signature type is only for ostree testing'
assert_file_has_content_literal err.txt ' No valid signatures found'
tap_ok "dummy sig requires env"
tap_end

View File

@ -27,48 +27,8 @@ export OSTREE_DUMMY_SIGN_ENABLED=1
mkdir ${test_tmpdir}/repo
ostree_repo_init repo --mode="archive"
echo "Unsigned commit" > file.txt
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Unsigned commit'
COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
# Test `ostree sign` with dummy module first
# For multi-sign test
DUMMYSIGN="dummysign"
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy ${COMMIT} ${DUMMYSIGN}
# Ensure that detached metadata really contain expected string
EXPECTEDSIGN="$(echo $DUMMYSIGN | hexdump -n 9 -e '8/1 "0x%.2x, " 1/1 " 0x%.2x"')"
${CMD_PREFIX} ostree --repo=repo show ${COMMIT} --print-detached-metadata-key=ostree.sign.dummy | grep -q -e "${EXPECTEDSIGN}"
tap_ok "Detached dummy signature added"
# Verify vith sign mechanism
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN}
tap_ok "dummy signature verified"
echo "Signed commit with dummy key: ${DUMMYSIGN}" >> file.txt
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Signed with dummy module' --sign=${DUMMYSIGN} --sign-type=dummy
COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN}
tap_ok "commit with dummy signing"
if ${CMD_PREFIX} env -u OSTREE_DUMMY_SIGN_ENABLED ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN} 2>err.txt; then
fatal "verified dummy signature without env"
fi
# FIXME the error message here is broken
#assert_file_has_content_literal err.txt 'dummy signature type is only for ostree testing'
assert_file_has_content_literal err.txt ' No valid signatures found'
tap_ok "dummy sig requires env"
# tests below require libsodium support
if ! has_ostree_feature sign-ed25519; then
tap_ok "Detached ed25519 signature # SKIP due libsodium unavailability"
tap_ok "ed25519 signature verified # SKIP due libsodium unavailability"
tap_ok "multiple signing # SKIP due libsodium unavailability"
tap_ok "verify ed25519 keys file # SKIP due libsodium unavailability"
tap_ok "sign with ed25519 keys file # SKIP due libsodium unavailability"
tap_ok "verify ed25519 system-wide configuration # SKIP due libsodium unavailability"
tap_ok "verify ed25519 revoking keys mechanism # SKIP due libsodium unavailability"
exit 0
fi
# Test ostree sign with 'ed25519' module
gen_ed25519_keys

164
tests/test-signed-commit-spki.sh Executable file
View File

@ -0,0 +1,164 @@
#!/bin/bash
#
# Copyright (C) 2019 Collabora Ltd.
#
# 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/>.
set -euo pipefail
. $(dirname $0)/libtest.sh
# This is explicitly opt in for testing
export OSTREE_DUMMY_SIGN_ENABLED=1
mkdir ${test_tmpdir}/repo
ostree_repo_init repo --mode="archive"
# For multi-sign test
DUMMYSIGN="dummysign"
# Test ostree sign with 'spki' module
gen_spki_keys
PUBLIC=${SPKIPUBLIC}
SECRET=${SPKISECRET}
WRONG_PUBLIC="$(gen_spki_random_public)"
echo "PUBLIC = $PUBLIC"
echo "Signed commit with spki: ${SECRET}" >> file.txt
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s "Signed with spki module" --sign="${SECRET}" --sign-type=spki
COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
# Ensure that detached metadata contain signature
${CMD_PREFIX} ostree --repo=repo show ${COMMIT} --print-detached-metadata-key=ostree.sign.spki &>/dev/null
tap_ok "Detached spki signature added"
# Verify vith sign mechanism
if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} ${WRONG_PUBLIC}; then
exit 1
fi
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} ${PUBLIC}
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} ${PUBLIC} ${PUBLIC}
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} $(gen_spki_random_public) ${PUBLIC}
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} $(gen_spki_random_public) $(gen_spki_random_public) ${PUBLIC}
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} ${PUBLIC} $(gen_spki_random_public) $(gen_spki_random_public)
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} $(gen_spki_random_public) $(gen_spki_random_public) ${PUBLIC} $(gen_spki_random_public) $(gen_spki_random_public)
tap_ok "spki signature verified"
# Check if we are able to use all available modules to sign the same commit
echo "Unsigned commit for multi-sign" >> file.txt
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Unsigned commit'
COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
# Check if we have no signatures
for mod in "dummy" "spki"; do
if ostree --repo=repo show ${COMMIT} --print-detached-metadata-key=ostree.sign.${mod}; then
echo "Unexpected signature for ${mod} found"
exit 1
fi
done
# Sign with all available modules
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy ${COMMIT} ${DUMMYSIGN}
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=spki ${COMMIT} ${SECRET}
# and verify
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} ${PUBLIC} >out.txt
assert_file_has_content out.txt "spki: Signature verified successfully with key"
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN} >out.txt
assert_file_has_content out.txt "dummy: Signature verified"
tap_ok "multiple signing "
# Prepare files with public spki signatures
PUBKEYS="$(mktemp -p ${test_tmpdir} spki_XXXXXX.spki)"
# Test if file contain no keys
if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT}; then
exit 1
fi
# Test if have a problem with file object
if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${test_tmpdir} ${COMMIT}; then
exit 1
fi
# Test with single key in list
cat ${SPKIPUBLICPEM} > ${PUBKEYS}
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT} >out.txt
assert_file_has_content out.txt 'spki: Signature verified successfully'
# Test the file with multiple keys without a valid public key
for((i=0;i<100;i++)); do
# Generate a list with some public signatures
gen_spki_random_public_pem
done > ${PUBKEYS}
# Check if file contain no valid signatures
if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT} 2>err.txt; then
fatal "validated with no signatures"
fi
assert_file_has_content err.txt 'error:.* spki: Signature couldn.t be verified; tried 100 keys'
# Check if no valid signatures provided via args&file
if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT} ${WRONG_PUBLIC}; then
exit 1
fi
#Test keys file and public key
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT} ${PUBLIC}
# Add correct key into the list
cat "${SPKIPUBLICPEM}" >> ${PUBKEYS}
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT}
tap_ok "verify spki keys file"
# Check spki signing with secret file
echo "Unsigned commit for secret file usage" >> file.txt
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Unsigned commit'
COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
KEYFILE="$(mktemp -p ${test_tmpdir} secret_XXXXXX.spki)"
cat "${SPKISECRETPEM}" > ${KEYFILE}
# Sign
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=spki --keys-file=${KEYFILE} ${COMMIT}
# Verify
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT}
tap_ok "sign with spki keys file"
# Check the well-known places mechanism
mkdir -p ${test_tmpdir}/{trusted,revoked}.spki.d
for((i=0;i<100;i++)); do
# Generate some key files with random public signatures
gen_spki_random_public_pem > ${test_tmpdir}/trusted.spki.d/signature_$i
done
# Check no valid public keys are available
if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-dir=${test_tmpdir} ${COMMIT}; then
exit 1
fi
cat "${SPKIPUBLICPEM}" > ${test_tmpdir}/trusted.spki.d/correct
# Verify with correct key
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-dir=${test_tmpdir} ${COMMIT}
tap_ok "verify spki system-wide configuration"
# Add the public key into revoked list
cat "${SPKIPUBLICPEM}" > ${test_tmpdir}/revoked.spki.d/correct
# Check if public key is not valid anymore
if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-dir=${test_tmpdir} ${COMMIT}; then
exit 1
fi
rm -rf ${test_tmpdir}/{trusted,revoked}.spki.d
tap_ok "verify spki revoking keys mechanism"
tap_end