Home | History | Annotate | Line # | Download | only in dist
      1 /* $OpenBSD: ssh-ed25519.c,v 1.20 2025/07/24 06:12:08 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 #define SSHKEY_INTERNAL
     18 #include "includes.h"
     19 __RCSID("$NetBSD: ssh-ed25519.c,v 1.11 2025/10/11 15:45:07 christos Exp $");
     20 
     21 #include <sys/types.h>
     22 #include <limits.h>
     23 
     24 #include "crypto_api.h"
     25 
     26 #include <string.h>
     27 #include <stdarg.h>
     28 
     29 #include "log.h"
     30 #include "sshbuf.h"
     31 #include "sshkey.h"
     32 #include "ssherr.h"
     33 #include "ssh.h"
     34 
     35 static void
     36 ssh_ed25519_cleanup(struct sshkey *k)
     37 {
     38 	freezero(k->ed25519_pk, ED25519_PK_SZ);
     39 	freezero(k->ed25519_sk, ED25519_SK_SZ);
     40 	k->ed25519_pk = NULL;
     41 	k->ed25519_sk = NULL;
     42 }
     43 
     44 static int
     45 ssh_ed25519_equal(const struct sshkey *a, const struct sshkey *b)
     46 {
     47 	if (a->ed25519_pk == NULL || b->ed25519_pk == NULL)
     48 		return 0;
     49 	if (memcmp(a->ed25519_pk, b->ed25519_pk, ED25519_PK_SZ) != 0)
     50 		return 0;
     51 	return 1;
     52 }
     53 
     54 static int
     55 ssh_ed25519_serialize_public(const struct sshkey *key, struct sshbuf *b,
     56     enum sshkey_serialize_rep opts)
     57 {
     58 	int r;
     59 
     60 	if (key->ed25519_pk == NULL)
     61 		return SSH_ERR_INVALID_ARGUMENT;
     62 	if ((r = sshbuf_put_string(b, key->ed25519_pk, ED25519_PK_SZ)) != 0)
     63 		return r;
     64 
     65 	return 0;
     66 }
     67 
     68 static int
     69 ssh_ed25519_serialize_private(const struct sshkey *key, struct sshbuf *b,
     70     enum sshkey_serialize_rep opts)
     71 {
     72 	int r;
     73 
     74 	if ((r = sshbuf_put_string(b, key->ed25519_pk, ED25519_PK_SZ)) != 0 ||
     75 	    (r = sshbuf_put_string(b, key->ed25519_sk, ED25519_SK_SZ)) != 0)
     76 		return r;
     77 
     78 	return 0;
     79 }
     80 
     81 static int
     82 ssh_ed25519_generate(struct sshkey *k, int bits)
     83 {
     84 	if ((k->ed25519_pk = malloc(ED25519_PK_SZ)) == NULL ||
     85 	    (k->ed25519_sk = malloc(ED25519_SK_SZ)) == NULL)
     86 		return SSH_ERR_ALLOC_FAIL;
     87 	crypto_sign_ed25519_keypair(k->ed25519_pk, k->ed25519_sk);
     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 = NULL;
    152 	size_t slen = 0;
    153 	unsigned long long smlen;
    154 	int r, ret;
    155 
    156 	if (lenp != NULL)
    157 		*lenp = 0;
    158 	if (sigp != NULL)
    159 		*sigp = NULL;
    160 
    161 	if (key == NULL ||
    162 	    sshkey_type_plain(key->type) != KEY_ED25519 ||
    163 	    key->ed25519_sk == NULL ||
    164 	    datalen >= INT_MAX - crypto_sign_ed25519_BYTES)
    165 		return SSH_ERR_INVALID_ARGUMENT;
    166 	smlen = slen = datalen + crypto_sign_ed25519_BYTES;
    167 	if ((sig = malloc(slen)) == NULL)
    168 		return SSH_ERR_ALLOC_FAIL;
    169 
    170 	if ((ret = crypto_sign_ed25519(sig, &smlen, data, datalen,
    171 	    key->ed25519_sk)) != 0 || smlen <= datalen) {
    172 		r = SSH_ERR_INVALID_ARGUMENT; /* XXX better error? */
    173 		goto out;
    174 	}
    175 	if ((r = ssh_ed25519_encode_store_sig(sig, smlen - datalen,
    176 	    sigp, lenp)) != 0)
    177 		goto out;
    178 
    179 	/* success */
    180 	r = 0;
    181  out:
    182 	freezero(sig, slen);
    183 	return r;
    184 }
    185 
    186 int
    187 ssh_ed25519_encode_store_sig(const u_char *sig, size_t slen,
    188     u_char **sigp, size_t *lenp)
    189 {
    190 	struct sshbuf *b = NULL;
    191 	int r = -1;
    192 	size_t len;
    193 
    194 	if (lenp != NULL)
    195 		*lenp = 0;
    196 	if (sigp != NULL)
    197 		*sigp = NULL;
    198 
    199 	if (slen != crypto_sign_ed25519_BYTES)
    200 		return SSH_ERR_INVALID_ARGUMENT;
    201 
    202 	/* encode signature */
    203 	if ((b = sshbuf_new()) == NULL) {
    204 		r = SSH_ERR_ALLOC_FAIL;
    205 		goto out;
    206 	}
    207 	if ((r = sshbuf_put_cstring(b, "ssh-ed25519")) != 0 ||
    208 	    (r = sshbuf_put_string(b, sig, slen)) != 0)
    209 		goto out;
    210 	len = sshbuf_len(b);
    211 	if (sigp != NULL) {
    212 		if ((*sigp = malloc(len)) == NULL) {
    213 			r = SSH_ERR_ALLOC_FAIL;
    214 			goto out;
    215 		}
    216 		memcpy(*sigp, sshbuf_ptr(b), len);
    217 	}
    218 	if (lenp != NULL)
    219 		*lenp = len;
    220 	/* success */
    221 	r = 0;
    222  out:
    223 	sshbuf_free(b);
    224 	return r;
    225 }
    226 
    227 static int
    228 ssh_ed25519_verify(const struct sshkey *key,
    229     const u_char *sig, size_t siglen,
    230     const u_char *data, size_t dlen, const char *alg, u_int compat,
    231     struct sshkey_sig_details **detailsp)
    232 {
    233 	struct sshbuf *b = NULL;
    234 	char *ktype = NULL;
    235 	const u_char *sigblob;
    236 	u_char *sm = NULL, *m = NULL;
    237 	size_t len;
    238 	unsigned long long smlen = 0, mlen = 0;
    239 	int r, ret;
    240 
    241 	if (key == NULL ||
    242 	    sshkey_type_plain(key->type) != KEY_ED25519 ||
    243 	    key->ed25519_pk == NULL ||
    244 	    dlen >= INT_MAX - crypto_sign_ed25519_BYTES ||
    245 	    sig == NULL || siglen == 0)
    246 		return SSH_ERR_INVALID_ARGUMENT;
    247 
    248 	if ((b = sshbuf_from(sig, siglen)) == NULL)
    249 		return SSH_ERR_ALLOC_FAIL;
    250 	if ((r = sshbuf_get_cstring(b, &ktype, NULL)) != 0 ||
    251 	    (r = sshbuf_get_string_direct(b, &sigblob, &len)) != 0)
    252 		goto out;
    253 	if (strcmp("ssh-ed25519", ktype) != 0) {
    254 		r = SSH_ERR_KEY_TYPE_MISMATCH;
    255 		goto out;
    256 	}
    257 	if (sshbuf_len(b) != 0) {
    258 		r = SSH_ERR_UNEXPECTED_TRAILING_DATA;
    259 		goto out;
    260 	}
    261 	if (len > crypto_sign_ed25519_BYTES) {
    262 		r = SSH_ERR_INVALID_FORMAT;
    263 		goto out;
    264 	}
    265 	if (dlen >= SIZE_MAX - len) {
    266 		r = SSH_ERR_INVALID_ARGUMENT;
    267 		goto out;
    268 	}
    269 	smlen = len + dlen;
    270 	mlen = smlen;
    271 	if ((sm = malloc(smlen)) == NULL || (m = malloc(mlen)) == NULL) {
    272 		r = SSH_ERR_ALLOC_FAIL;
    273 		goto out;
    274 	}
    275 	memcpy(sm, sigblob, len);
    276 	memcpy(sm+len, data, dlen);
    277 	if ((ret = crypto_sign_ed25519_open(m, &mlen, sm, smlen,
    278 	    key->ed25519_pk)) != 0) {
    279 		debug2_f("crypto_sign_ed25519_open failed: %d", ret);
    280 	}
    281 	if (ret != 0 || mlen != dlen) {
    282 		r = SSH_ERR_SIGNATURE_INVALID;
    283 		goto out;
    284 	}
    285 	/* XXX compare 'm' and 'data' ? */
    286 	/* success */
    287 	r = 0;
    288  out:
    289 	if (sm != NULL)
    290 		freezero(sm, smlen);
    291 	if (m != NULL)
    292 		freezero(m, smlen); /* NB mlen may be invalid if r != 0 */
    293 	sshbuf_free(b);
    294 	free(ktype);
    295 	return r;
    296 }
    297 
    298 /* NB. not static; used by ED25519-SK */
    299 const struct sshkey_impl_funcs sshkey_ed25519_funcs = {
    300 	/* .size = */		NULL,
    301 	/* .alloc = */		NULL,
    302 	/* .cleanup = */	ssh_ed25519_cleanup,
    303 	/* .equal = */		ssh_ed25519_equal,
    304 	/* .ssh_serialize_public = */ ssh_ed25519_serialize_public,
    305 	/* .ssh_deserialize_public = */ ssh_ed25519_deserialize_public,
    306 	/* .ssh_serialize_private = */ ssh_ed25519_serialize_private,
    307 	/* .ssh_deserialize_private = */ ssh_ed25519_deserialize_private,
    308 	/* .generate = */	ssh_ed25519_generate,
    309 	/* .copy_public = */	ssh_ed25519_copy_public,
    310 	/* .sign = */		ssh_ed25519_sign,
    311 	/* .verify = */		ssh_ed25519_verify,
    312 };
    313 
    314 const struct sshkey_impl sshkey_ed25519_impl = {
    315 	/* .name = */		"ssh-ed25519",
    316 	/* .shortname = */	"ED25519",
    317 	/* .sigalg = */		NULL,
    318 	/* .type = */		KEY_ED25519,
    319 	/* .nid = */		0,
    320 	/* .cert = */		0,
    321 	/* .sigonly = */	0,
    322 	/* .keybits = */	256,
    323 	/* .funcs = */		&sshkey_ed25519_funcs,
    324 };
    325 
    326 const struct sshkey_impl sshkey_ed25519_cert_impl = {
    327 	/* .name = */		"ssh-ed25519-cert-v01 (at) openssh.com",
    328 	/* .shortname = */	"ED25519-CERT",
    329 	/* .sigalg = */		NULL,
    330 	/* .type = */		KEY_ED25519_CERT,
    331 	/* .nid = */		0,
    332 	/* .cert = */		1,
    333 	/* .sigonly = */	0,
    334 	/* .keybits = */	256,
    335 	/* .funcs = */		&sshkey_ed25519_funcs,
    336 };
    337