From 2303202c86c96768abb42728b2e5b2090b9d0198 Mon Sep 17 00:00:00 2001 From: Denis Pynkin Date: Fri, 9 Aug 2019 22:07:57 +0300 Subject: [PATCH] sign: API changes for public keys and CLI keys format API changes: - added function `ostree_sign_add_pk()` for multiple public keys using. - `ostree_sign_set_pk()` now substitutes all previously added keys. - added function `ostree_sign_load_pk()` allowed to load keys from file. - `ostree_sign_ed25519_load_pk()` able to load the raw keys list from file. - use base64 encoded public and private ed25519 keys for CLI and keys file. Signed-off-by: Denis Pynkin --- apidoc/ostree-sections.txt | 1 + src/libostree/libostree-devel.sym | 1 + src/libostree/ostree-repo-pull.c | 17 +- src/libostree/ostree-sign-dummy.h | 2 - src/libostree/ostree-sign-ed25519.c | 238 +++++++++++++++++++++++++--- src/libostree/ostree-sign-ed25519.h | 9 +- src/libostree/ostree-sign.c | 19 ++- src/libostree/ostree-sign.h | 13 +- src/ostree/ot-builtin-commit.c | 9 +- src/ostree/ot-builtin-sign.c | 14 +- tests/test-signed-commit.sh | 8 +- 11 files changed, 278 insertions(+), 53 deletions(-) diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index cfc4a340..806bd1a7 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -719,6 +719,7 @@ ostree_sign_get_by_name ostree_sign_get_name ostree_sign_detached_metadata_append ostree_sign_metadata_verify +ostree_sign_add_pk ostree_sign_load_pk ostree_sign_set_pk ostree_sign_set_sk diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index 4066f383..8be5a3bf 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -33,6 +33,7 @@ global: ostree_sign_metadata_verify; ostree_sign_load_pk; ostree_sign_set_pk; + ostree_sign_add_pk; ostree_sign_set_sk; ostree_sign_dummy_get_type; ostree_sign_ed25519_get_type; diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index 507bcc2e..781c2458 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -1508,13 +1508,21 @@ ostree_verify_unwritten_commit (OtPullData *pull_data, gboolean ret = FALSE; g_autoptr(GBytes) signed_data = g_variant_get_data_as_bytes (commit); /* list all signature types in detached metadata and check if signed by any? */ - GStrv names = ostree_sign_list_names(); + g_auto(GStrv) names = ostree_sign_list_names(); for (guint i=0; i < g_strv_length (names); i++) { - g_autoptr (OstreeSign) sign = ostree_sign_get_by_name (names[i], error); + g_autoptr (OstreeSign) sign = NULL; g_autoptr(GVariant) signatures = NULL; - g_autofree gchar *signature_key = ostree_sign_metadata_key (sign); - g_autofree GVariantType *signature_format = (GVariantType *) ostree_sign_metadata_format (sign); + g_autofree gchar *signature_key = NULL; + g_autofree GVariantType *signature_format = NULL; + + if ((sign = ostree_sign_get_by_name (names[i], error)) == NULL) + { + g_error_free (*error); + continue; + } + signature_key = ostree_sign_metadata_key (sign); + signature_format = (GVariantType *) ostree_sign_metadata_format (sign); signatures = g_variant_lookup_value (detached_metadata, signature_key, @@ -1531,7 +1539,6 @@ ostree_verify_unwritten_commit (OtPullData *pull_data, )) ret = TRUE; } - g_strfreev(names); return ret; } diff --git a/src/libostree/ostree-sign-dummy.h b/src/libostree/ostree-sign-dummy.h index 8bbd407d..73bad135 100644 --- a/src/libostree/ostree-sign-dummy.h +++ b/src/libostree/ostree-sign-dummy.h @@ -57,7 +57,5 @@ gboolean ostree_sign_dummy_metadata_verify (OstreeSign *self, gboolean ostree_sign_dummy_set_signature (OstreeSign *self, GVariant *key, GError **error); -void ostree_sign_dummy_finalize (GObject *gobject); - G_END_DECLS diff --git a/src/libostree/ostree-sign-ed25519.c b/src/libostree/ostree-sign-ed25519.c index 6a110104..779c0b27 100644 --- a/src/libostree/ostree-sign-ed25519.c +++ b/src/libostree/ostree-sign-ed25519.c @@ -30,17 +30,25 @@ #include #endif +#define G_LOG_DOMAIN "OSTreeSign" + #define OSTREE_SIGN_ED25519_NAME "ed25519" #define OSTREE_SIGN_METADATA_ED25519_KEY "ostree.sign.ed25519" #define OSTREE_SIGN_METADATA_ED25519_TYPE "aay" +#if 0 +#define SIGNIFY_COMMENT_HEADER "untrusted comment:" +#define SIGNIFY_ID_LENGTH 8 +#define SIGNIFY_MAGIC_ED25519 "Ed" +#endif + struct _OstreeSignEd25519 { GObject parent; gboolean initialized; guchar *secret_key; - guchar *public_key; + GList *public_keys; }; static void @@ -61,12 +69,32 @@ ostree_sign_ed25519_iface_init (OstreeSignInterface *self) self->metadata_verify = ostree_sign_ed25519_metadata_verify; self->set_sk = ostree_sign_ed25519_set_sk; self->set_pk = ostree_sign_ed25519_set_pk; + self->add_pk = ostree_sign_ed25519_add_pk; + self->load_pk = ostree_sign_ed25519_load_pk; +} + +static void +ostree_sign_ed25519_finalize (GObject *object) +{ + g_debug ("%s enter", __FUNCTION__); +#if 0 + OstreeSignEd25519 *self = OSTREE_SIGN_ED25519 (object); + + if (self->public_keys != NULL) + g_list_free_full (self->public_keys, g_object_unref); + if (self->secret_key != NULL) + free(self->secret_key); +#endif + G_OBJECT_CLASS (ostree_sign_ed25519_parent_class)->finalize (object); } static void ostree_sign_ed25519_class_init (OstreeSignEd25519Class *self) { g_debug ("%s enter", __FUNCTION__); + GObjectClass *object_class = G_OBJECT_CLASS (self); + + object_class->finalize = ostree_sign_ed25519_finalize; } static void @@ -76,7 +104,7 @@ ostree_sign_ed25519_init (OstreeSignEd25519 *self) self->initialized = TRUE; self->secret_key = NULL; - self->public_key = NULL; + self->public_keys = NULL; #ifdef HAVE_LIBSODIUM if (sodium_init() < 0) @@ -188,7 +216,7 @@ gboolean ostree_sign_ed25519_metadata_verify (OstreeSign *self, goto err; } - if ((sign->initialized != TRUE) || (sign->public_key == NULL)) + if ((sign->initialized != TRUE) || (sign->public_keys == NULL)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Not able to verify: libsodium library isn't initialized properly"); @@ -207,20 +235,26 @@ gboolean ostree_sign_ed25519_metadata_verify (OstreeSign *self, g_debug("Read signature %d: %s", (gint)i, g_variant_print(child, TRUE)); - if (crypto_sign_verify_detached ((guchar *) g_variant_get_data (child), - g_bytes_get_data (data, NULL), - g_bytes_get_size (data), - sign->public_key) != 0) + for (GList *public_key = sign->public_keys; + public_key != NULL; + public_key = public_key->next) { - /* Incorrect signature! */ - g_debug("Signature couldn't be verified with key '%s'", - sodium_bin2hex (hex, crypto_sign_PUBLICKEYBYTES*2+1, sign->public_key, crypto_sign_PUBLICKEYBYTES)); - } - else - { - ret = TRUE; - g_debug ("Signature verified successfully with key '%s'", - sodium_bin2hex (hex, crypto_sign_PUBLICKEYBYTES*2+1, sign->public_key, crypto_sign_PUBLICKEYBYTES)); + if (crypto_sign_verify_detached ((guchar *) g_variant_get_data (child), + g_bytes_get_data (data, NULL), + g_bytes_get_size (data), + public_key->data) != 0) + { + /* Incorrect signature! */ + g_debug("Signature couldn't be verified with key '%s'", + sodium_bin2hex (hex, crypto_sign_PUBLICKEYBYTES*2+1, public_key->data, crypto_sign_PUBLICKEYBYTES)); + } + else + { + ret = TRUE; + g_debug ("Signature verified successfully with key '%s'", + sodium_bin2hex (hex, crypto_sign_PUBLICKEYBYTES*2+1, public_key->data, crypto_sign_PUBLICKEYBYTES)); + break; + } } } @@ -297,7 +331,7 @@ gboolean ostree_sign_ed25519_set_sk (OstreeSign *self, } hex = g_malloc0 (crypto_sign_SECRETKEYBYTES*2 + 1); - g_debug ("Set ed25519 secret key = %s", sodium_bin2hex (hex, crypto_sign_SECRETKEYBYTES*2+1, sign->secret_key, n_elements)); +// g_debug ("Set ed25519 secret key = %s", sodium_bin2hex (hex, crypto_sign_SECRETKEYBYTES*2+1, sign->secret_key, n_elements)); return TRUE; @@ -313,16 +347,35 @@ gboolean ostree_sign_ed25519_set_pk (OstreeSign *self, g_debug ("%s enter", __FUNCTION__); g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE); + OstreeSignEd25519 *sign = ostree_sign_ed25519_get_instance_private(OSTREE_SIGN_ED25519(self)); + + /* Substitute the key(s) with a new one */ + if (sign->public_keys != NULL) + { + g_list_free_full (sign->public_keys, g_object_unref); + sign->public_keys = NULL; + } + + return ostree_sign_ed25519_add_pk (self, public_key, error); +} + +gboolean ostree_sign_ed25519_add_pk (OstreeSign *self, + GVariant *public_key, + GError **error) +{ + g_debug ("%s enter", __FUNCTION__); + g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE); + #ifdef HAVE_LIBSODIUM OstreeSignEd25519 *sign = ostree_sign_ed25519_get_instance_private(OSTREE_SIGN_ED25519(self)); g_autofree char * hex = NULL; + gpointer key = NULL; gsize n_elements = 0; - g_free (sign->public_key); - sign->public_key = (guchar *) g_variant_get_fixed_array (public_key, &n_elements, sizeof(guchar)); + key = (gpointer) g_variant_get_fixed_array (public_key, &n_elements, sizeof(guchar)); hex = g_malloc0 (crypto_sign_PUBLICKEYBYTES*2 + 1); - g_debug ("Read ed25519 public key = %s", sodium_bin2hex (hex, crypto_sign_PUBLICKEYBYTES*2+1, sign->public_key, n_elements)); + g_debug ("Read ed25519 public key = %s", sodium_bin2hex (hex, crypto_sign_PUBLICKEYBYTES*2+1, key, n_elements)); if (n_elements != crypto_sign_PUBLICKEYBYTES) { @@ -331,7 +384,9 @@ gboolean ostree_sign_ed25519_set_pk (OstreeSign *self, goto err; } - g_debug ("Set ed25519 public key = %s", sodium_bin2hex (hex, crypto_sign_PUBLICKEYBYTES*2+1, sign->public_key, n_elements)); + key = g_memdup (key, n_elements); + if (g_list_find (sign->public_keys, key) == NULL) + sign->public_keys = g_list_prepend (sign->public_keys, key); return TRUE; @@ -339,3 +394,144 @@ err: #endif /* HAVE_LIBSODIUM */ return FALSE; } + + +static gboolean +load_pk_from_stream (OstreeSign *self, GDataInputStream *key_data_in, GError **error) +{ + g_return_val_if_fail (key_data_in, FALSE); +#ifdef HAVE_LIBSODIUM + gboolean ret = FALSE; + +#if 0 +/* Try to load the public key in signify format from the stream + * https://www.openbsd.org/papers/bsdcan-signify.html + * + * FIXME: Not sure if we need to support that format. + * */ + g_autofree gchar * comment = NULL; + while (TRUE) + { + gsize len = 0; + g_autofree char *line = g_data_input_stream_read_line (key_data_in, &len, NULL, error); + if (error) + goto err; + + if (line) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Signify format for ed25519 public key not found"); + goto err; + } + + if (comment == NULL) + { + /* Scan for the comment first and compare with prefix&suffix */ + if (g_str_has_prefix (line, SIGNIFY_COMMENT_HEADER) && g_str_has_suffix (line, "public key")) + /* Save comment without the prefix and blank space */ + comment = g_strdup (line + strlen(SIGNIFY_COMMENT_HEADER) + 1); + } + else + { + /* Read the key itself */ + /* base64 encoded key */ + gsize keylen = 0; + g_autofree guchar *key = g_base64_decode (line, &keylen); + + /* Malformed key */ + if (keylen != SIGNIFY_ID_LENGTH || + strncmp (line, SIGNIFY_MAGIC_ED25519, strlen(SIGNIFY_MAGIC_ED25519)) != 0) + continue; + + } + } +#endif /* 0 */ + + /* 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 (GVariant) pk = NULL; + + if (*error != NULL) + goto err; + + if (line == NULL) + goto out; + + /* Read the key itself */ + /* base64 encoded key */ + gsize key_len = 0; + g_autofree guchar *key = g_base64_decode (line, &key_len); + + pk = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, key, key_len, sizeof(guchar)); + if (ostree_sign_ed25519_add_pk (self, pk, error)) + { + ret = TRUE; + g_debug ("Added public key: %s", line); + } + else + g_debug ("Invalid public key: %s", line); + } + +out: + return ret; + +err: +#endif /* HAVE_LIBSODIUM */ + return FALSE; +} + +gboolean +ostree_sign_ed25519_load_pk (OstreeSign *self, + GVariant *options, + GError **error) +{ + g_debug ("%s enter", __FUNCTION__); + + OstreeSignEd25519 *sign = ostree_sign_ed25519_get_instance_private(OSTREE_SIGN_ED25519(self)); + + g_autoptr (GFile) keyfile = NULL; + g_autoptr (GFileInputStream) key_stream_in = NULL; + g_autoptr (GDataInputStream) key_data_in = NULL; + + const gchar *remote_name = NULL; + const gchar *filename = NULL; + + /* Clear already loaded keys */ + if (sign->public_keys != NULL) + { + g_list_free_full (sign->public_keys, g_object_unref); + sign->public_keys = NULL; + } + + /* Check if the name of remote is provided */ + if (! g_variant_lookup (options, "remote", "&s", &remote_name)) + remote_name = OSTREE_SIGN_ALL_REMOTES; + + /* Read filename or use will-known if not provided */ + if (! g_variant_lookup (options, "filename", "&s", &filename)) + { + // TODO: define well-known places and load file(s) + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Please provide a filename to load"); + goto err; + } + + keyfile = g_file_new_for_path (filename); + key_stream_in = g_file_read (keyfile, NULL, error); + if (key_stream_in == NULL) + goto err; + + 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, error)) + goto err; + + return TRUE; +err: + return FALSE; +} + diff --git a/src/libostree/ostree-sign-ed25519.h b/src/libostree/ostree-sign-ed25519.h index d4a6b56d..797ac138 100644 --- a/src/libostree/ostree-sign-ed25519.h +++ b/src/libostree/ostree-sign-ed25519.h @@ -63,7 +63,13 @@ gboolean ostree_sign_ed25519_set_pk (OstreeSign *self, GVariant *public_key, GError **error); -void ostree_sign_ed25519_finalize (GObject *gobject); +gboolean ostree_sign_ed25519_add_pk (OstreeSign *self, + GVariant *public_key, + GError **error); + +gboolean ostree_sign_ed25519_load_pk (OstreeSign *self, + GVariant *options, + GError **error); _OSTREE_PUBLIC gboolean ostree_sign_ed25519_keypair_generate (OstreeSign *self, @@ -71,5 +77,6 @@ gboolean ostree_sign_ed25519_keypair_generate (OstreeSign *self, GVariant **out_public_key, GError **error); + G_END_DECLS diff --git a/src/libostree/ostree-sign.c b/src/libostree/ostree-sign.c index 96455f86..0708395c 100644 --- a/src/libostree/ostree-sign.c +++ b/src/libostree/ostree-sign.c @@ -88,22 +88,31 @@ gboolean ostree_sign_set_pk (OstreeSign *self, return OSTREE_SIGN_GET_IFACE (self)->set_pk (self, public_key, error); } +gboolean ostree_sign_add_pk (OstreeSign *self, + GVariant *public_key, + GError **error) +{ + g_debug ("%s enter", __FUNCTION__); + + if (OSTREE_SIGN_GET_IFACE (self)->add_pk == NULL) + return TRUE; + + return OSTREE_SIGN_GET_IFACE (self)->add_pk (self, public_key, error); +} + /* Load private keys for verification from anywhere. * No need to have the same function for secret keys -- the signing SW must do it in it's own way * */ gboolean ostree_sign_load_pk (OstreeSign *self, - gchar *remote_name, + GVariant *options, GError **error) { g_debug ("%s enter", __FUNCTION__); g_return_val_if_fail (OSTREE_SIGN_GET_IFACE (self)->load_pk != NULL, FALSE); - if (remote_name == NULL) - remote_name = OSTREE_SIGN_ALL_REMOTES; - - return OSTREE_SIGN_GET_IFACE (self)->load_pk (self, remote_name, error); + return OSTREE_SIGN_GET_IFACE (self)->load_pk (self, options, error); } gboolean ostree_sign_data (OstreeSign *self, diff --git a/src/libostree/ostree-sign.h b/src/libostree/ostree-sign.h index f06206aa..78e2487d 100644 --- a/src/libostree/ostree-sign.h +++ b/src/libostree/ostree-sign.h @@ -68,8 +68,12 @@ struct _OstreeSignInterface GVariant *public_key, GError **error); + gboolean (* add_pk) (OstreeSign *self, + GVariant *public_key, + GError **error); + gboolean (* load_pk) (OstreeSign *self, - gchar *remote_name, + GVariant *options, GError **error); }; @@ -126,9 +130,14 @@ gboolean ostree_sign_set_pk (OstreeSign *self, GVariant *public_key, GError **error); +_OSTREE_PUBLIC +gboolean ostree_sign_add_pk (OstreeSign *self, + GVariant *public_key, + GError **error); + _OSTREE_PUBLIC gboolean ostree_sign_load_pk (OstreeSign *self, - gchar *remote_name, + GVariant *options, GError **error); diff --git a/src/ostree/ot-builtin-commit.c b/src/ostree/ot-builtin-commit.c index 4bbde92e..89ada19e 100644 --- a/src/ostree/ot-builtin-commit.c +++ b/src/ostree/ot-builtin-commit.c @@ -868,11 +868,10 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio else if (!g_strcmp0 (ostree_sign_get_name (sign), "ed25519")) { gsize key_len = 0; - key = g_malloc0 (crypto_sign_SECRETKEYBYTES); - if (sodium_hex2bin (key, crypto_sign_SECRETKEYBYTES, - keyid, strlen (keyid), - NULL, &key_len, NULL) != 0) - { + g_autofree guchar *key = g_base64_decode (keyid, &key_len); + + if ( key_len != crypto_sign_SECRETKEYBYTES) + { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid KEY '%s'", keyid); diff --git a/src/ostree/ot-builtin-sign.c b/src/ostree/ot-builtin-sign.c index 8edd5490..e36a50f1 100644 --- a/src/ostree/ot-builtin-sign.c +++ b/src/ostree/ot-builtin-sign.c @@ -136,10 +136,9 @@ ostree_builtin_sign (int argc, char **argv, OstreeCommandInvocation *invocation, if (!g_strcmp0(ostree_sign_get_name(sign), "ed25519")) { gsize key_len = 0; - key = g_malloc0 (crypto_sign_PUBLICKEYBYTES); - if (sodium_hex2bin (key, crypto_sign_PUBLICKEYBYTES, - key_ids[ii], strlen (key_ids[ii]), - NULL, &key_len, NULL) != 0) + g_autofree guchar *key = g_base64_decode (key_ids[ii], &key_len); + + if ( key_len != crypto_sign_PUBLICKEYBYTES) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid KEY '%s'", key_ids[ii]); @@ -170,10 +169,9 @@ ostree_builtin_sign (int argc, char **argv, OstreeCommandInvocation *invocation, if (!g_strcmp0(ostree_sign_get_name(sign), "ed25519")) { gsize key_len = 0; - key = g_malloc0 (crypto_sign_SECRETKEYBYTES); - if (sodium_hex2bin (key, crypto_sign_SECRETKEYBYTES, - key_ids[ii], strlen (key_ids[ii]), - NULL, &key_len, NULL) != 0) + g_autofree guchar *key = g_base64_decode (key_ids[ii], &key_len); + + if ( key_len != crypto_sign_SECRETKEYBYTES) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid KEY '%s'", key_ids[ii]); diff --git a/tests/test-signed-commit.sh b/tests/test-signed-commit.sh index 08993c60..55945f8a 100755 --- a/tests/test-signed-commit.sh +++ b/tests/test-signed-commit.sh @@ -60,16 +60,16 @@ openssl genpkey -algorithm ed25519 -outform PEM -out "${PEMFILE}" if has_libsodium; then # Based on: http://openssl.6102.n7.nabble.com/ed25519-key-generation-td73907.html # Extract the private and public parts from generated key. - PUBLIC="$(openssl pkey -outform DER -pubout -in ${PEMFILE} | hexdump -s 12 -e '16/1 "%.2x"')" - SEED="$(openssl pkey -outform DER -in ${PEMFILE} | hexdump -s 16 -e '16/1 "%.2x"')" + PUBLIC="$(openssl pkey -outform DER -pubout -in ${PEMFILE} | tail -c 32 | base64)" + SEED="$(openssl pkey -outform DER -in ${PEMFILE} | tail -c 32 | base64)" # Secret key is concantination of SEED and PUBLIC - SECRET="${SEED}${PUBLIC}" + SECRET="$(echo ${SEED}${PUBLIC} | base64 -d | base64 -w 0)" echo "SEED = $SEED" echo "PUBLIC = $PUBLIC" echo "Signed commit with ed25519: ${SECRET}" >> file.txt - ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s "Signed with ed25519 module" --sign=${SECRET} --sign-type=ed25519 + ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s "Signed with ed25519 module" --sign="${SECRET}" --sign-type=ed25519 COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)" # Ensure that detached metadata contain signature