diff --git a/Makefile-libostree.am b/Makefile-libostree.am index 27c20b5d95..701653ed1c 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -268,6 +268,9 @@ libostree_1_la_SOURCES += \ 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 diff --git a/Makefile-tests.am b/Makefile-tests.am index abf61af2d0..415637a5d1 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -270,7 +270,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 +403,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) diff --git a/src/libostree/ostree-blob-reader-pem.c b/src/libostree/ostree-blob-reader-pem.c new file mode 100644 index 0000000000..9f0a29609f --- /dev/null +++ b/src/libostree/ostree-blob-reader-pem.c @@ -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 . + */ + +/* 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 + +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) + { + g_autofree gchar *line = NULL; + gsize length; + + 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 (); + } + } + + return NULL; +} + +GBytes * +ostree_blob_reader_pem_read_blob (OstreeBlobReader *self, GCancellable *cancellable, GError **error) +{ + g_autofree gchar *label = NULL; + g_autoptr (GBytes) blob = NULL; + OstreeBlobReaderPem *pself = OSTREE_BLOB_READER_PEM (self); + + 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); +} diff --git a/src/libostree/ostree-blob-reader-pem.h b/src/libostree/ostree-blob-reader-pem.h new file mode 100644 index 0000000000..04efb99d5d --- /dev/null +++ b/src/libostree/ostree-blob-reader-pem.h @@ -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 . + */ + +#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 diff --git a/src/libostree/ostree-blob-reader-private.h b/src/libostree/ostree-blob-reader-private.h new file mode 100644 index 0000000000..52b589be65 --- /dev/null +++ b/src/libostree/ostree-blob-reader-private.h @@ -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 . + */ + +#pragma once + +#ifndef __GI_SCANNER__ + +#include + +G_BEGIN_DECLS + +GBytes *_ostree_read_pem_block (GDataInputStream *stream, gchar **label, GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif diff --git a/tests/test-pem.c b/tests/test-pem.c new file mode 100644 index 0000000000..1bee2963fe --- /dev/null +++ b/tests/test-pem.c @@ -0,0 +1,70 @@ +/* + * 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 . + */ + +#include "config.h" + +#include + +#include "ostree-blob-reader-private.h" + +static const gchar pem_pubkey_ed25519[] + = "-----BEGIN PUBLIC KEY-----\n" + "MCowBQYDK2VwAyEANgkGafNSseN+1LXjTFJrfdu6N2qs5rlf9d3xlaVclgk=\n" + "-----END PUBLIC KEY-----\n"; +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 struct +{ + const gchar *pem_data; + gsize pem_size; + const guint8 *data; + gsize size; +} tests[] = { + { pem_pubkey_ed25519, sizeof (pem_pubkey_ed25519), pubkey_ed25519, sizeof (pubkey_ed25519) }, +}; + +static void +test_ostree_read_pem_block (void) +{ + for (gsize i = 0; i < G_N_ELEMENTS (tests); i++) + { + g_autoptr (GInputStream) stream = NULL; + g_autoptr (GDataInputStream) data_stream = NULL; + g_autofree char *label = NULL; + g_autoptr (GBytes) bytes = NULL; + g_autoptr (GError) error = NULL; + g_autoptr (GBytes) expected_bytes = NULL; + + stream = g_memory_input_stream_new_from_data (tests[i].pem_data, tests[i].pem_size, NULL); + data_stream = g_data_input_stream_new (stream); + + bytes = _ostree_read_pem_block (data_stream, &label, NULL, &error); + expected_bytes = g_bytes_new_static (tests[i].data, tests[i].size); + g_assert (g_bytes_equal (bytes, expected_bytes)); + } +} + +int +main (int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func ("/ostree_read_pem_block", test_ostree_read_pem_block); + return g_test_run (); +}