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 <denis.pynkin@collabora.com>
This commit is contained in:
Denis Pynkin 2019-08-09 22:07:57 +03:00
parent 9e8f0f4ca0
commit 2303202c86
11 changed files with 278 additions and 53 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -30,17 +30,25 @@
#include <sodium.h>
#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;
}

View File

@ -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

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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]);

View File

@ -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