Home | History | Annotate | Line # | Download | only in dist
      1 /* $OpenBSD: ssh-ed25519.c,v 1.24 2026/09/16 00:43:00 djm Exp $ */
      2 /*
      3  * Copyright (c) 2013 Markus Friedl <markus (at) openbsd.org>
      4  *
      5  * Permission to use, copy, modify, and distribute this software for any
      6  * purpose with or without fee is hereby granted, provided that the above
      7  * copyright notice and this permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     16  */
     17 #include "includes.h"
     18 __RCSID("$NetBSD: ssh-ed25519.c,v 1.14 2026/10/07 17:32:08 christos Exp $");
     19 
     20 #include <sys/types.h>
     21 #include <limits.h>
     22 
     23 #include "crypto_api.h"
     24 
     25 #include <string.h>
     26 #include <stdarg.h>
     27 
     28 #include "log.h"
     29 #include "sshbuf.h"
     30 #define SSHKEY_INTERNAL
     31 #include "sshkey.h"
     32 #include "ssherr.h"
     33 
     34 static void
     35 ssh_ed25519_cleanup(struct sshkey *k)
     36 {
     37 	freezero(k->ed25519_pk, ED25519_PK_SZ);
     38 	freezero(k->ed25519_sk, ED25519_SK_SZ);
     39 	k->ed25519_pk = NULL;
     40 	k->ed25519_sk = NULL;
     41 }
     42 
     43 static int
     44 ssh_ed25519_equal(const struct sshkey *a, const struct sshkey *b)
     45 {
     46 	if (a->ed25519_pk == NULL || b->ed25519_pk == NULL)
     47 		return 0;
     48 	if (memcmp(a->ed25519_pk, b->ed25519_pk, ED25519_PK_SZ) != 0)
     49 		return 0;
     50 	return 1;
     51 }
     52 
     53 static int
     54 ssh_ed25519_serialize_public(const struct sshkey *key, struct sshbuf *b,
     55     enum sshkey_serialize_rep opts)
     56 {
     57 	int r;
     58 
     59 	if (key->ed25519_pk == NULL)
     60 		return SSH_ERR_INVALID_ARGUMENT;
     61 	if ((r = sshbuf_put_string(b, key->ed25519_pk, ED25519_PK_SZ)) != 0)
     62 		return r;
     63 
     64 	return 0;
     65 }
     66 
     67 static int
     68 ssh_ed25519_serialize_private(const struct sshkey *key, struct sshbuf *b,
     69     enum sshkey_serialize_rep opts)
     70 {
     71 	int r;
     72 
     73 	if ((r = sshbuf_put_string(b, key->ed25519_pk, ED25519_PK_SZ)) != 0 ||
     74 	    (r = sshbuf_put_string(b, key->ed25519_sk, ED25519_SK_SZ)) != 0)
     75 		return r;
     76 
     77 	return 0;
     78 }
     79 
     80 static int
     81 ssh_ed25519_generate(struct sshkey *k, int bits)
     82 {
     83 	if ((k->ed25519_pk = malloc(ED25519_PK_SZ)) == NULL ||
     84 	    (k->ed25519_sk = malloc(ED25519_SK_SZ)) == NULL)
     85 		return SSH_ERR_ALLOC_FAIL;
     86 	if (crypto_sign_ed25519_keypair(k->ed25519_pk, k->ed25519_sk) != 0)
     87 		return SSH_ERR_CRYPTO_ERROR;
     88 	return 0;
     89 }
     90 
     91 static int
     92 ssh_ed25519_copy_public(const struct sshkey *from, struct sshkey *to)
     93 {
     94 	if (from->ed25519_pk == NULL)
     95 		return 0; /* XXX SSH_ERR_INTERNAL_ERROR ? */
     96 	if ((to->ed25519_pk = malloc(ED25519_PK_SZ)) == NULL)
     97 		return SSH_ERR_ALLOC_FAIL;
     98 	memcpy(to->ed25519_pk, from->ed25519_pk, ED25519_PK_SZ);
     99 	return 0;
    100 }
    101 
    102 static int
    103 ssh_ed25519_deserialize_public(const char *ktype, struct sshbuf *b,
    104     struct sshkey *key)
    105 {
    106 	u_char *pk = NULL;
    107 	size_t len = 0;
    108 	int r;
    109 
    110 	if ((r = sshbuf_get_string(b, &pk, &len)) != 0)
    111 		return r;
    112 	if (len != ED25519_PK_SZ) {
    113 		freezero(pk, len);
    114 		return SSH_ERR_INVALID_FORMAT;
    115 	}
    116 	key->ed25519_pk = pk;
    117 	return 0;
    118 }
    119 
    120 static int
    121 ssh_ed25519_deserialize_private(const char *ktype, struct sshbuf *b,
    122     struct sshkey *key)
    123 {
    124 	int r;
    125 	size_t sklen = 0;
    126 	u_char *ed25519_sk = NULL;
    127 
    128 	if ((r = ssh_ed25519_deserialize_public(NULL, b, key)) != 0)
    129 		goto out;
    130 	if ((r = sshbuf_get_string(b, &ed25519_sk, &sklen)) != 0)
    131 		goto out;
    132 	if (sklen != ED25519_SK_SZ) {
    133 		r = SSH_ERR_INVALID_FORMAT;
    134 		goto out;
    135 	}
    136 	key->ed25519_sk = ed25519_sk;
    137 	ed25519_sk = NULL; /* transferred */
    138 	/* success */
    139 	r = 0;
    140  out:
    141 	freezero(ed25519_sk, sklen);
    142 	return r;
    143 }
    144 
    145 static int
    146 ssh_ed25519_sign(struct sshkey *key,
    147     u_char **sigp, size_t *lenp,
    148     const u_char *data, size_t datalen,
    149     const char *alg, const char *sk_provider, const char *sk_pin, u_int compat)
    150 {
    151 	u_char sig[crypto_sign_ed25519_BYTES];
    152 	unsigned long long siglen = 0;
    153 	int r, ret;
    154 
    155 	if (lenp != NULL)
    156 		*lenp = 0;
    157 	if (sigp != NULL)
    158 		*sigp = NULL;
    159 
    160 	if (key == NULL ||
    161 	    sshkey_type_plain(key->type) != KEY_ED25519 ||
    162 	    key->ed25519_sk == NULL)
    163 		return SSH_ERR_INVALID_ARGUMENT;
    164 
    165 	if ((ret = crypto_sign_ed25519_detached(sig, &siglen, data, datalen,
    166 	    key->ed25519_sk)) != 0 || siglen != sizeof(sig)) {
    167 		r = SSH_ERR_INVALID_ARGUMENT; /* XXX better error? */
    168 		goto out;
    169 	}
    170 	if ((r = ssh_ed25519_encode_store_sig(sig, siglen,
    171 	    sigp, lenp)) != 0)
    172 		goto out;
    173 
    174 	/* success */
    175 	r = 0;
    176  out:
    177 	explicit_bzero(sig, sizeof(sig));
    178 	return r;
    179 }
    180 
    181 int
    182 ssh_ed25519_encode_store_sig(const u_char *sig, size_t slen,
    183     u_char **sigp, size_t *lenp)
    184 {
    185 	struct sshbuf *b = NULL;
    186 	int r = -1;
    187 	size_t len;
    188 
    189 	if (lenp != NULL)
    190 		*lenp = 0;
    191 	if (sigp != NULL)
    192 		*sigp = NULL;
    193 
    194 	if (slen != crypto_sign_ed25519_BYTES)
    195 		return SSH_ERR_INVALID_ARGUMENT;
    196 
    197 	/* encode signature */
    198 	if ((b = sshbuf_new()) == NULL) {
    199 		r = SSH_ERR_ALLOC_FAIL;
    200 		goto out;
    201 	}
    202 	if ((r = sshbuf_put_cstring(b, "ssh-ed25519")) != 0 ||
    203 	    (r = sshbuf_put_string(b, sig, slen)) != 0)
    204 		goto out;
    205 	len = sshbuf_len(b);
    206 	if (sigp != NULL) {
    207 		if ((*sigp = malloc(len)) == NULL) {
    208 			r = SSH_ERR_ALLOC_FAIL;
    209 			goto out;
    210 		}
    211 		memcpy(*sigp, sshbuf_ptr(b), len);
    212 	}
    213 	if (lenp != NULL)
    214 		*lenp = len;
    215 	/* success */
    216 	r = 0;
    217  out:
    218 	sshbuf_free(b);
    219 	return r;
    220 }
    221 
    222 static int
    223 ssh_ed25519_verify(const struct sshkey *key,
    224     const u_char *sig, size_t siglen,
    225     const u_char *data, size_t dlen, const char *alg, u_int compat,
    226     struct sshkey_sig_details **detailsp)
    227 {
    228 	struct sshbuf *b = NULL;
    229 	char *ktype = NULL;
    230 	const u_char *sigblob;
    231 	size_t len;
    232 	int r, ret;
    233 
    234 	if (key == NULL ||
    235 	    sshkey_type_plain(key->type) != KEY_ED25519 ||
    236 	    key->ed25519_pk == NULL ||
    237 	    sig == NULL || siglen == 0)
    238 		return SSH_ERR_INVALID_ARGUMENT;
    239 
    240 	if ((b = sshbuf_from(sig, siglen)) == NULL)
    241 		return SSH_ERR_ALLOC_FAIL;
    242 	if ((r = sshbuf_get_cstring(b, &ktype, NULL)) != 0 ||
    243 	    (r = sshbuf_get_string_direct(b, &sigblob, &len)) != 0)
    244 		goto out;
    245 	if (strcmp("ssh-ed25519", ktype) != 0) {
    246 		r = SSH_ERR_KEY_TYPE_MISMATCH;
    247 		goto out;
    248 	}
    249 	if (sshbuf_len(b) != 0) {
    250 		r = SSH_ERR_UNEXPECTED_TRAILING_DATA;
    251 		goto out;
    252 	}
    253 	if (len != crypto_sign_ed25519_BYTES) {
    254 		r = SSH_ERR_INVALID_FORMAT;
    255 		goto out;
    256 	}
    257 	if ((ret = crypto_sign_ed25519_verify_detached(sigblob, data, dlen,
    258 	    key->ed25519_pk)) != 0) {
    259 		debug2_f("crypto_sign_ed25519_verify_detached failed: %d", ret);
    260 	}
    261 	if (ret != 0) {
    262 		r = SSH_ERR_SIGNATURE_INVALID;
    263 		goto out;
    264 	}
    265 	/* success */
    266 	r = 0;
    267  out:
    268 	sshbuf_free(b);
    269 	free(ktype);
    270 	return r;
    271 }
    272 
    273 /* NB. not static; used by ED25519-SK */
    274 const struct sshkey_impl_funcs sshkey_ed25519_funcs = {
    275 	/* .size = */		NULL,
    276 	/* .alloc = */		NULL,
    277 	/* .cleanup = */	ssh_ed25519_cleanup,
    278 	/* .equal = */		ssh_ed25519_equal,
    279 	/* .ssh_serialize_public = */ ssh_ed25519_serialize_public,
    280 	/* .ssh_deserialize_public = */ ssh_ed25519_deserialize_public,
    281 	/* .ssh_serialize_private = */ ssh_ed25519_serialize_private,
    282 	/* .ssh_deserialize_private = */ ssh_ed25519_deserialize_private,
    283 	/* .generate = */	ssh_ed25519_generate,
    284 	/* .copy_public = */	ssh_ed25519_copy_public,
    285 	/* .sign = */		ssh_ed25519_sign,
    286 	/* .verify = */		ssh_ed25519_verify,
    287 };
    288 
    289 const struct sshkey_impl sshkey_ed25519_impl = {
    290 	/* .name = */		"ssh-ed25519",
    291 	/* .shortname = */	"ED25519",
    292 	/* .sigalg = */		NULL,
    293 	/* .type = */		KEY_ED25519,
    294 	/* .nid = */		0,
    295 	/* .cert = */		0,
    296 	/* .sigonly = */	0,
    297 	/* .keybits = */	256,
    298 	/* .funcs = */		&sshkey_ed25519_funcs,
    299 };
    300 
    301 const struct sshkey_impl sshkey_ed25519_cert_impl = {
    302 	/* .name = */		"ssh-ed25519-cert-v01 (at) openssh.com",
    303 	/* .shortname = */	"ED25519-CERT",
    304 	/* .sigalg = */		NULL,
    305 	/* .type = */		KEY_ED25519_CERT,
    306 	/* .nid = */		0,
    307 	/* .cert = */		1,
    308 	/* .sigonly = */	0,
    309 	/* .keybits = */	256,
    310 	/* .funcs = */		&sshkey_ed25519_funcs,
    311 };
    312