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