xref: /openbsd-src/usr.bin/ssh/ssh-ed25519.c (revision a2c931d988e31395022f1322d85edf6de1b6b1db)
1*a2c931d9Sdjm /* $OpenBSD: ssh-ed25519.c,v 1.19 2022/10/28 00:44:44 djm Exp $ */
28ffbcf6dSmarkus /*
38ffbcf6dSmarkus  * Copyright (c) 2013 Markus Friedl <markus@openbsd.org>
48ffbcf6dSmarkus  *
58ffbcf6dSmarkus  * Permission to use, copy, modify, and distribute this software for any
68ffbcf6dSmarkus  * purpose with or without fee is hereby granted, provided that the above
78ffbcf6dSmarkus  * copyright notice and this permission notice appear in all copies.
88ffbcf6dSmarkus  *
98ffbcf6dSmarkus  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
108ffbcf6dSmarkus  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
118ffbcf6dSmarkus  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
128ffbcf6dSmarkus  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
138ffbcf6dSmarkus  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
148ffbcf6dSmarkus  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
158ffbcf6dSmarkus  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
168ffbcf6dSmarkus  */
17ea2d8289Sdjm #define SSHKEY_INTERNAL
188ffbcf6dSmarkus #include <sys/types.h>
19ea2d8289Sdjm #include <limits.h>
208ffbcf6dSmarkus 
218ffbcf6dSmarkus #include "crypto_api.h"
228ffbcf6dSmarkus 
238ffbcf6dSmarkus #include <string.h>
248ffbcf6dSmarkus #include <stdarg.h>
258ffbcf6dSmarkus 
268ffbcf6dSmarkus #include "log.h"
274e045c6aSmarkus #include "sshbuf.h"
28ea2d8289Sdjm #include "sshkey.h"
29ea2d8289Sdjm #include "ssherr.h"
308ffbcf6dSmarkus #include "ssh.h"
318ffbcf6dSmarkus 
329c1667dbSdjm static void
ssh_ed25519_cleanup(struct sshkey * k)339c1667dbSdjm ssh_ed25519_cleanup(struct sshkey *k)
349c1667dbSdjm {
359c1667dbSdjm 	freezero(k->ed25519_pk, ED25519_PK_SZ);
369c1667dbSdjm 	freezero(k->ed25519_sk, ED25519_SK_SZ);
379c1667dbSdjm 	k->ed25519_pk = NULL;
389c1667dbSdjm 	k->ed25519_sk = NULL;
399c1667dbSdjm }
409c1667dbSdjm 
41712f5ecfSdjm static int
ssh_ed25519_equal(const struct sshkey * a,const struct sshkey * b)42712f5ecfSdjm ssh_ed25519_equal(const struct sshkey *a, const struct sshkey *b)
43712f5ecfSdjm {
44712f5ecfSdjm 	if (a->ed25519_pk == NULL || b->ed25519_pk == NULL)
45712f5ecfSdjm 		return 0;
46712f5ecfSdjm 	if (memcmp(a->ed25519_pk, b->ed25519_pk, ED25519_PK_SZ) != 0)
47712f5ecfSdjm 		return 0;
48712f5ecfSdjm 	return 1;
49712f5ecfSdjm }
50712f5ecfSdjm 
51eefcf659Sdjm static int
ssh_ed25519_serialize_public(const struct sshkey * key,struct sshbuf * b,enum sshkey_serialize_rep opts)52eefcf659Sdjm ssh_ed25519_serialize_public(const struct sshkey *key, struct sshbuf *b,
53c8d92406Sdjm     enum sshkey_serialize_rep opts)
54eefcf659Sdjm {
55eefcf659Sdjm 	int r;
56eefcf659Sdjm 
57eefcf659Sdjm 	if (key->ed25519_pk == NULL)
58eefcf659Sdjm 		return SSH_ERR_INVALID_ARGUMENT;
59c8d92406Sdjm 	if ((r = sshbuf_put_string(b, key->ed25519_pk, ED25519_PK_SZ)) != 0)
60eefcf659Sdjm 		return r;
61eefcf659Sdjm 
62eefcf659Sdjm 	return 0;
63eefcf659Sdjm }
64eefcf659Sdjm 
65b6025febSdjm static int
ssh_ed25519_serialize_private(const struct sshkey * key,struct sshbuf * b,enum sshkey_serialize_rep opts)66d03db38bSdjm ssh_ed25519_serialize_private(const struct sshkey *key, struct sshbuf *b,
67d03db38bSdjm     enum sshkey_serialize_rep opts)
68d03db38bSdjm {
69d03db38bSdjm 	int r;
70d03db38bSdjm 
71d03db38bSdjm 	if ((r = sshbuf_put_string(b, key->ed25519_pk, ED25519_PK_SZ)) != 0 ||
72d03db38bSdjm 	    (r = sshbuf_put_string(b, key->ed25519_sk, ED25519_SK_SZ)) != 0)
73d03db38bSdjm 		return r;
74d03db38bSdjm 
75d03db38bSdjm 	return 0;
76d03db38bSdjm }
77d03db38bSdjm 
78d03db38bSdjm static int
ssh_ed25519_generate(struct sshkey * k,int bits)79b6025febSdjm ssh_ed25519_generate(struct sshkey *k, int bits)
80b6025febSdjm {
81b6025febSdjm 	if ((k->ed25519_pk = malloc(ED25519_PK_SZ)) == NULL ||
82b6025febSdjm 	    (k->ed25519_sk = malloc(ED25519_SK_SZ)) == NULL)
83b6025febSdjm 		return SSH_ERR_ALLOC_FAIL;
84b6025febSdjm 	crypto_sign_ed25519_keypair(k->ed25519_pk, k->ed25519_sk);
85b6025febSdjm 	return 0;
86b6025febSdjm }
87b6025febSdjm 
880d39f001Sdjm static int
ssh_ed25519_copy_public(const struct sshkey * from,struct sshkey * to)890d39f001Sdjm ssh_ed25519_copy_public(const struct sshkey *from, struct sshkey *to)
900d39f001Sdjm {
910d39f001Sdjm 	if (from->ed25519_pk == NULL)
920d39f001Sdjm 		return 0; /* XXX SSH_ERR_INTERNAL_ERROR ? */
930d39f001Sdjm 	if ((to->ed25519_pk = malloc(ED25519_PK_SZ)) == NULL)
940d39f001Sdjm 		return SSH_ERR_ALLOC_FAIL;
950d39f001Sdjm 	memcpy(to->ed25519_pk, from->ed25519_pk, ED25519_PK_SZ);
960d39f001Sdjm 	return 0;
970d39f001Sdjm }
980d39f001Sdjm 
99c8d92406Sdjm static int
ssh_ed25519_deserialize_public(const char * ktype,struct sshbuf * b,struct sshkey * key)100c8d92406Sdjm ssh_ed25519_deserialize_public(const char *ktype, struct sshbuf *b,
101c8d92406Sdjm     struct sshkey *key)
102c8d92406Sdjm {
103c8d92406Sdjm 	u_char *pk = NULL;
104c8d92406Sdjm 	size_t len = 0;
105c8d92406Sdjm 	int r;
106c8d92406Sdjm 
107c8d92406Sdjm 	if ((r = sshbuf_get_string(b, &pk, &len)) != 0)
108c8d92406Sdjm 		return r;
109c8d92406Sdjm 	if (len != ED25519_PK_SZ) {
110c8d92406Sdjm 		freezero(pk, len);
111c8d92406Sdjm 		return SSH_ERR_INVALID_FORMAT;
112c8d92406Sdjm 	}
113c8d92406Sdjm 	key->ed25519_pk = pk;
114c8d92406Sdjm 	return 0;
115c8d92406Sdjm }
116c8d92406Sdjm 
117c5c174faSdjm static int
ssh_ed25519_deserialize_private(const char * ktype,struct sshbuf * b,struct sshkey * key)118*a2c931d9Sdjm ssh_ed25519_deserialize_private(const char *ktype, struct sshbuf *b,
119*a2c931d9Sdjm     struct sshkey *key)
120*a2c931d9Sdjm {
121*a2c931d9Sdjm 	int r;
122*a2c931d9Sdjm 	size_t sklen = 0;
123*a2c931d9Sdjm 	u_char *ed25519_sk = NULL;
124*a2c931d9Sdjm 
125*a2c931d9Sdjm 	if ((r = ssh_ed25519_deserialize_public(NULL, b, key)) != 0)
126*a2c931d9Sdjm 		goto out;
127*a2c931d9Sdjm 	if ((r = sshbuf_get_string(b, &ed25519_sk, &sklen)) != 0)
128*a2c931d9Sdjm 		goto out;
129*a2c931d9Sdjm 	if (sklen != ED25519_SK_SZ) {
130*a2c931d9Sdjm 		r = SSH_ERR_INVALID_FORMAT;
131*a2c931d9Sdjm 		goto out;
132*a2c931d9Sdjm 	}
133*a2c931d9Sdjm 	key->ed25519_sk = ed25519_sk;
134*a2c931d9Sdjm 	ed25519_sk = NULL; /* transferred */
135*a2c931d9Sdjm 	/* success */
136*a2c931d9Sdjm 	r = 0;
137*a2c931d9Sdjm  out:
138*a2c931d9Sdjm 	freezero(ed25519_sk, sklen);
139*a2c931d9Sdjm 	return r;
140*a2c931d9Sdjm }
141*a2c931d9Sdjm 
142*a2c931d9Sdjm static int
ssh_ed25519_sign(struct sshkey * key,u_char ** sigp,size_t * lenp,const u_char * data,size_t datalen,const char * alg,const char * sk_provider,const char * sk_pin,u_int compat)143c5c174faSdjm ssh_ed25519_sign(struct sshkey *key,
144c5c174faSdjm     u_char **sigp, size_t *lenp,
145c5c174faSdjm     const u_char *data, size_t datalen,
146c5c174faSdjm     const char *alg, const char *sk_provider, const char *sk_pin, u_int compat)
1478ffbcf6dSmarkus {
148ea2d8289Sdjm 	u_char *sig = NULL;
149ea2d8289Sdjm 	size_t slen = 0, len;
1508ffbcf6dSmarkus 	unsigned long long smlen;
151ea2d8289Sdjm 	int r, ret;
152ea2d8289Sdjm 	struct sshbuf *b = NULL;
1538ffbcf6dSmarkus 
154ea2d8289Sdjm 	if (lenp != NULL)
155ea2d8289Sdjm 		*lenp = 0;
156ea2d8289Sdjm 	if (sigp != NULL)
157ea2d8289Sdjm 		*sigp = NULL;
1584f0a865cSdjm 
159ea2d8289Sdjm 	if (key == NULL ||
160ea2d8289Sdjm 	    sshkey_type_plain(key->type) != KEY_ED25519 ||
161ea2d8289Sdjm 	    key->ed25519_sk == NULL ||
162ea2d8289Sdjm 	    datalen >= INT_MAX - crypto_sign_ed25519_BYTES)
163ea2d8289Sdjm 		return SSH_ERR_INVALID_ARGUMENT;
1648ffbcf6dSmarkus 	smlen = slen = datalen + crypto_sign_ed25519_BYTES;
165ea2d8289Sdjm 	if ((sig = malloc(slen)) == NULL)
166ea2d8289Sdjm 		return SSH_ERR_ALLOC_FAIL;
1678ffbcf6dSmarkus 
1688ffbcf6dSmarkus 	if ((ret = crypto_sign_ed25519(sig, &smlen, data, datalen,
1698ffbcf6dSmarkus 	    key->ed25519_sk)) != 0 || smlen <= datalen) {
170ea2d8289Sdjm 		r = SSH_ERR_INVALID_ARGUMENT; /* XXX better error? */
171ea2d8289Sdjm 		goto out;
1728ffbcf6dSmarkus 	}
1738ffbcf6dSmarkus 	/* encode signature */
174ea2d8289Sdjm 	if ((b = sshbuf_new()) == NULL) {
175ea2d8289Sdjm 		r = SSH_ERR_ALLOC_FAIL;
176ea2d8289Sdjm 		goto out;
177ea2d8289Sdjm 	}
178ea2d8289Sdjm 	if ((r = sshbuf_put_cstring(b, "ssh-ed25519")) != 0 ||
179ea2d8289Sdjm 	    (r = sshbuf_put_string(b, sig, smlen - datalen)) != 0)
180ea2d8289Sdjm 		goto out;
181ea2d8289Sdjm 	len = sshbuf_len(b);
182ea2d8289Sdjm 	if (sigp != NULL) {
183ea2d8289Sdjm 		if ((*sigp = malloc(len)) == NULL) {
184ea2d8289Sdjm 			r = SSH_ERR_ALLOC_FAIL;
185ea2d8289Sdjm 			goto out;
186ea2d8289Sdjm 		}
187ea2d8289Sdjm 		memcpy(*sigp, sshbuf_ptr(b), len);
188ea2d8289Sdjm 	}
1898ffbcf6dSmarkus 	if (lenp != NULL)
1908ffbcf6dSmarkus 		*lenp = len;
191ea2d8289Sdjm 	/* success */
192ea2d8289Sdjm 	r = 0;
193ea2d8289Sdjm  out:
194ea2d8289Sdjm 	sshbuf_free(b);
195c9831b39Sjsg 	if (sig != NULL)
196c9831b39Sjsg 		freezero(sig, slen);
1978ffbcf6dSmarkus 
198ea2d8289Sdjm 	return r;
1998ffbcf6dSmarkus }
2008ffbcf6dSmarkus 
201c5c174faSdjm static int
ssh_ed25519_verify(const struct sshkey * key,const u_char * sig,size_t siglen,const u_char * data,size_t dlen,const char * alg,u_int compat,struct sshkey_sig_details ** detailsp)202ea2d8289Sdjm ssh_ed25519_verify(const struct sshkey *key,
203c5c174faSdjm     const u_char *sig, size_t siglen,
204c5c174faSdjm     const u_char *data, size_t dlen, const char *alg, u_int compat,
205c5c174faSdjm     struct sshkey_sig_details **detailsp)
2068ffbcf6dSmarkus {
207ea2d8289Sdjm 	struct sshbuf *b = NULL;
208ea2d8289Sdjm 	char *ktype = NULL;
209ea2d8289Sdjm 	const u_char *sigblob;
210ea2d8289Sdjm 	u_char *sm = NULL, *m = NULL;
211ea2d8289Sdjm 	size_t len;
212ea2d8289Sdjm 	unsigned long long smlen = 0, mlen = 0;
213ea2d8289Sdjm 	int r, ret;
2148ffbcf6dSmarkus 
215ea2d8289Sdjm 	if (key == NULL ||
216ea2d8289Sdjm 	    sshkey_type_plain(key->type) != KEY_ED25519 ||
217ea2d8289Sdjm 	    key->ed25519_pk == NULL ||
218c5c174faSdjm 	    dlen >= INT_MAX - crypto_sign_ed25519_BYTES ||
219c5c174faSdjm 	    sig == NULL || siglen == 0)
220ea2d8289Sdjm 		return SSH_ERR_INVALID_ARGUMENT;
221ea2d8289Sdjm 
222c5c174faSdjm 	if ((b = sshbuf_from(sig, siglen)) == NULL)
223ea2d8289Sdjm 		return SSH_ERR_ALLOC_FAIL;
224ea2d8289Sdjm 	if ((r = sshbuf_get_cstring(b, &ktype, NULL)) != 0 ||
225ea2d8289Sdjm 	    (r = sshbuf_get_string_direct(b, &sigblob, &len)) != 0)
226ea2d8289Sdjm 		goto out;
2278ffbcf6dSmarkus 	if (strcmp("ssh-ed25519", ktype) != 0) {
228ea2d8289Sdjm 		r = SSH_ERR_KEY_TYPE_MISMATCH;
229ea2d8289Sdjm 		goto out;
2308ffbcf6dSmarkus 	}
231ea2d8289Sdjm 	if (sshbuf_len(b) != 0) {
232ea2d8289Sdjm 		r = SSH_ERR_UNEXPECTED_TRAILING_DATA;
233ea2d8289Sdjm 		goto out;
2348ffbcf6dSmarkus 	}
2358ffbcf6dSmarkus 	if (len > crypto_sign_ed25519_BYTES) {
236ea2d8289Sdjm 		r = SSH_ERR_INVALID_FORMAT;
237ea2d8289Sdjm 		goto out;
2388ffbcf6dSmarkus 	}
239c5c174faSdjm 	if (dlen >= SIZE_MAX - len) {
240cae4e563Sdaniel 		r = SSH_ERR_INVALID_ARGUMENT;
241cae4e563Sdaniel 		goto out;
242cae4e563Sdaniel 	}
243c5c174faSdjm 	smlen = len + dlen;
244ea2d8289Sdjm 	mlen = smlen;
2454e045c6aSmarkus 	if ((sm = malloc(smlen)) == NULL || (m = malloc(mlen)) == NULL) {
246ea2d8289Sdjm 		r = SSH_ERR_ALLOC_FAIL;
247ea2d8289Sdjm 		goto out;
248ea2d8289Sdjm 	}
2498ffbcf6dSmarkus 	memcpy(sm, sigblob, len);
250c5c174faSdjm 	memcpy(sm+len, data, dlen);
2518ffbcf6dSmarkus 	if ((ret = crypto_sign_ed25519_open(m, &mlen, sm, smlen,
2528ffbcf6dSmarkus 	    key->ed25519_pk)) != 0) {
25348e6b99dSdjm 		debug2_f("crypto_sign_ed25519_open failed: %d", ret);
2548ffbcf6dSmarkus 	}
255c5c174faSdjm 	if (ret != 0 || mlen != dlen) {
256ea2d8289Sdjm 		r = SSH_ERR_SIGNATURE_INVALID;
257ea2d8289Sdjm 		goto out;
2588ffbcf6dSmarkus 	}
2598ffbcf6dSmarkus 	/* XXX compare 'm' and 'data' ? */
260ea2d8289Sdjm 	/* success */
261ea2d8289Sdjm 	r = 0;
262ea2d8289Sdjm  out:
263c9831b39Sjsg 	if (sm != NULL)
264c9831b39Sjsg 		freezero(sm, smlen);
265c9831b39Sjsg 	if (m != NULL)
266c9831b39Sjsg 		freezero(m, smlen); /* NB mlen may be invalid if r != 0 */
267ea2d8289Sdjm 	sshbuf_free(b);
268ea2d8289Sdjm 	free(ktype);
269ea2d8289Sdjm 	return r;
270ea2d8289Sdjm }
2719c1667dbSdjm 
272712f5ecfSdjm /* NB. not static; used by ED25519-SK */
273712f5ecfSdjm const struct sshkey_impl_funcs sshkey_ed25519_funcs = {
2749c1667dbSdjm 	/* .size = */		NULL,
2759c1667dbSdjm 	/* .alloc = */		NULL,
2769c1667dbSdjm 	/* .cleanup = */	ssh_ed25519_cleanup,
277712f5ecfSdjm 	/* .equal = */		ssh_ed25519_equal,
278eefcf659Sdjm 	/* .ssh_serialize_public = */ ssh_ed25519_serialize_public,
279c8d92406Sdjm 	/* .ssh_deserialize_public = */ ssh_ed25519_deserialize_public,
280d03db38bSdjm 	/* .ssh_serialize_private = */ ssh_ed25519_serialize_private,
281*a2c931d9Sdjm 	/* .ssh_deserialize_private = */ ssh_ed25519_deserialize_private,
282b6025febSdjm 	/* .generate = */	ssh_ed25519_generate,
2830d39f001Sdjm 	/* .copy_public = */	ssh_ed25519_copy_public,
284c5c174faSdjm 	/* .sign = */		ssh_ed25519_sign,
285c5c174faSdjm 	/* .verify = */		ssh_ed25519_verify,
2869c1667dbSdjm };
2879c1667dbSdjm 
2889c1667dbSdjm const struct sshkey_impl sshkey_ed25519_impl = {
2899c1667dbSdjm 	/* .name = */		"ssh-ed25519",
2909c1667dbSdjm 	/* .shortname = */	"ED25519",
2919c1667dbSdjm 	/* .sigalg = */		NULL,
2929c1667dbSdjm 	/* .type = */		KEY_ED25519,
2939c1667dbSdjm 	/* .nid = */		0,
2949c1667dbSdjm 	/* .cert = */		0,
2959c1667dbSdjm 	/* .sigonly = */	0,
2969c1667dbSdjm 	/* .keybits = */	256,
2979c1667dbSdjm 	/* .funcs = */		&sshkey_ed25519_funcs,
2989c1667dbSdjm };
2999c1667dbSdjm 
3009c1667dbSdjm const struct sshkey_impl sshkey_ed25519_cert_impl = {
3019c1667dbSdjm 	/* .name = */		"ssh-ed25519-cert-v01@openssh.com",
3029c1667dbSdjm 	/* .shortname = */	"ED25519-CERT",
3039c1667dbSdjm 	/* .sigalg = */		NULL,
3049c1667dbSdjm 	/* .type = */		KEY_ED25519_CERT,
3059c1667dbSdjm 	/* .nid = */		0,
3069c1667dbSdjm 	/* .cert = */		1,
3079c1667dbSdjm 	/* .sigonly = */	0,
3089c1667dbSdjm 	/* .keybits = */	256,
3099c1667dbSdjm 	/* .funcs = */		&sshkey_ed25519_funcs,
3109c1667dbSdjm };
311