Skip to content

Commit 554697d

Browse files
committed
sign: Support keys embedded with public key algorithm
The current commit signing mechanism 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. This patch switches to using the standard key formats natively supported by OpenSSL (PKCS#8 and SubjectPublicKeyInfo) and capable of embedding algorithm identifier, while the support for the original key format is preserved for backward compatibility. As a PoC of the feature, this adds a couple of new tests using Ed448, instead of Ed25519, in tests/test-signed-commit.sh. 1. https://en.wikipedia.org/wiki/Cryptographic_agility Signed-off-by: Daiki Ueno <[email protected]>
1 parent 97fb111 commit 554697d

File tree

6 files changed

+213
-60
lines changed

6 files changed

+213
-60
lines changed

configure.ac

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,10 @@ if test x$with_openssl != xno || test x$with_ed25519_libsodium != xno; then
457457
OSTREE_FEATURES="$OSTREE_FEATURES sign-ed25519"
458458
fi
459459

460+
if test x$with_openssl != xno; then
461+
OSTREE_FEATURES="$OSTREE_FEATURES sign-pkcs8"
462+
fi
463+
460464
dnl begin gnutls; in contrast to openssl this one only
461465
dnl supports --with-crypto=gnutls
462466
GNUTLS_DEPENDENCY="gnutls >= 3.5.0"

src/libostree/ostree-sign-ed25519.c

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ struct _OstreeSignEd25519
4848
{
4949
GObject parent;
5050
ed25519_state state;
51-
guchar *secret_key; /* malloc'd buffer of length OSTREE_SIGN_ED25519_SECKEY_SIZE */
52-
GList *public_keys; /* malloc'd buffer of length OSTREE_SIGN_ED25519_PUBKEY_SIZE */
53-
GList *revoked_keys; /* malloc'd buffer of length OSTREE_SIGN_ED25519_PUBKEY_SIZE */
51+
GBytes *secret_key;
52+
GList *public_keys; /* GBytes */
53+
GList *revoked_keys; /* GBytes */
5454
};
5555

5656
static void ostree_sign_ed25519_iface_init (OstreeSignInterface *self);
@@ -96,15 +96,19 @@ _ostree_sign_ed25519_init (OstreeSignEd25519 *self)
9696
#endif
9797
}
9898

99+
#if defined(USE_LIBSODIUM)
100+
// Strictly verify pubkey and signature lengths, as libsodium can
101+
// only handle raw ed25519 public key and signatures.
99102
static gboolean
100103
validate_length (gsize found, gsize expected, GError **error)
101104
{
102105
if (found == expected)
103106
return TRUE;
104107
return glnx_throw (
105108
error, "Ill-formed input: expected %" G_GSIZE_FORMAT " bytes, got %" G_GSIZE_FORMAT " bytes",
106-
found, expected);
109+
expected, found);
107110
}
111+
#endif
108112

109113
static gboolean
110114
_ostree_sign_ed25519_is_initialized (OstreeSignEd25519 *self, GError **error)
@@ -136,27 +140,43 @@ ostree_sign_ed25519_data (OstreeSign *self, GBytes *data, GBytes **signature,
136140
if (sign->secret_key == NULL)
137141
return glnx_throw (error, "Not able to sign: secret key is not set");
138142

143+
#if defined(USE_LIBSODIUM) || defined(USE_OPENSSL)
144+
gsize secret_key_size;
145+
const guint8 *secret_key_buf = g_bytes_get_data (sign->secret_key, &secret_key_size);
146+
#endif
147+
139148
unsigned long long sig_size = 0;
140-
g_autofree guchar *sig = g_malloc0 (OSTREE_SIGN_ED25519_SIG_SIZE);
149+
g_autofree guchar *sig = NULL;
141150

142151
#if defined(USE_LIBSODIUM)
152+
sig = g_malloc0 (OSTREE_SIGN_ED25519_SIG_SIZE);
143153
if (crypto_sign_detached (sig, &sig_size, g_bytes_get_data (data, NULL), g_bytes_get_size (data),
144-
sign->secret_key))
154+
secret_key_buf))
145155
sig_size = 0;
146156
#elif defined(USE_OPENSSL)
147157
EVP_MD_CTX *ctx = EVP_MD_CTX_new ();
148158
if (!ctx)
149159
return glnx_throw (error, "openssl: failed to allocate context");
150-
EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, sign->secret_key,
151-
OSTREE_SIGN_ED25519_SEED_SIZE);
160+
161+
// Try PKCS8 encoded private key first.
162+
const unsigned char *p = secret_key_buf;
163+
EVP_PKEY *pkey = d2i_AutoPrivateKey (NULL, &p, secret_key_size);
164+
165+
// Try raw ed25519 private key if the length matches.
166+
if (pkey == NULL && secret_key_size == OSTREE_SIGN_ED25519_SECKEY_SIZE)
167+
pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, secret_key_buf,
168+
OSTREE_SIGN_ED25519_SEED_SIZE);
169+
152170
if (!pkey)
153171
{
154172
EVP_MD_CTX_free (ctx);
155-
return glnx_throw (error, "openssl: Failed to initialize ed5519 key");
173+
return glnx_throw (error, "openssl: Failed to initialize ed25519 key");
156174
}
157175

158176
size_t len;
159177
if (EVP_DigestSignInit (ctx, NULL, NULL, NULL, pkey)
178+
&& EVP_DigestSign (ctx, NULL, &len, g_bytes_get_data (data, NULL), g_bytes_get_size (data))
179+
&& (sig = g_malloc0 (len)) != NULL
160180
&& EVP_DigestSign (ctx, sig, &len, g_bytes_get_data (data, NULL), g_bytes_get_size (data)))
161181
sig_size = len;
162182

@@ -172,12 +192,6 @@ ostree_sign_ed25519_data (OstreeSign *self, GBytes *data, GBytes **signature,
172192
return TRUE;
173193
}
174194

175-
static gint
176-
_compare_ed25519_keys (gconstpointer a, gconstpointer b)
177-
{
178-
return memcmp (a, b, OSTREE_SIGN_ED25519_PUBKEY_SIZE);
179-
}
180-
181195
gboolean
182196
ostree_sign_ed25519_data_verify (OstreeSign *self, GBytes *data, GVariant *signatures,
183197
char **out_success_message, GError **error)
@@ -222,29 +236,27 @@ ostree_sign_ed25519_data_verify (OstreeSign *self, GBytes *data, GVariant *signa
222236
g_autoptr (GVariant) child = g_variant_get_child_value (signatures, i);
223237
g_autoptr (GBytes) signature = g_variant_get_data_as_bytes (child);
224238

239+
#if defined(USE_LIBSODIUM)
225240
if (!validate_length (g_bytes_get_size (signature), OSTREE_SIGN_ED25519_SIG_SIZE, error))
226241
return glnx_prefix_error (error, "Invalid signature");
227-
228-
g_autofree char *hex = g_malloc0 (OSTREE_SIGN_ED25519_PUBKEY_SIZE * 2 + 1);
242+
#endif
229243

230244
g_debug ("Read signature %d: %s", (gint)i, g_variant_print (child, TRUE));
231245

232-
for (GList *public_key = sign->public_keys; public_key != NULL; public_key = public_key->next)
246+
for (GList *l = sign->public_keys; l != NULL; l = l->next)
233247
{
248+
GBytes *public_key = l->data;
234249
/* TODO: use non-list for tons of revoked keys? */
235-
if (g_list_find_custom (sign->revoked_keys, public_key->data, _compare_ed25519_keys)
236-
!= NULL)
250+
if (g_list_find_custom (sign->revoked_keys, public_key, g_bytes_compare) != NULL)
237251
{
238-
ot_bin2hex (hex, public_key->data, OSTREE_SIGN_ED25519_PUBKEY_SIZE);
252+
g_autofree char *hex = g_malloc0 (g_bytes_get_size (public_key) * 2 + 1);
253+
ot_bin2hex (hex, g_bytes_get_data (public_key, NULL), g_bytes_get_size (public_key));
239254
g_debug ("Skip revoked key '%s'", hex);
240255
continue;
241256
}
242257

243258
bool valid = false;
244-
// Wrap the pubkey in a GBytes as that's what this API wants
245-
g_autoptr (GBytes) public_key_bytes
246-
= g_bytes_new_static (public_key->data, OSTREE_SIGN_ED25519_PUBKEY_SIZE);
247-
if (!otcore_validate_ed25519_signature (data, public_key_bytes, signature, &valid, error))
259+
if (!otcore_validate_ed25519_signature (data, public_key, signature, &valid, error))
248260
return FALSE;
249261
if (!valid)
250262
{
@@ -254,14 +266,17 @@ ostree_sign_ed25519_data_verify (OstreeSign *self, GBytes *data, GVariant *signa
254266
else
255267
g_string_append (invalid_signatures, "; ");
256268
n_invalid_signatures++;
257-
ot_bin2hex (hex, public_key->data, OSTREE_SIGN_ED25519_PUBKEY_SIZE);
269+
g_autofree char *hex = g_malloc0 (g_bytes_get_size (public_key) * 2 + 1);
270+
ot_bin2hex (hex, g_bytes_get_data (public_key, NULL), g_bytes_get_size (public_key));
258271
g_string_append_printf (invalid_signatures, "key '%s'", hex);
259272
}
260273
else
261274
{
262275
if (out_success_message)
263276
{
264-
ot_bin2hex (hex, public_key->data, OSTREE_SIGN_ED25519_PUBKEY_SIZE);
277+
g_autofree char *hex = g_malloc0 (g_bytes_get_size (public_key) * 2 + 1);
278+
ot_bin2hex (hex, g_bytes_get_data (public_key, NULL),
279+
g_bytes_get_size (public_key));
265280
*out_success_message = g_strdup_printf (
266281
"ed25519: Signature verified successfully with key '%s'", hex);
267282
}
@@ -320,22 +335,23 @@ ostree_sign_ed25519_clear_keys (OstreeSign *self, GError **error)
320335
/* Clear secret key */
321336
if (sign->secret_key != NULL)
322337
{
323-
memset (sign->secret_key, 0, OSTREE_SIGN_ED25519_SECKEY_SIZE);
324-
g_free (sign->secret_key);
338+
gsize size;
339+
gpointer data = g_bytes_unref_to_data (sign->secret_key, &size);
340+
memset (data, 0, size);
325341
sign->secret_key = NULL;
326342
}
327343

328344
/* Clear already loaded trusted keys */
329345
if (sign->public_keys != NULL)
330346
{
331-
g_list_free_full (sign->public_keys, g_free);
347+
g_list_free_full (sign->public_keys, (GDestroyNotify)g_bytes_unref);
332348
sign->public_keys = NULL;
333349
}
334350

335351
/* Clear already loaded revoked keys */
336352
if (sign->revoked_keys != NULL)
337353
{
338-
g_list_free_full (sign->revoked_keys, g_free);
354+
g_list_free_full (sign->revoked_keys, (GDestroyNotify)g_bytes_unref);
339355
sign->revoked_keys = NULL;
340356
}
341357

@@ -374,10 +390,12 @@ ostree_sign_ed25519_set_sk (OstreeSign *self, GVariant *secret_key, GError **err
374390
return glnx_throw (error, "Unknown ed25519 secret key type");
375391
}
376392

393+
#if defined(USE_LIBSODIUM)
377394
if (!validate_length (n_elements, OSTREE_SIGN_ED25519_SECKEY_SIZE, error))
378395
return glnx_prefix_error (error, "Invalid ed25519 secret key");
396+
#endif
379397

380-
sign->secret_key = g_steal_pointer (&secret_key_buf);
398+
sign->secret_key = g_bytes_new_take (g_steal_pointer (&secret_key_buf), n_elements);
381399

382400
return TRUE;
383401
}
@@ -429,17 +447,20 @@ ostree_sign_ed25519_add_pk (OstreeSign *self, GVariant *public_key, GError **err
429447
return glnx_throw (error, "Unknown ed25519 public key type");
430448
}
431449

450+
#if defined(USE_LIBSODIUM)
432451
if (!validate_length (n_elements, OSTREE_SIGN_ED25519_PUBKEY_SIZE, error))
433452
return glnx_prefix_error (error, "Invalid ed25519 public key");
453+
#endif
434454

435-
g_autofree char *hex = g_malloc0 (OSTREE_SIGN_ED25519_PUBKEY_SIZE * 2 + 1);
455+
g_autofree char *hex = g_malloc0 (n_elements * 2 + 1);
436456
ot_bin2hex (hex, key, n_elements);
437457
g_debug ("Read ed25519 public key = %s", hex);
438458

439-
if (g_list_find_custom (sign->public_keys, key, _compare_ed25519_keys) == NULL)
459+
g_autoptr (GBytes) key_bytes = g_bytes_new_static (key, n_elements);
460+
if (g_list_find_custom (sign->public_keys, key_bytes, g_bytes_compare) == NULL)
440461
{
441-
gpointer newkey = g_memdup2 (key, n_elements);
442-
sign->public_keys = g_list_prepend (sign->public_keys, newkey);
462+
GBytes *new_key_bytes = g_bytes_new (key, n_elements);
463+
sign->public_keys = g_list_prepend (sign->public_keys, new_key_bytes);
443464
}
444465

445466
return TRUE;
@@ -460,17 +481,20 @@ _ed25519_add_revoked (OstreeSign *self, GVariant *revoked_key, GError **error)
460481
gsize n_elements = 0;
461482
g_autofree guint8 *key = g_base64_decode (rk_ascii, &n_elements);
462483

484+
#if defined(USE_LIBSODIUM)
463485
if (!validate_length (n_elements, OSTREE_SIGN_ED25519_PUBKEY_SIZE, error))
464486
return glnx_prefix_error (error, "Incorrect ed25519 revoked key");
487+
#endif
465488

466-
g_autofree char *hex = g_malloc0 (OSTREE_SIGN_ED25519_PUBKEY_SIZE * 2 + 1);
489+
g_autofree char *hex = g_malloc0 (n_elements * 2 + 1);
467490
ot_bin2hex (hex, key, n_elements);
468491
g_debug ("Read ed25519 revoked key = %s", hex);
469492

470-
if (g_list_find_custom (sign->revoked_keys, key, _compare_ed25519_keys) == NULL)
493+
g_autoptr (GBytes) key_bytes = g_bytes_new_static (key, n_elements);
494+
if (g_list_find_custom (sign->revoked_keys, key, g_bytes_compare) == NULL)
471495
{
472-
gpointer newkey = g_memdup2 (key, n_elements);
473-
sign->revoked_keys = g_list_prepend (sign->revoked_keys, newkey);
496+
GBytes *new_key_bytes = g_bytes_new (key, n_elements);
497+
sign->revoked_keys = g_list_prepend (sign->revoked_keys, new_key_bytes);
474498
}
475499

476500
return TRUE;

src/libotcore/otcore-ed25519-verify.c

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ otcore_ed25519_init (void)
5151
* `out_valid` will be set to `false`.
5252
*
5353
* If the signature is correct, `out_valid` will be `true`.
54-
* */
54+
*
55+
* Note: when OpenSSL is enabled, public key is not restricted to ed25519 but
56+
* something else if encoded in the X.509 SubjectPublicKeyInfo format. In that
57+
* case, however, the hash algorithm is implicitly determined and thus
58+
* unrestricted key types, e.g., raw RSA or ECDSA are not supported.
59+
*/
5560
gboolean
5661
otcore_validate_ed25519_signature (GBytes *data, GBytes *public_key, GBytes *signature,
5762
bool *out_valid, GError **error)
@@ -64,21 +69,24 @@ otcore_validate_ed25519_signature (GBytes *data, GBytes *public_key, GBytes *sig
6469
// It is OK for error to be NULL, though according to GError rules.
6570

6671
#if defined(HAVE_LIBSODIUM) || defined(HAVE_OPENSSL)
67-
// And strictly verify pubkey and signature lengths
68-
if (g_bytes_get_size (public_key) != OSTREE_SIGN_ED25519_PUBKEY_SIZE)
69-
return glnx_throw (error, "Invalid public key of %" G_GSIZE_FORMAT " expected %" G_GSIZE_FORMAT,
70-
(gsize)g_bytes_get_size (public_key),
71-
(gsize)OSTREE_SIGN_ED25519_PUBKEY_SIZE);
72-
const guint8 *public_key_buf = g_bytes_get_data (public_key, NULL);
73-
if (g_bytes_get_size (signature) != OSTREE_SIGN_ED25519_SIG_SIZE)
74-
return glnx_throw (
75-
error, "Invalid signature length of %" G_GSIZE_FORMAT " bytes, expected %" G_GSIZE_FORMAT,
76-
(gsize)g_bytes_get_size (signature), (gsize)OSTREE_SIGN_ED25519_SIG_SIZE);
77-
const guint8 *signature_buf = g_bytes_get_data (signature, NULL);
72+
gsize public_key_size;
73+
const guint8 *public_key_buf = g_bytes_get_data (public_key, &public_key_size);
7874

75+
gsize signature_size;
76+
const guint8 *signature_buf = g_bytes_get_data (signature, &signature_size);
7977
#endif
8078

8179
#if defined(HAVE_LIBSODIUM)
80+
// Strictly verify pubkey and signature lengths, as libsodium can
81+
// only handle raw ed25519 public key and signatures.
82+
if (public_key_size != OSTREE_SIGN_ED25519_PUBKEY_SIZE)
83+
return glnx_throw (error, "Invalid public key of %" G_GSIZE_FORMAT " expected %" G_GSIZE_FORMAT,
84+
public_key_size, (gsize)OSTREE_SIGN_ED25519_PUBKEY_SIZE);
85+
if (signature_size != OSTREE_SIGN_ED25519_SIG_SIZE)
86+
return glnx_throw (
87+
error, "Invalid signature length of %" G_GSIZE_FORMAT " bytes, expected %" G_GSIZE_FORMAT,
88+
signature_size, (gsize)OSTREE_SIGN_ED25519_SIG_SIZE);
89+
8290
// Note that libsodium assumes the passed byte arrays for the signature and public key
8391
// have at least the expected length, but we checked that above.
8492
if (crypto_sign_verify_detached (signature_buf, g_bytes_get_data (data, NULL),
@@ -92,16 +100,23 @@ otcore_validate_ed25519_signature (GBytes *data, GBytes *public_key, GBytes *sig
92100
EVP_MD_CTX *ctx = EVP_MD_CTX_new ();
93101
if (!ctx)
94102
return glnx_throw (error, "openssl: failed to allocate context");
95-
EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, public_key_buf,
96-
OSTREE_SIGN_ED25519_PUBKEY_SIZE);
103+
104+
// Try SubjectPublicKeyInfo encoded public key first.
105+
const unsigned char *p = public_key_buf;
106+
EVP_PKEY *pkey = d2i_PUBKEY (NULL, &p, public_key_size);
107+
108+
// Try raw ed25519 public key if the length matches.
109+
if (pkey == NULL && public_key_size == OSTREE_SIGN_ED25519_PUBKEY_SIZE)
110+
pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, public_key_buf, public_key_size);
111+
97112
if (!pkey)
98113
{
99114
EVP_MD_CTX_free (ctx);
100-
return glnx_throw (error, "openssl: Failed to initialize ed5519 key");
115+
return glnx_throw (error, "openssl: Failed to initialize ed25519 key");
101116
}
102117
if (EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, pkey) != 0
103-
&& EVP_DigestVerify (ctx, signature_buf, OSTREE_SIGN_ED25519_SIG_SIZE,
104-
g_bytes_get_data (data, NULL), g_bytes_get_size (data))
118+
&& EVP_DigestVerify (ctx, signature_buf, signature_size, g_bytes_get_data (data, NULL),
119+
g_bytes_get_size (data))
105120
!= 0)
106121
{
107122
*out_valid = true;

src/libotcore/otcore.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#define USE_LIBSODIUM
2828
#elif defined(HAVE_OPENSSL)
2929
#include <openssl/evp.h>
30+
#include <openssl/x509.h>
3031
#define USE_OPENSSL
3132
#endif
3233

0 commit comments

Comments
 (0)