1 /* $OpenBSD: ssh-ed25519.c,v 1.22 2026/02/14 00:18:34 jsg Exp $ */
2 /*
3 * Copyright (c) 2013 Markus Friedl <markus@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
18 #include "includes.h"
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
ssh_ed25519_cleanup(struct sshkey * k)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
ssh_ed25519_equal(const struct sshkey * a,const struct sshkey * b)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
ssh_ed25519_serialize_public(const struct sshkey * key,struct sshbuf * b,enum sshkey_serialize_rep opts)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
ssh_ed25519_serialize_private(const struct sshkey * key,struct sshbuf * b,enum sshkey_serialize_rep opts)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
ssh_ed25519_generate(struct sshkey * k,int bits)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 crypto_sign_ed25519_keypair(k->ed25519_pk, k->ed25519_sk);
87 return 0;
88 }
89
90 static int
ssh_ed25519_copy_public(const struct sshkey * from,struct sshkey * to)91 ssh_ed25519_copy_public(const struct sshkey *from, struct sshkey *to)
92 {
93 if (from->ed25519_pk == NULL)
94 return 0; /* XXX SSH_ERR_INTERNAL_ERROR ? */
95 if ((to->ed25519_pk = malloc(ED25519_PK_SZ)) == NULL)
96 return SSH_ERR_ALLOC_FAIL;
97 memcpy(to->ed25519_pk, from->ed25519_pk, ED25519_PK_SZ);
98 return 0;
99 }
100
101 static int
ssh_ed25519_deserialize_public(const char * ktype,struct sshbuf * b,struct sshkey * key)102 ssh_ed25519_deserialize_public(const char *ktype, struct sshbuf *b,
103 struct sshkey *key)
104 {
105 u_char *pk = NULL;
106 size_t len = 0;
107 int r;
108
109 if ((r = sshbuf_get_string(b, &pk, &len)) != 0)
110 return r;
111 if (len != ED25519_PK_SZ) {
112 freezero(pk, len);
113 return SSH_ERR_INVALID_FORMAT;
114 }
115 key->ed25519_pk = pk;
116 return 0;
117 }
118
119 static int
ssh_ed25519_deserialize_private(const char * ktype,struct sshbuf * b,struct sshkey * key)120 ssh_ed25519_deserialize_private(const char *ktype, struct sshbuf *b,
121 struct sshkey *key)
122 {
123 int r;
124 size_t sklen = 0;
125 u_char *ed25519_sk = NULL;
126
127 if ((r = ssh_ed25519_deserialize_public(NULL, b, key)) != 0)
128 goto out;
129 if ((r = sshbuf_get_string(b, &ed25519_sk, &sklen)) != 0)
130 goto out;
131 if (sklen != ED25519_SK_SZ) {
132 r = SSH_ERR_INVALID_FORMAT;
133 goto out;
134 }
135 key->ed25519_sk = ed25519_sk;
136 ed25519_sk = NULL; /* transferred */
137 /* success */
138 r = 0;
139 out:
140 freezero(ed25519_sk, sklen);
141 return r;
142 }
143
144 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)145 ssh_ed25519_sign(struct sshkey *key,
146 u_char **sigp, size_t *lenp,
147 const u_char *data, size_t datalen,
148 const char *alg, const char *sk_provider, const char *sk_pin, u_int compat)
149 {
150 u_char *sig = NULL;
151 size_t slen = 0;
152 unsigned long long smlen;
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 datalen >= INT_MAX - crypto_sign_ed25519_BYTES)
164 return SSH_ERR_INVALID_ARGUMENT;
165 smlen = slen = datalen + crypto_sign_ed25519_BYTES;
166 if ((sig = malloc(slen)) == NULL)
167 return SSH_ERR_ALLOC_FAIL;
168
169 if ((ret = crypto_sign_ed25519(sig, &smlen, data, datalen,
170 key->ed25519_sk)) != 0 || smlen <= datalen) {
171 r = SSH_ERR_INVALID_ARGUMENT; /* XXX better error? */
172 goto out;
173 }
174 if ((r = ssh_ed25519_encode_store_sig(sig, smlen - datalen,
175 sigp, lenp)) != 0)
176 goto out;
177
178 /* success */
179 r = 0;
180 out:
181 freezero(sig, slen);
182 return r;
183 }
184
185 int
ssh_ed25519_encode_store_sig(const u_char * sig,size_t slen,u_char ** sigp,size_t * lenp)186 ssh_ed25519_encode_store_sig(const u_char *sig, size_t slen,
187 u_char **sigp, size_t *lenp)
188 {
189 struct sshbuf *b = NULL;
190 int r = -1;
191 size_t len;
192
193 if (lenp != NULL)
194 *lenp = 0;
195 if (sigp != NULL)
196 *sigp = NULL;
197
198 if (slen != crypto_sign_ed25519_BYTES)
199 return SSH_ERR_INVALID_ARGUMENT;
200
201 /* encode signature */
202 if ((b = sshbuf_new()) == NULL) {
203 r = SSH_ERR_ALLOC_FAIL;
204 goto out;
205 }
206 if ((r = sshbuf_put_cstring(b, "ssh-ed25519")) != 0 ||
207 (r = sshbuf_put_string(b, sig, slen)) != 0)
208 goto out;
209 len = sshbuf_len(b);
210 if (sigp != NULL) {
211 if ((*sigp = malloc(len)) == NULL) {
212 r = SSH_ERR_ALLOC_FAIL;
213 goto out;
214 }
215 memcpy(*sigp, sshbuf_ptr(b), len);
216 }
217 if (lenp != NULL)
218 *lenp = len;
219 /* success */
220 r = 0;
221 out:
222 sshbuf_free(b);
223 return r;
224 }
225
226 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)227 ssh_ed25519_verify(const struct sshkey *key,
228 const u_char *sig, size_t siglen,
229 const u_char *data, size_t dlen, const char *alg, u_int compat,
230 struct sshkey_sig_details **detailsp)
231 {
232 struct sshbuf *b = NULL;
233 char *ktype = NULL;
234 const u_char *sigblob;
235 u_char *sm = NULL, *m = NULL;
236 size_t len;
237 unsigned long long smlen = 0, mlen = 0;
238 int r, ret;
239
240 if (key == NULL ||
241 sshkey_type_plain(key->type) != KEY_ED25519 ||
242 key->ed25519_pk == NULL ||
243 dlen >= INT_MAX - crypto_sign_ed25519_BYTES ||
244 sig == NULL || siglen == 0)
245 return SSH_ERR_INVALID_ARGUMENT;
246
247 if ((b = sshbuf_from(sig, siglen)) == NULL)
248 return SSH_ERR_ALLOC_FAIL;
249 if ((r = sshbuf_get_cstring(b, &ktype, NULL)) != 0 ||
250 (r = sshbuf_get_string_direct(b, &sigblob, &len)) != 0)
251 goto out;
252 if (strcmp("ssh-ed25519", ktype) != 0) {
253 r = SSH_ERR_KEY_TYPE_MISMATCH;
254 goto out;
255 }
256 if (sshbuf_len(b) != 0) {
257 r = SSH_ERR_UNEXPECTED_TRAILING_DATA;
258 goto out;
259 }
260 if (len > crypto_sign_ed25519_BYTES) {
261 r = SSH_ERR_INVALID_FORMAT;
262 goto out;
263 }
264 if (dlen >= SIZE_MAX - len) {
265 r = SSH_ERR_INVALID_ARGUMENT;
266 goto out;
267 }
268 smlen = len + dlen;
269 mlen = smlen;
270 if ((sm = malloc(smlen)) == NULL || (m = malloc(mlen)) == NULL) {
271 r = SSH_ERR_ALLOC_FAIL;
272 goto out;
273 }
274 memcpy(sm, sigblob, len);
275 memcpy(sm+len, data, dlen);
276 if ((ret = crypto_sign_ed25519_open(m, &mlen, sm, smlen,
277 key->ed25519_pk)) != 0) {
278 debug2_f("crypto_sign_ed25519_open failed: %d", ret);
279 }
280 if (ret != 0 || mlen != dlen) {
281 r = SSH_ERR_SIGNATURE_INVALID;
282 goto out;
283 }
284 /* XXX compare 'm' and 'data' ? */
285 /* success */
286 r = 0;
287 out:
288 if (sm != NULL)
289 freezero(sm, smlen);
290 if (m != NULL)
291 freezero(m, smlen); /* NB mlen may be invalid if r != 0 */
292 sshbuf_free(b);
293 free(ktype);
294 return r;
295 }
296
297 /* NB. not static; used by ED25519-SK */
298 const struct sshkey_impl_funcs sshkey_ed25519_funcs = {
299 /* .size = */ NULL,
300 /* .alloc = */ NULL,
301 /* .cleanup = */ ssh_ed25519_cleanup,
302 /* .equal = */ ssh_ed25519_equal,
303 /* .ssh_serialize_public = */ ssh_ed25519_serialize_public,
304 /* .ssh_deserialize_public = */ ssh_ed25519_deserialize_public,
305 /* .ssh_serialize_private = */ ssh_ed25519_serialize_private,
306 /* .ssh_deserialize_private = */ ssh_ed25519_deserialize_private,
307 /* .generate = */ ssh_ed25519_generate,
308 /* .copy_public = */ ssh_ed25519_copy_public,
309 /* .sign = */ ssh_ed25519_sign,
310 /* .verify = */ ssh_ed25519_verify,
311 };
312
313 const struct sshkey_impl sshkey_ed25519_impl = {
314 /* .name = */ "ssh-ed25519",
315 /* .shortname = */ "ED25519",
316 /* .sigalg = */ NULL,
317 /* .type = */ KEY_ED25519,
318 /* .nid = */ 0,
319 /* .cert = */ 0,
320 /* .sigonly = */ 0,
321 /* .keybits = */ 256,
322 /* .funcs = */ &sshkey_ed25519_funcs,
323 };
324
325 const struct sshkey_impl sshkey_ed25519_cert_impl = {
326 /* .name = */ "ssh-ed25519-cert-v01@openssh.com",
327 /* .shortname = */ "ED25519-CERT",
328 /* .sigalg = */ NULL,
329 /* .type = */ KEY_ED25519_CERT,
330 /* .nid = */ 0,
331 /* .cert = */ 1,
332 /* .sigonly = */ 0,
333 /* .keybits = */ 256,
334 /* .funcs = */ &sshkey_ed25519_funcs,
335 };
336