Compare commits

...

12 Commits

Author SHA1 Message Date
Daiki Ueno
47d529e8c3
Merge e83eda0aec into 8aaea0c65d 2024-12-20 14:43:22 -05:00
Colin Walters
8aaea0c65d
Merge pull request #3361 from cgwalters/release
Release 2024.10
2024-12-19 17:10:40 -05:00
Colin Walters
45ddf3b798
Merge pull request #3351 from cgwalters/fix-transient-root-doc
man: Note semantics combining `root.transient` with `composefs.enabled`
2024-12-19 16:23:48 -05:00
Colin Walters
aca6f17ff8 Post-release version bump
Signed-off-by: Colin Walters <walters@verbum.org>
2024-12-19 15:11:42 -05:00
Colin Walters
66f5a77ae6 Release 2024.10
Signed-off-by: Colin Walters <walters@verbum.org>
2024-12-19 15:10:12 -05:00
Colin Walters
786b38c2cf man: Note semantics combining root.transient with composefs.enabled
It's all quite confusing having to reason about both the pre-composefs
ostree and the composefs version. But hopefully soon we more firmly
leave behind that first legacy.

Signed-off-by: Colin Walters <walters@verbum.org>
2024-12-17 16:04:38 -05:00
Daiki Ueno
e83eda0aec 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>
2024-09-17 16:23:26 +09:00
Daiki Ueno
5583563b8f tests: Use tap_ok/tap_end in test-signed-commit.sh
Signed-off-by: Daiki Ueno <dueno@redhat.com>
2024-09-12 21:23:54 +09:00
Daiki Ueno
0fa1061779 sign: Add PEM reading facility
This adds a new class OstreePemReader, which reads PEM blocks from an
input stream.  This would be useful for the "x509" signing backend, as
the keys are typically stored in the PEM format.

Signed-off-by: Daiki Ueno <dueno@redhat.com>
2024-09-12 21:23:48 +09:00
Daiki Ueno
ef44e33def sign: Factor out logic to read key blobs
This defines a new interface OstreeBlobReader, which encapsulates the
key file parsing logic. This would make it easy to support custom file
formats such as PEM.

Signed-off-by: Daiki Ueno <dueno@redhat.com>
2024-09-11 16:12:19 +09:00
Daiki Ueno
62e54529e4 sign: Use explicit_bzero to clear secret key material
Suggested in:
https://github.com/ostreedev/ostree/pull/3278#discussion_r1675696052

Signed-off-by: Daiki Ueno <dueno@redhat.com>
2024-09-11 16:12:19 +09:00
Daiki Ueno
334e0b0d2e sign: Fix typo in error messages and comments
Signed-off-by: Daiki Ueno <dueno@redhat.com>
2024-09-11 16:12:19 +09:00
35 changed files with 2048 additions and 121 deletions

View File

@ -48,6 +48,7 @@ libostree_public_headers = \
src/libostree/ostree-kernel-args.h \
src/libostree/ostree-sign.h \
src/libostree/ostree-sign-ed25519.h \
src/libostree/ostree-blob-reader.h \
$(NULL)
# This one is generated via configure.ac, and the gtk-doc

View File

@ -175,9 +175,9 @@ endif # USE_GPGME
symbol_files = $(top_srcdir)/src/libostree/libostree-released.sym
# Uncomment this include when adding new development symbols.
#if BUILDOPT_IS_DEVEL_BUILD
#symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
#endif
if BUILDOPT_IS_DEVEL_BUILD
symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
endif
# http://blog.jgc.org/2007/06/escaping-comma-and-space-in-gnu-make.html
wl_versionscript_arg = -Wl,--version-script=
@ -261,7 +261,18 @@ 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 \
src/libostree/ostree-blob-reader-base64.c \
src/libostree/ostree-blob-reader-base64.h \
src/libostree/ostree-blob-reader-raw.c \
src/libostree/ostree-blob-reader-raw.h \
src/libostree/ostree-blob-reader-pem.c \
src/libostree/ostree-blob-reader-pem.h \
src/libostree/ostree-blob-reader-private.h \
$(NULL)
if USE_COMPOSEFS

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 \
@ -270,7 +282,7 @@ _installed_or_uninstalled_test_programs = tests/test-varint tests/test-ot-unix-u
tests/test-keyfile-utils tests/test-ot-opt-utils tests/test-ot-tool-util \
tests/test-checksum tests/test-lzma tests/test-rollsum \
tests/test-basic-c tests/test-sysroot-c tests/test-pull-c tests/test-repo tests/test-include-ostree-h tests/test-kargs \
tests/test-rfc2616-dates
tests/test-rfc2616-dates tests/test-pem
if USE_GPGME
_installed_or_uninstalled_test_programs += \
@ -403,6 +415,12 @@ tests_test_rfc2616_dates_SOURCES = \
tests_test_rfc2616_dates_CFLAGS = $(TESTS_CFLAGS)
tests_test_rfc2616_dates_LDADD = $(TESTS_LDADD)
tests_test_pem_SOURCES = \
src/libostree/ostree-blob-reader-pem.c \
tests/test-pem.c
tests_test_pem_CFLAGS = $(TESTS_CFLAGS)
tests_test_pem_LDADD = $(TESTS_LDADD)
noinst_PROGRAMS += tests/test-commit-sign-sh-ext
tests_test_commit_sign_sh_ext_CFLAGS = $(TESTS_CFLAGS)
tests_test_commit_sign_sh_ext_LDADD = $(TESTS_LDADD)

View File

@ -767,6 +767,15 @@ ostree_sign_metadata_key
ostree_sign_set_pk
ostree_sign_set_sk
ostree_sign_summary
ostree_sign_read_pk
ostree_sign_read_sk
<SUBSECTION Standard>
ostree_sign_get_type
</SECTION>
<SECTION>
<FILE>ostree-blob-reader</FILE>
ostree_blob_reader_read_blob
<SUBSECTION Standard>
ostree_blob_reader_get_type
</SECTION>

View File

@ -1,7 +1,7 @@
AC_PREREQ([2.63])
dnl To perform a release, follow the instructions in `docs/CONTRIBUTING.md`.
m4_define([year_version], [2024])
m4_define([release_version], [10])
m4_define([release_version], [11])
m4_define([package_version], [year_version.release_version])
AC_INIT([libostree], [package_version], [walters@verbum.org])
is_release_build=no
@ -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

@ -120,20 +120,25 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
<varlistentry>
<term><varname>root.transient</varname></term>
<listitem><para>A boolean value; the default is <literal>false</literal>.
If this is set to <literal>true</literal>, then the <literal>/</literal> filesystem will be a writable <literal>overlayfs</literal>,
with the upper directory being a hidden directory (in the underlying system root filesystem) that will persist across reboots by default.
However, changes will <emphasis>be discarded</emphasis> on OS updates!
Setting this flag to <literal>true</literal> requires composefs (See <literal>composefs.enabled</literal>).
When enabled, the root mount point <literal>/</literal> will be an overlayfs whose contents will be stored
in a tmpfs, and hence discarded on OS upgrade or reboot.
</para>
<para>
Enabling this option can be very useful for cases such as packages (dpkg/rpm/etc) that write content into <literal>/opt</literal>,
particularly where they expect the target to be writable at runtime. To make that work, ensure that your <literal>/opt</literal>
directory is *not* a symlink to <literal>/var/opt</literal>, but is just an empty directory.
</para>
<para>
Note the <literal>/usr</literal> mount point remains read-only by default. This option is independent of <literal>etc.transient</literal> and <literal>sysroot.readonly</literal>;
This option is independent of <literal>etc.transient</literal> and <literal>sysroot.readonly</literal>;
it is supported for example to have <literal>root.transient=true</literal> but <literal>etc.transient=false</literal> in which case changes to <literal>/etc</literal> continue
to persist across updates, with the default OSTree 3-way merge applied.
</para></listitem>
Also related to persistence it is important to emphasize that <literal>/sysroot</literal> (the physical root filesystem) is still persistent
by default; in-place OS upgrades can be applied.
</para>
<para>
Enabling this option can make it significantly easier to adopt an image-based model in some circumstances.
For example, if you have a configuration management system that is inspecting machine-specific state and
e.g. dynamically installing packages or applying configuration, it can more easily be adapted to
run on each boot, while still shifting a portion (or ideally most) image configuration to build time
as part of the base image/commit.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>composefs.enabled</varname></term>

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/rvokeded.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

@ -20,6 +20,14 @@
- uncomment the include in Makefile-libostree.am
*/
LIBOSTREE_2024.8 {
global:
ostree_sign_read_pk;
ostree_sign_read_sk;
ostree_blob_reader_get_type;
ostree_blob_reader_read_blob;
} LIBOSTREE_2024.7;
/* Stub section for the stable release *after* this development one; don't
* edit this other than to update the year. This is just a copy/paste
* source. Replace $LASTSTABLE with the last stable version, and $NEWVERSION

View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 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-blob-reader-base64.h"
struct _OstreeBlobReaderBase64
{
GDataInputStream parent_instance;
};
static void ostree_blob_reader_base64_iface_init (OstreeBlobReaderInterface *iface);
G_DEFINE_TYPE_WITH_CODE (OstreeBlobReaderBase64, _ostree_blob_reader_base64,
G_TYPE_DATA_INPUT_STREAM,
G_IMPLEMENT_INTERFACE (OSTREE_TYPE_BLOB_READER,
ostree_blob_reader_base64_iface_init));
static void
ostree_blob_reader_base64_iface_init (OstreeBlobReaderInterface *iface)
{
iface->read_blob = ostree_blob_reader_base64_read_blob;
}
static void
_ostree_blob_reader_base64_class_init (OstreeBlobReaderBase64Class *klass)
{
}
static void
_ostree_blob_reader_base64_init (OstreeBlobReaderBase64 *self)
{
}
OstreeBlobReaderBase64 *
_ostree_blob_reader_base64_new (GInputStream *stream)
{
return g_object_new (OSTREE_TYPE_BLOB_READER_BASE64, "base-stream", stream, NULL);
}
GBytes *
ostree_blob_reader_base64_read_blob (OstreeBlobReader *self, GCancellable *cancellable,
GError **error)
{
gsize len = 0;
g_autoptr (GError) local_error = NULL;
g_autofree char *line
= g_data_input_stream_read_line (G_DATA_INPUT_STREAM (self), &len, cancellable, &local_error);
if (local_error != NULL)
{
g_propagate_error (error, g_steal_pointer (&local_error));
return NULL;
}
if (line == NULL)
return NULL;
gsize n_elements;
g_base64_decode_inplace (line, &n_elements);
explicit_bzero (line + n_elements, len - n_elements);
return g_bytes_new_take (g_steal_pointer (&line), n_elements);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 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-blob-reader.h"
G_BEGIN_DECLS
#define OSTREE_TYPE_BLOB_READER_BASE64 (_ostree_blob_reader_base64_get_type ())
_OSTREE_PUBLIC
G_DECLARE_FINAL_TYPE (OstreeBlobReaderBase64, _ostree_blob_reader_base64, OSTREE,
BLOB_READER_BASE64, GDataInputStream);
_OSTREE_PUBLIC
OstreeBlobReaderBase64 *_ostree_blob_reader_base64_new (GInputStream *stream);
_OSTREE_PUBLIC
GBytes *ostree_blob_reader_base64_read_blob (OstreeBlobReader *self, GCancellable *cancellable,
GError **error);
G_END_DECLS

View File

@ -0,0 +1,225 @@
/*
* 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/>.
*/
/* This implements a simple parser of the PEM format defined in RFC
* 7468, which doesn't allow headers to be encoded alongside the data
* (unlike the legacy RFC 1421).
*/
#include "config.h"
#include "ostree-blob-reader-pem.h"
#include "ostree-blob-reader-private.h"
#include <string.h>
enum
{
PROP_0,
PROP_LABEL
};
struct _OstreeBlobReaderPem
{
GDataInputStream parent_instance;
gchar *label;
};
static void ostree_blob_reader_pem_iface_init (OstreeBlobReaderInterface *iface);
G_DEFINE_TYPE_WITH_CODE (OstreeBlobReaderPem, _ostree_blob_reader_pem, G_TYPE_DATA_INPUT_STREAM,
G_IMPLEMENT_INTERFACE (OSTREE_TYPE_BLOB_READER,
ostree_blob_reader_pem_iface_init));
static void
ostree_blob_reader_pem_iface_init (OstreeBlobReaderInterface *iface)
{
iface->read_blob = ostree_blob_reader_pem_read_blob;
}
static void
ostree_blob_reader_pem_set_property (GObject *object, guint prop_id, const GValue *value,
GParamSpec *pspec)
{
OstreeBlobReaderPem *self = OSTREE_BLOB_READER_PEM (object);
switch (prop_id)
{
case PROP_LABEL:
self->label = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
ostree_blob_reader_pem_get_property (GObject *object, guint prop_id, GValue *value,
GParamSpec *pspec)
{
OstreeBlobReaderPem *self = OSTREE_BLOB_READER_PEM (object);
switch (prop_id)
{
case PROP_LABEL:
g_value_set_string (value, self->label);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
ostree_blob_reader_pem_finalize (GObject *object)
{
OstreeBlobReaderPem *self = OSTREE_BLOB_READER_PEM (object);
g_free (self->label);
G_OBJECT_CLASS (_ostree_blob_reader_pem_parent_class)->finalize (object);
}
static void
_ostree_blob_reader_pem_class_init (OstreeBlobReaderPemClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->set_property = ostree_blob_reader_pem_set_property;
gobject_class->get_property = ostree_blob_reader_pem_get_property;
gobject_class->finalize = ostree_blob_reader_pem_finalize;
/*
* OstreeBlobReaderPem:label:
*
* The label to filter the PEM blocks.
*/
g_object_class_install_property (
gobject_class, PROP_LABEL,
g_param_spec_string ("label", "", "", "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}
static void
_ostree_blob_reader_pem_init (OstreeBlobReaderPem *self)
{
}
OstreeBlobReaderPem *
_ostree_blob_reader_pem_new (GInputStream *base, const gchar *label)
{
g_assert (G_IS_INPUT_STREAM (base));
return g_object_new (OSTREE_TYPE_BLOB_READER_PEM, "base-stream", base, "label", label, NULL);
}
enum PemInputState
{
PEM_INPUT_STATE_OUTER,
PEM_INPUT_STATE_INNER
};
#define PEM_SUFFIX "-----"
#define PEM_PREFIX_BEGIN "-----BEGIN "
#define PEM_PREFIX_END "-----END "
GBytes *
_ostree_read_pem_block (GDataInputStream *stream, gchar **label, GCancellable *cancellable,
GError **error)
{
enum PemInputState state = PEM_INPUT_STATE_OUTER;
g_autofree gchar *tmp_label = NULL;
g_autoptr (GString) buf = g_string_new ("");
while (TRUE)
{
gsize length;
g_autofree gchar *line = g_data_input_stream_read_line (stream, &length, cancellable, error);
if (!line)
break;
line = g_strstrip (line);
if (*line == '\0')
continue;
switch (state)
{
case PEM_INPUT_STATE_OUTER:
if (g_str_has_prefix (line, PEM_PREFIX_BEGIN) && g_str_has_suffix (line, PEM_SUFFIX))
{
const gchar *start = line + sizeof (PEM_PREFIX_BEGIN) - 1;
const gchar *end = g_strrstr (start + 1, PEM_SUFFIX);
tmp_label = g_strndup (start, end - start);
state = PEM_INPUT_STATE_INNER;
}
break;
case PEM_INPUT_STATE_INNER:
if (g_str_has_prefix (line, PEM_PREFIX_END) && g_str_has_suffix (line, PEM_SUFFIX))
{
const gchar *start = line + sizeof (PEM_PREFIX_END) - 1;
const gchar *end = g_strrstr (start + 1, PEM_SUFFIX);
if (strncmp (tmp_label, start, end - start) != 0)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Unmatched PEM header");
return NULL;
}
g_base64_decode_inplace (buf->str, &buf->len);
GBytes *result = g_bytes_new_take (buf->str, buf->len);
/* Don't leak the trailing encoded bytes */
explicit_bzero (buf->str + buf->len, buf->allocated_len - buf->len);
g_string_free (buf, FALSE);
buf = NULL;
if (label)
*label = g_steal_pointer (&tmp_label);
return result;
}
else
g_string_append (buf, line);
break;
default:
g_assert_not_reached ();
}
}
if (state != PEM_INPUT_STATE_OUTER)
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "PEM trailer not found");
return NULL;
}
GBytes *
ostree_blob_reader_pem_read_blob (OstreeBlobReader *self, GCancellable *cancellable, GError **error)
{
OstreeBlobReaderPem *pself = OSTREE_BLOB_READER_PEM (self);
g_autofree gchar *label = NULL;
g_autoptr (GBytes) blob
= _ostree_read_pem_block (G_DATA_INPUT_STREAM (self), &label, cancellable, error);
if (blob != NULL && !g_str_equal (label, pself->label))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unexpected label \"%s\"", label);
g_clear_pointer (&blob, g_bytes_unref);
}
return g_steal_pointer (&blob);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 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-blob-reader.h"
G_BEGIN_DECLS
#define OSTREE_TYPE_BLOB_READER_PEM (_ostree_blob_reader_pem_get_type ())
_OSTREE_PUBLIC
G_DECLARE_FINAL_TYPE (OstreeBlobReaderPem, _ostree_blob_reader_pem, OSTREE, BLOB_READER_PEM,
GDataInputStream);
_OSTREE_PUBLIC
OstreeBlobReaderPem *_ostree_blob_reader_pem_new (GInputStream *base, const gchar *label);
_OSTREE_PUBLIC
GBytes *ostree_blob_reader_pem_read_blob (OstreeBlobReader *self, GCancellable *cancellable,
GError **error);
G_END_DECLS

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 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
#ifndef __GI_SCANNER__
#include <gio/gio.h>
G_BEGIN_DECLS
GBytes *_ostree_read_pem_block (GDataInputStream *stream, gchar **label, GCancellable *cancellable,
GError **error);
G_END_DECLS
#endif

View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 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-blob-reader-raw.h"
struct _OstreeBlobReaderRaw
{
GDataInputStream parent_instance;
};
static void ostree_blob_reader_raw_iface_init (OstreeBlobReaderInterface *iface);
G_DEFINE_TYPE_WITH_CODE (OstreeBlobReaderRaw, _ostree_blob_reader_raw, G_TYPE_DATA_INPUT_STREAM,
G_IMPLEMENT_INTERFACE (OSTREE_TYPE_BLOB_READER,
ostree_blob_reader_raw_iface_init));
static void
ostree_blob_reader_raw_iface_init (OstreeBlobReaderInterface *iface)
{
iface->read_blob = ostree_blob_reader_raw_read_blob;
}
static void
_ostree_blob_reader_raw_class_init (OstreeBlobReaderRawClass *klass)
{
}
static void
_ostree_blob_reader_raw_init (OstreeBlobReaderRaw *self)
{
}
OstreeBlobReaderRaw *
_ostree_blob_reader_raw_new (GInputStream *stream)
{
return g_object_new (OSTREE_TYPE_BLOB_READER_RAW, "base-stream", stream, NULL);
}
GBytes *
ostree_blob_reader_raw_read_blob (OstreeBlobReader *self, GCancellable *cancellable, GError **error)
{
gsize len = 0;
g_autoptr (GError) local_error = NULL;
g_autofree char *line
= g_data_input_stream_read_line (G_DATA_INPUT_STREAM (self), &len, cancellable, &local_error);
if (local_error != NULL)
{
g_propagate_error (error, g_steal_pointer (&local_error));
return NULL;
}
if (line == NULL)
return NULL;
return g_bytes_new_take (g_steal_pointer (&line), len);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 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-blob-reader.h"
G_BEGIN_DECLS
#define OSTREE_TYPE_BLOB_READER_RAW (_ostree_blob_reader_raw_get_type ())
_OSTREE_PUBLIC
G_DECLARE_FINAL_TYPE (OstreeBlobReaderRaw, _ostree_blob_reader_raw, OSTREE, BLOB_READER_RAW,
GDataInputStream);
_OSTREE_PUBLIC
OstreeBlobReaderRaw *_ostree_blob_reader_raw_new (GInputStream *stream);
_OSTREE_PUBLIC
GBytes *ostree_blob_reader_raw_read_blob (OstreeBlobReader *self, GCancellable *cancellable,
GError **error);
G_END_DECLS

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 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-blob-reader.h"
G_DEFINE_INTERFACE (OstreeBlobReader, ostree_blob_reader, G_TYPE_OBJECT);
static void
ostree_blob_reader_default_init (OstreeBlobReaderInterface *iface)
{
g_debug ("OstreeBlobReader initialization");
}
GBytes *
ostree_blob_reader_read_blob (OstreeBlobReader *self, GCancellable *cancellable, GError **error)
{
g_assert (OSTREE_IS_BLOB_READER (self));
return OSTREE_BLOB_READER_GET_IFACE (self)->read_blob (self, cancellable, error);
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 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-types.h"
#include <gio/gio.h>
G_BEGIN_DECLS
#define OSTREE_TYPE_BLOB_READER (ostree_blob_reader_get_type ())
_OSTREE_PUBLIC
G_DECLARE_INTERFACE (OstreeBlobReader, ostree_blob_reader, OSTREE, BLOB_READER, GObject)
struct _OstreeBlobReaderInterface
{
GTypeInterface g_iface;
GBytes *(*read_blob) (OstreeBlobReader *self, GCancellable *cancellable, GError **error);
};
_OSTREE_PUBLIC
GBytes *ostree_blob_reader_read_blob (OstreeBlobReader *self, GCancellable *cancellable,
GError **error);
G_END_DECLS

View File

@ -27,6 +27,7 @@
#include "otcore.h"
#include <libglnx.h>
#include <ot-checksum-utils.h>
#include <string.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "OSTreeSign"
@ -152,7 +153,7 @@ ostree_sign_ed25519_data (OstreeSign *self, GBytes *data, GBytes **signature,
if (!pkey)
{
EVP_MD_CTX_free (ctx);
return glnx_throw (error, "openssl: Failed to initialize ed5519 key");
return glnx_throw (error, "openssl: Failed to initialize ed25519 key");
}
size_t len;
@ -320,7 +321,7 @@ ostree_sign_ed25519_clear_keys (OstreeSign *self, GError **error)
/* Clear secret key */
if (sign->secret_key != NULL)
{
memset (sign->secret_key, 0, OSTREE_SIGN_ED25519_SECKEY_SIZE);
explicit_bzero (sign->secret_key, OSTREE_SIGN_ED25519_SECKEY_SIZE);
g_free (sign->secret_key);
sign->secret_key = NULL;
}
@ -451,14 +452,25 @@ _ed25519_add_revoked (OstreeSign *self, GVariant *revoked_key, GError **error)
{
g_assert (OSTREE_IS_SIGN (self));
if (!g_variant_is_of_type (revoked_key, G_VARIANT_TYPE_STRING))
return glnx_throw (error, "Unknown ed25519 revoked key type");
OstreeSignEd25519 *sign = _ostree_sign_ed25519_get_instance_private (OSTREE_SIGN_ED25519 (self));
const gchar *rk_ascii = g_variant_get_string (revoked_key, NULL);
g_autofree guint8 *key_owned = NULL;
const guint8 *key = NULL;
gsize n_elements = 0;
g_autofree guint8 *key = g_base64_decode (rk_ascii, &n_elements);
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 ed25519 revoked key type");
}
if (!validate_length (n_elements, OSTREE_SIGN_ED25519_PUBKEY_SIZE, error))
return glnx_prefix_error (error, "Incorrect ed25519 revoked key");
@ -477,22 +489,24 @@ _ed25519_add_revoked (OstreeSign *self, GVariant *revoked_key, GError **error)
}
static gboolean
_load_pk_from_stream (OstreeSign *self, GDataInputStream *key_data_in, gboolean trusted,
_load_pk_from_stream (OstreeSign *self, GInputStream *key_stream_in, gboolean trusted,
GError **error)
{
if (key_data_in == NULL)
if (key_stream_in == NULL)
return glnx_throw (error, "ed25519: 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)
{
gsize len = 0;
g_autoptr (GVariant) pk = NULL;
gboolean added = FALSE;
g_autoptr (GError) local_error = NULL;
g_autofree char *line = g_data_input_stream_read_line (key_data_in, &len, NULL, &local_error);
g_autoptr (GBytes) blob = ostree_blob_reader_read_blob (blob_reader, NULL, &local_error);
if (local_error != NULL)
{
@ -500,19 +514,20 @@ _load_pk_from_stream (OstreeSign *self, GDataInputStream *key_data_in, gboolean
return FALSE;
}
if (line == NULL)
if (blob == NULL)
return ret;
/* Read the key itself */
/* base64 encoded key */
pk = g_variant_new_string (line);
pk = g_variant_new_from_bytes (G_VARIANT_TYPE_BYTESTRING, blob, FALSE);
if (trusted)
added = ostree_sign_ed25519_add_pk (self, pk, error);
else
added = _ed25519_add_revoked (self, pk, error);
g_debug ("%s %s key: %s", added ? "Added" : "Invalid", trusted ? "public" : "revoked", line);
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)
@ -529,7 +544,6 @@ _load_pk_from_file (OstreeSign *self, const gchar *filename, gboolean trusted, G
g_autoptr (GFile) keyfile = NULL;
g_autoptr (GFileInputStream) key_stream_in = NULL;
g_autoptr (GDataInputStream) key_data_in = NULL;
if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR))
{
@ -542,10 +556,7 @@ _load_pk_from_file (OstreeSign *self, const gchar *filename, gboolean trusted, G
if (key_stream_in == NULL)
return FALSE;
key_data_in = g_data_input_stream_new (G_INPUT_STREAM (key_stream_in));
g_assert (key_data_in != NULL);
if (!_load_pk_from_stream (self, key_data_in, trusted, error))
if (!_load_pk_from_stream (self, G_INPUT_STREAM (key_stream_in), trusted, error))
{
if (error == NULL || *error == NULL)
return glnx_throw (error, "signature: ed25519: no valid keys in file '%s'", filename);

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

@ -38,10 +38,14 @@
#include <unistd.h>
#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"
@ -59,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 }
};
@ -67,6 +74,9 @@ enum
{
#if defined(HAVE_ED25519)
SIGN_ED25519,
#endif
#if defined(HAVE_SPKI)
SIGN_SPKI,
#endif
SIGN_DUMMY
};
@ -536,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;
@ -641,3 +655,57 @@ ostree_sign_summary (OstreeSign *self, OstreeRepo *repo, GVariant *keys, GCancel
{
return _ostree_sign_summary_at (self, repo, repo->repo_dir_fd, keys, cancellable, error);
}
/**
* ostree_sign_read_pk:
* @self: Self
* @stream: a #GInputStream
*
* Start reading public keys from a stream.
*
* Returns: (transfer full): a #OstreamBlobReader or %NULL on error
*
* Since: 2024.8
*/
OstreeBlobReader *
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));
return NULL;
}
/**
* ostree_sign_read_sk:
* @self: Self
* @stream: a #GInputStream
*
* Start reading secret keys from a stream.
*
* Returns: (transfer full): a #OstreamBlobReader or %NULL on error
*
* Since: 2024.8
*/
OstreeBlobReader *
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));
return NULL;
}

View File

@ -27,6 +27,7 @@
#include <glib-object.h>
#include <glib.h>
#include "ostree-blob-reader.h"
#include "ostree-ref.h"
#include "ostree-remote.h"
#include "ostree-types.h"
@ -43,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)
@ -113,4 +122,11 @@ OstreeSign *ostree_sign_get_by_name (const gchar *name, GError **error);
_OSTREE_PUBLIC
gboolean ostree_sign_summary (OstreeSign *self, OstreeRepo *repo, GVariant *keys,
GCancellable *cancellable, GError **error);
_OSTREE_PUBLIC
OstreeBlobReader *ostree_sign_read_pk (OstreeSign *self, GInputStream *stream);
_OSTREE_PUBLIC
OstreeBlobReader *ostree_sign_read_sk (OstreeSign *self, GInputStream *stream);
G_END_DECLS

View File

@ -97,7 +97,7 @@ otcore_validate_ed25519_signature (GBytes *data, GBytes *public_key, GBytes *sig
if (!pkey)
{
EVP_MD_CTX_free (ctx);
return glnx_throw (error, "openssl: Failed to initialize ed5519 key");
return glnx_throw (error, "openssl: Failed to initialize ed25519 key");
}
if (EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, pkey) != 0
&& EVP_DigestVerify (ctx, signature_buf, OSTREE_SIGN_ED25519_SIG_SIZE,

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

@ -961,19 +961,43 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio
goto out;
}
// Load each base64 encoded private key in a file and sign with it.
// Load each encoded private key in a file and sign with it.
for (char **iter = opt_key_files; iter && *iter; iter++)
{
const char *path = *iter;
g_autofree char *b64key
= glnx_file_get_contents_utf8_at (AT_FDCWD, path, NULL, NULL, error);
if (!b64key)
g_autoptr (GFile) keyfile = NULL;
g_autoptr (GFileInputStream) key_stream_in = NULL;
g_autoptr (OstreeBlobReader) blob_reader = NULL;
g_autoptr (GBytes) blob = NULL;
g_autoptr (GError) local_error = NULL;
g_autoptr (GVariant) secret_key = NULL;
keyfile = g_file_new_for_path (path);
key_stream_in = g_file_read (keyfile, NULL, error);
if (key_stream_in == NULL)
goto out;
g_assert (sign);
blob_reader = ostree_sign_read_sk (sign, G_INPUT_STREAM (key_stream_in));
if (blob_reader == NULL)
goto out;
blob = ostree_blob_reader_read_blob (blob_reader, cancellable, &local_error);
if (local_error != NULL)
{
g_propagate_prefixed_error (error, g_steal_pointer (&local_error), "Reading %s",
path);
goto out;
}
if (blob == NULL)
{
g_prefix_error (error, "Reading %s", path);
goto out;
}
g_autoptr (GVariant) secret_key = g_variant_new_string (b64key);
g_assert (sign);
// Pass the key as a bytestring
secret_key = g_variant_new_from_bytes (G_VARIANT_TYPE_BYTESTRING, blob, FALSE);
if (!ostree_sign_set_sk (sign, secret_key, error))
goto out;

View File

@ -188,7 +188,7 @@ ostree_builtin_sign (int argc, char **argv, OstreeCommandInvocation *invocation,
{
g_autoptr (GFile) keyfile = NULL;
g_autoptr (GFileInputStream) key_stream_in = NULL;
g_autoptr (GDataInputStream) key_data_in = NULL;
g_autoptr (OstreeBlobReader) blob_reader = NULL;
if (!g_file_test (opt_filename, G_FILE_TEST_IS_REGULAR))
{
@ -203,25 +203,24 @@ ostree_builtin_sign (int argc, char **argv, OstreeCommandInvocation *invocation,
if (key_stream_in == NULL)
goto out;
key_data_in = g_data_input_stream_new (G_INPUT_STREAM (key_stream_in));
g_assert (key_data_in != NULL);
blob_reader = ostree_sign_read_sk (sign, G_INPUT_STREAM (key_stream_in));
g_assert (blob_reader != NULL);
/* Use simple file format with just a list of base64 public keys per line */
while (TRUE)
{
gsize len = 0;
g_autofree char *line
= g_data_input_stream_read_line (key_data_in, &len, NULL, error);
g_autoptr (GBytes) blob
= ostree_blob_reader_read_blob (blob_reader, cancellable, error);
g_autoptr (GVariant) sk = NULL;
if (*error != NULL)
goto out;
if (line == NULL)
if (blob == NULL)
break;
// Pass the key as a string
sk = g_variant_new_string (line);
sk = g_variant_new_from_bytes (G_VARIANT_TYPE_BYTESTRING, blob, FALSE);
if (!ostree_sign_set_sk (sign, sk, error))
{
ret = FALSE;

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
}

124
tests/test-pem.c Normal file
View File

@ -0,0 +1,124 @@
/*
* 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 <gio/gio.h>
#include "ostree-blob-reader-private.h"
static const guint8 pubkey_ed25519[]
= { 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, 0x36, 0x09, 0x06,
0x69, 0xf3, 0x52, 0xb1, 0xe3, 0x7e, 0xd4, 0xb5, 0xe3, 0x4c, 0x52, 0x6b, 0x7d, 0xdb, 0xba,
0x37, 0x6a, 0xac, 0xe6, 0xb9, 0x5f, 0xf5, 0xdd, 0xf1, 0x95, 0xa5, 0x5c, 0x96, 0x09 };
static const gchar pem_pubkey_ed25519[]
= "-----BEGIN PUBLIC KEY-----\n"
"MCowBQYDK2VwAyEANgkGafNSseN+1LXjTFJrfdu6N2qs5rlf9d3xlaVclgk=\n"
"-----END PUBLIC KEY-----\n";
static const gchar pem_pubkey_ed25519_whitespace[]
= "-----BEGIN PUBLIC KEY-----\n"
" \n"
"MCowBQYDK2VwAyEANgkGafNSseN+1LXjTFJrfdu6N2qs5rlf9d3xlaVclgk=\n"
"-----END PUBLIC KEY-----\n";
static const gchar pem_pubkey_empty[] = "";
static const gchar pem_pubkey_ed25519_no_trailer[]
= "-----BEGIN PUBLIC KEY-----\n"
"MCowBQYDK2VwAyEANgkGafNSseN+1LXjTFJrfdu6N2qs5rlf9d3xlaVclgk=\n";
static const gchar pem_pubkey_ed25519_label_mismatch[]
= "-----BEGIN PUBLIC KEY X-----\n"
"MCowBQYDK2VwAyEANgkGafNSseN+1LXjTFJrfdu6N2qs5rlf9d3xlaVclgk=\n"
"-----END PUBLIC KEY Y-----\n";
static void
test_ostree_read_pem_block_valid (void)
{
static const struct
{
const gchar *pem_data;
gsize pem_size;
const gchar *label;
const guint8 *data;
gsize size;
} tests[] = {
{ pem_pubkey_ed25519, sizeof (pem_pubkey_ed25519), "PUBLIC KEY", pubkey_ed25519,
sizeof (pubkey_ed25519) },
{ pem_pubkey_ed25519_whitespace, sizeof (pem_pubkey_ed25519_whitespace), "PUBLIC KEY",
pubkey_ed25519, sizeof (pubkey_ed25519) },
{ pem_pubkey_empty, sizeof (pem_pubkey_empty), NULL, NULL, 0 },
};
for (gsize i = 0; i < G_N_ELEMENTS (tests); i++)
{
g_autoptr (GInputStream) stream
= g_memory_input_stream_new_from_data (tests[i].pem_data, tests[i].pem_size, NULL);
g_autoptr (GDataInputStream) data_stream = g_data_input_stream_new (stream);
g_autofree char *label = NULL;
g_autoptr (GBytes) bytes = NULL;
g_autoptr (GError) error = NULL;
bytes = _ostree_read_pem_block (data_stream, &label, NULL, &error);
g_assert_no_error (error);
g_assert_cmpstr (label, ==, tests[i].label);
if (tests[i].data)
{
g_autoptr (GBytes) expected_bytes = g_bytes_new_static (tests[i].data, tests[i].size);
g_assert (g_bytes_equal (bytes, expected_bytes));
}
}
}
static void
test_ostree_read_pem_block_invalid (void)
{
static const struct
{
const gchar *pem_data;
gsize pem_size;
} tests[] = {
{ pem_pubkey_ed25519_no_trailer, sizeof (pem_pubkey_ed25519_no_trailer) },
{ pem_pubkey_ed25519_label_mismatch, sizeof (pem_pubkey_ed25519_label_mismatch) },
};
for (gsize i = 0; i < G_N_ELEMENTS (tests); i++)
{
g_autoptr (GInputStream) stream
= g_memory_input_stream_new_from_data (tests[i].pem_data, tests[i].pem_size, NULL);
g_autoptr (GDataInputStream) data_stream = g_data_input_stream_new (stream);
g_autofree char *label = NULL;
g_autoptr (GBytes) bytes = NULL;
g_autoptr (GError) error = NULL;
bytes = _ostree_read_pem_block (data_stream, &label, NULL, &error);
g_assert_null (bytes);
g_assert_null (label);
g_assert_nonnull (error);
}
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/ostree_read_pem_block/valid", test_ostree_read_pem_block_valid);
g_test_add_func ("/ostree_read_pem_block/invalid", test_ostree_read_pem_block_invalid);
return g_test_run ();
}

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

@ -21,56 +21,14 @@ set -euo pipefail
. $(dirname $0)/libtest.sh
echo "1..11"
# 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
# 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}"
echo "ok Detached dummy signature added"
# Verify vith sign mechanism
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN}
echo "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}
echo "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'
echo "ok dummy sig requires env"
# tests below require libsodium support
if ! has_ostree_feature sign-ed25519; then
echo "ok Detached ed25519 signature # SKIP due libsodium unavailability"
echo "ok ed25519 signature verified # SKIP due libsodium unavailability"
echo "ok multiple signing # SKIP due libsodium unavailability"
echo "ok verify ed25519 keys file # SKIP due libsodium unavailability"
echo "ok sign with ed25519 keys file # SKIP due libsodium unavailability"
echo "ok verify ed25519 system-wide configuration # SKIP due libsodium unavailability"
echo "ok verify ed25519 revoking keys mechanism # SKIP due libsodium unavailability"
exit 0
fi
# Test ostree sign with 'ed25519' module
gen_ed25519_keys
@ -87,7 +45,7 @@ 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.ed25519 &>/dev/null
echo "ok Detached ed25519 signature added"
tap_ok "Detached ed25519 signature added"
# Verify vith sign mechanism
if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} ${WRONG_PUBLIC}; then
@ -99,9 +57,9 @@ ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed255
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} $(gen_ed25519_random_public) $(gen_ed25519_random_public) ${PUBLIC}
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} ${PUBLIC} $(gen_ed25519_random_public) $(gen_ed25519_random_public)
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} $(gen_ed25519_random_public) $(gen_ed25519_random_public) ${PUBLIC} $(gen_ed25519_random_public) $(gen_ed25519_random_public)
echo "ok ed25519 signature verified"
tap_ok "ed25519 signature verified"
# Check if we able to use all available modules to sign the same commit
# 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)"
@ -121,7 +79,7 @@ ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed2551
assert_file_has_content out.txt "ed25519: 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"
echo "ok multiple signing "
tap_ok "multiple signing "
# Prepare files with public ed25519 signatures
PUBKEYS="$(mktemp -p ${test_tmpdir} ed25519_XXXXXX.ed25519)"
@ -163,7 +121,7 @@ ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed2551
echo ${PUBLIC} >> ${PUBKEYS}
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT}
echo "ok verify ed25519 keys file"
tap_ok "verify ed25519 keys file"
# Check ed25519 signing with secret file
echo "Unsigned commit for secret file usage" >> file.txt
@ -176,7 +134,7 @@ echo "${SECRET}" > ${KEYFILE}
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=ed25519 --keys-file=${KEYFILE} ${COMMIT}
# Verify
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT}
echo "ok sign with ed25519 keys file"
tap_ok "sign with ed25519 keys file"
# Check the well-known places mechanism
mkdir -p ${test_tmpdir}/{trusted,revoked}.ed25519.d
@ -192,7 +150,7 @@ echo ${PUBLIC} > ${test_tmpdir}/trusted.ed25519.d/correct
# Verify with correct key
${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-dir=${test_tmpdir} ${COMMIT}
echo "ok verify ed25519 system-wide configuration"
tap_ok "verify ed25519 system-wide configuration"
# Add the public key into revoked list
echo ${PUBLIC} > ${test_tmpdir}/revoked.ed25519.d/correct
@ -201,4 +159,6 @@ if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed2
exit 1
fi
rm -rf ${test_tmpdir}/{trusted,revoked}.ed25519.d
echo "ok verify ed25519 revoking keys mechanism"
tap_ok "verify ed25519 revoking keys mechanism"
tap_end

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