Coverage Report

Created: 2026-02-26 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssh/ssh-ed25519.c
Line
Count
Source
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
35
ssh_ed25519_cleanup(struct sshkey *k)
36
2.28k
{
37
2.28k
  freezero(k->ed25519_pk, ED25519_PK_SZ);
38
2.28k
  freezero(k->ed25519_sk, ED25519_SK_SZ);
39
2.28k
  k->ed25519_pk = NULL;
40
2.28k
  k->ed25519_sk = NULL;
41
2.28k
}
42
43
static int
44
ssh_ed25519_equal(const struct sshkey *a, const struct sshkey *b)
45
0
{
46
0
  if (a->ed25519_pk == NULL || b->ed25519_pk == NULL)
47
0
    return 0;
48
0
  if (memcmp(a->ed25519_pk, b->ed25519_pk, ED25519_PK_SZ) != 0)
49
0
    return 0;
50
0
  return 1;
51
0
}
52
53
static int
54
ssh_ed25519_serialize_public(const struct sshkey *key, struct sshbuf *b,
55
    enum sshkey_serialize_rep opts)
56
0
{
57
0
  int r;
58
59
0
  if (key->ed25519_pk == NULL)
60
0
    return SSH_ERR_INVALID_ARGUMENT;
61
0
  if ((r = sshbuf_put_string(b, key->ed25519_pk, ED25519_PK_SZ)) != 0)
62
0
    return r;
63
64
0
  return 0;
65
0
}
66
67
static int
68
ssh_ed25519_serialize_private(const struct sshkey *key, struct sshbuf *b,
69
    enum sshkey_serialize_rep opts)
70
0
{
71
0
  int r;
72
73
0
  if ((r = sshbuf_put_string(b, key->ed25519_pk, ED25519_PK_SZ)) != 0 ||
74
0
      (r = sshbuf_put_string(b, key->ed25519_sk, ED25519_SK_SZ)) != 0)
75
0
    return r;
76
77
0
  return 0;
78
0
}
79
80
static int
81
ssh_ed25519_generate(struct sshkey *k, int bits)
82
0
{
83
0
  if ((k->ed25519_pk = malloc(ED25519_PK_SZ)) == NULL ||
84
0
      (k->ed25519_sk = malloc(ED25519_SK_SZ)) == NULL)
85
0
    return SSH_ERR_ALLOC_FAIL;
86
0
  crypto_sign_ed25519_keypair(k->ed25519_pk, k->ed25519_sk);
87
0
  return 0;
88
0
}
89
90
static int
91
ssh_ed25519_copy_public(const struct sshkey *from, struct sshkey *to)
92
0
{
93
0
  if (from->ed25519_pk == NULL)
94
0
    return 0; /* XXX SSH_ERR_INTERNAL_ERROR ? */
95
0
  if ((to->ed25519_pk = malloc(ED25519_PK_SZ)) == NULL)
96
0
    return SSH_ERR_ALLOC_FAIL;
97
0
  memcpy(to->ed25519_pk, from->ed25519_pk, ED25519_PK_SZ);
98
0
  return 0;
99
0
}
100
101
static int
102
ssh_ed25519_deserialize_public(const char *ktype, struct sshbuf *b,
103
    struct sshkey *key)
104
2.41k
{
105
2.41k
  u_char *pk = NULL;
106
2.41k
  size_t len = 0;
107
2.41k
  int r;
108
109
2.41k
  if ((r = sshbuf_get_string(b, &pk, &len)) != 0)
110
59
    return r;
111
2.35k
  if (len != ED25519_PK_SZ) {
112
65
    freezero(pk, len);
113
65
    return SSH_ERR_INVALID_FORMAT;
114
65
  }
115
2.28k
  key->ed25519_pk = pk;
116
2.28k
  return 0;
117
2.35k
}
118
119
static int
120
ssh_ed25519_deserialize_private(const char *ktype, struct sshbuf *b,
121
    struct sshkey *key)
122
227
{
123
227
  int r;
124
227
  size_t sklen = 0;
125
227
  u_char *ed25519_sk = NULL;
126
127
227
  if ((r = ssh_ed25519_deserialize_public(NULL, b, key)) != 0)
128
89
    goto out;
129
138
  if ((r = sshbuf_get_string(b, &ed25519_sk, &sklen)) != 0)
130
2
    goto out;
131
136
  if (sklen != ED25519_SK_SZ) {
132
10
    r = SSH_ERR_INVALID_FORMAT;
133
10
    goto out;
134
10
  }
135
126
  key->ed25519_sk = ed25519_sk;
136
126
  ed25519_sk = NULL; /* transferred */
137
  /* success */
138
126
  r = 0;
139
227
 out:
140
227
  freezero(ed25519_sk, sklen);
141
227
  return r;
142
126
}
143
144
static int
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
0
{
150
0
  u_char *sig = NULL;
151
0
  size_t slen = 0;
152
0
  unsigned long long smlen;
153
0
  int r, ret;
154
155
0
  if (lenp != NULL)
156
0
    *lenp = 0;
157
0
  if (sigp != NULL)
158
0
    *sigp = NULL;
159
160
0
  if (key == NULL ||
161
0
      sshkey_type_plain(key->type) != KEY_ED25519 ||
162
0
      key->ed25519_sk == NULL ||
163
0
      datalen >= INT_MAX - crypto_sign_ed25519_BYTES)
164
0
    return SSH_ERR_INVALID_ARGUMENT;
165
0
  smlen = slen = datalen + crypto_sign_ed25519_BYTES;
166
0
  if ((sig = malloc(slen)) == NULL)
167
0
    return SSH_ERR_ALLOC_FAIL;
168
169
0
  if ((ret = crypto_sign_ed25519(sig, &smlen, data, datalen,
170
0
      key->ed25519_sk)) != 0 || smlen <= datalen) {
171
0
    r = SSH_ERR_INVALID_ARGUMENT; /* XXX better error? */
172
0
    goto out;
173
0
  }
174
0
  if ((r = ssh_ed25519_encode_store_sig(sig, smlen - datalen,
175
0
      sigp, lenp)) != 0)
176
0
    goto out;
177
178
  /* success */
179
0
  r = 0;
180
0
 out:
181
0
  freezero(sig, slen);
182
0
  return r;
183
0
}
184
185
int
186
ssh_ed25519_encode_store_sig(const u_char *sig, size_t slen,
187
    u_char **sigp, size_t *lenp)
188
0
{
189
0
  struct sshbuf *b = NULL;
190
0
  int r = -1;
191
0
  size_t len;
192
193
0
  if (lenp != NULL)
194
0
    *lenp = 0;
195
0
  if (sigp != NULL)
196
0
    *sigp = NULL;
197
198
0
  if (slen != crypto_sign_ed25519_BYTES)
199
0
    return SSH_ERR_INVALID_ARGUMENT;
200
201
  /* encode signature */
202
0
  if ((b = sshbuf_new()) == NULL) {
203
0
    r = SSH_ERR_ALLOC_FAIL;
204
0
    goto out;
205
0
  }
206
0
  if ((r = sshbuf_put_cstring(b, "ssh-ed25519")) != 0 ||
207
0
      (r = sshbuf_put_string(b, sig, slen)) != 0)
208
0
    goto out;
209
0
  len = sshbuf_len(b);
210
0
  if (sigp != NULL) {
211
0
    if ((*sigp = malloc(len)) == NULL) {
212
0
      r = SSH_ERR_ALLOC_FAIL;
213
0
      goto out;
214
0
    }
215
0
    memcpy(*sigp, sshbuf_ptr(b), len);
216
0
  }
217
0
  if (lenp != NULL)
218
0
    *lenp = len;
219
  /* success */
220
0
  r = 0;
221
0
 out:
222
0
  sshbuf_free(b);
223
0
  return r;
224
0
}
225
226
static int
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
174
{
232
174
  struct sshbuf *b = NULL;
233
174
  char *ktype = NULL;
234
174
  const u_char *sigblob;
235
174
  u_char *sm = NULL, *m = NULL;
236
174
  size_t len;
237
174
  unsigned long long smlen = 0, mlen = 0;
238
174
  int r, ret;
239
240
174
  if (key == NULL ||
241
174
      sshkey_type_plain(key->type) != KEY_ED25519 ||
242
174
      key->ed25519_pk == NULL ||
243
174
      dlen >= INT_MAX - crypto_sign_ed25519_BYTES ||
244
174
      sig == NULL || siglen == 0)
245
0
    return SSH_ERR_INVALID_ARGUMENT;
246
247
174
  if ((b = sshbuf_from(sig, siglen)) == NULL)
248
0
    return SSH_ERR_ALLOC_FAIL;
249
174
  if ((r = sshbuf_get_cstring(b, &ktype, NULL)) != 0 ||
250
142
      (r = sshbuf_get_string_direct(b, &sigblob, &len)) != 0)
251
37
    goto out;
252
137
  if (strcmp("ssh-ed25519", ktype) != 0) {
253
100
    r = SSH_ERR_KEY_TYPE_MISMATCH;
254
100
    goto out;
255
100
  }
256
37
  if (sshbuf_len(b) != 0) {
257
16
    r = SSH_ERR_UNEXPECTED_TRAILING_DATA;
258
16
    goto out;
259
16
  }
260
21
  if (len > crypto_sign_ed25519_BYTES) {
261
1
    r = SSH_ERR_INVALID_FORMAT;
262
1
    goto out;
263
1
  }
264
20
  if (dlen >= SIZE_MAX - len) {
265
0
    r = SSH_ERR_INVALID_ARGUMENT;
266
0
    goto out;
267
0
  }
268
20
  smlen = len + dlen;
269
20
  mlen = smlen;
270
20
  if ((sm = malloc(smlen)) == NULL || (m = malloc(mlen)) == NULL) {
271
0
    r = SSH_ERR_ALLOC_FAIL;
272
0
    goto out;
273
0
  }
274
20
  memcpy(sm, sigblob, len);
275
20
  memcpy(sm+len, data, dlen);
276
20
  if ((ret = crypto_sign_ed25519_open(m, &mlen, sm, smlen,
277
20
      key->ed25519_pk)) != 0) {
278
16
    debug2_f("crypto_sign_ed25519_open failed: %d", ret);
279
16
  }
280
20
  if (ret != 0 || mlen != dlen) {
281
18
    r = SSH_ERR_SIGNATURE_INVALID;
282
18
    goto out;
283
18
  }
284
  /* XXX compare 'm' and 'data' ? */
285
  /* success */
286
2
  r = 0;
287
174
 out:
288
174
  if (sm != NULL)
289
20
    freezero(sm, smlen);
290
174
  if (m != NULL)
291
20
    freezero(m, smlen); /* NB mlen may be invalid if r != 0 */
292
174
  sshbuf_free(b);
293
174
  free(ktype);
294
174
  return r;
295
2
}
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
};