Coverage Report

Created: 2026-07-16 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssh/ed25519-openssl.c
Line
Count
Source
1
/* $OpenBSD: ed25519-openssl.c,v 1.3 2026/06/29 02:08:55 djm Exp $ */
2
/*
3
 * Copyright (c) 2025 OpenSSH
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
/*
19
 * OpenSSL-based implementation of Ed25519 crypto_sign API
20
 * Alternative to the internal SUPERCOP-based implementation in ed25519.c
21
 */
22
23
#include "includes.h"
24
25
#ifdef OPENSSL_HAS_ED25519
26
27
#include <sys/types.h>
28
29
#include <string.h>
30
#include <stdint.h>
31
#include <limits.h>
32
33
#include <openssl/evp.h>
34
35
#include "crypto_api.h"
36
#include "log.h"
37
38
#if crypto_sign_ed25519_SECRETKEYBYTES <= crypto_sign_ed25519_PUBLICKEYBYTES
39
#error "crypto_sign_ed25519_SECRETKEYBYTES < crypto_sign_ed25519_PUBLICKEYBYTES"
40
#endif
41
42
#define SSH_ED25519_RAW_SECRET_KEY_LEN \
43
2
    (crypto_sign_ed25519_SECRETKEYBYTES - crypto_sign_ed25519_PUBLICKEYBYTES)
44
45
int
46
crypto_sign_ed25519_keypair(unsigned char *pk, unsigned char *sk)
47
1
{
48
1
  EVP_PKEY_CTX *ctx = NULL;
49
1
  EVP_PKEY *pkey = NULL;
50
1
  size_t pklen, sklen;
51
1
  int ret = -1;
52
53
1
  if ((ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL)) == NULL) {
54
0
    debug3_f("EVP_PKEY_CTX_new_id failed");
55
0
    goto out;
56
0
  }
57
1
  if (EVP_PKEY_keygen_init(ctx) <= 0) {
58
0
    debug3_f("EVP_PKEY_keygen_init failed");
59
0
    goto out;
60
0
  }
61
1
  if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
62
0
    debug3_f("EVP_PKEY_keygen failed");
63
0
    goto out;
64
0
  }
65
66
  /* Extract public key */
67
1
  pklen = crypto_sign_ed25519_PUBLICKEYBYTES;
68
1
  if (!EVP_PKEY_get_raw_public_key(pkey, pk, &pklen)) {
69
0
    debug3_f("EVP_PKEY_get_raw_public_key failed");
70
0
    goto out;
71
0
  }
72
1
  if (pklen != crypto_sign_ed25519_PUBLICKEYBYTES) {
73
0
    debug3_f("public key length mismatch: %zu", pklen);
74
0
    goto out;
75
0
  }
76
77
1
  sklen = SSH_ED25519_RAW_SECRET_KEY_LEN;
78
  /* Extract private key (32 bytes seed) */
79
1
  if (!EVP_PKEY_get_raw_private_key(pkey, sk, &sklen)) {
80
0
    debug3_f("EVP_PKEY_get_raw_private_key failed");
81
0
    goto out;
82
0
  }
83
1
  if (sklen != SSH_ED25519_RAW_SECRET_KEY_LEN) {
84
0
    debug3_f("private key length mismatch: %zu", sklen);
85
0
    goto out;
86
0
  }
87
88
  /* Append public key to secret key (SUPERCOP format compatibility) */
89
1
  memcpy(sk + sklen, pk, crypto_sign_ed25519_PUBLICKEYBYTES);
90
91
1
  ret = 0;
92
1
out:
93
1
  EVP_PKEY_free(pkey);
94
1
  EVP_PKEY_CTX_free(ctx);
95
1
  return ret;
96
1
}
97
98
int
99
crypto_sign_ed25519_keypair_from_seed(unsigned char *pk, unsigned char *sk,
100
    const unsigned char *seed)
101
0
{
102
0
  EVP_PKEY *pkey = NULL;
103
0
  size_t pklen;
104
0
  int ret = -1;
105
106
0
  if ((pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
107
0
      seed, SSH_ED25519_RAW_SECRET_KEY_LEN)) == NULL) {
108
0
    debug3_f("EVP_PKEY_new_raw_private_key failed");
109
0
    goto out;
110
0
  }
111
112
  /* Extract public key */
113
0
  pklen = crypto_sign_ed25519_PUBLICKEYBYTES;
114
0
  if (!EVP_PKEY_get_raw_public_key(pkey, pk, &pklen)) {
115
0
    debug3_f("EVP_PKEY_get_raw_public_key failed");
116
0
    goto out;
117
0
  }
118
119
  /* Build sk = seed || pk */
120
0
  memcpy(sk, seed, SSH_ED25519_RAW_SECRET_KEY_LEN);
121
0
  memcpy(sk + SSH_ED25519_RAW_SECRET_KEY_LEN, pk,
122
0
      crypto_sign_ed25519_PUBLICKEYBYTES);
123
124
0
  ret = 0;
125
0
out:
126
0
  EVP_PKEY_free(pkey);
127
0
  return ret;
128
0
}
129
130
int
131
crypto_sign_ed25519(unsigned char *sm, unsigned long long *smlen,
132
    const unsigned char *m, unsigned long long mlen,
133
    const unsigned char *sk)
134
0
{
135
0
  EVP_PKEY *pkey = NULL;
136
0
  EVP_MD_CTX *mdctx = NULL;
137
0
  size_t siglen;
138
0
  int ret = -1;
139
140
  /* Create EVP_PKEY from secret key (first 32 bytes are the seed) */
141
0
  if ((pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
142
0
      sk, SSH_ED25519_RAW_SECRET_KEY_LEN)) == NULL) {
143
0
    debug3_f("EVP_PKEY_new_raw_private_key failed");
144
0
    goto out;
145
0
  }
146
147
  /* Sign the message */
148
0
  if ((mdctx = EVP_MD_CTX_new()) == NULL) {
149
0
    debug3_f("EVP_MD_CTX_new failed");
150
0
    goto out;
151
0
  }
152
0
  if (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) != 1) {
153
0
    debug3_f("EVP_DigestSignInit failed");
154
0
    goto out;
155
0
  }
156
0
  siglen = crypto_sign_ed25519_BYTES;
157
0
  if (EVP_DigestSign(mdctx, sm, &siglen, m, mlen) != 1) {
158
0
    debug3_f("EVP_DigestSign failed");
159
0
    goto out;
160
0
  }
161
0
  if (siglen != crypto_sign_ed25519_BYTES) {
162
0
    debug3_f("signature length mismatch: %zu", siglen);
163
0
    goto out;
164
0
  }
165
166
  /* Append message after signature (SUPERCOP format) */
167
0
  if (mlen > ULLONG_MAX - siglen) {
168
0
    debug3_f("message length overflow: siglen=%zu mlen=%llu",
169
0
        siglen, mlen);
170
0
    goto out;
171
0
  }
172
0
  memmove(sm + siglen, m, mlen);
173
0
  *smlen = siglen + mlen;
174
175
0
  ret = 0;
176
0
out:
177
0
  EVP_MD_CTX_free(mdctx);
178
0
  EVP_PKEY_free(pkey);
179
0
  return ret;
180
0
}
181
182
int
183
crypto_sign_ed25519_open(unsigned char *m, unsigned long long *mlen,
184
    const unsigned char *sm, unsigned long long smlen,
185
    const unsigned char *pk)
186
17
{
187
17
  EVP_PKEY *pkey = NULL;
188
17
  EVP_MD_CTX *mdctx = NULL;
189
17
  int ret = -1;
190
17
  const unsigned char *msg;
191
17
  size_t msglen;
192
193
17
  if (smlen < crypto_sign_ed25519_BYTES) {
194
0
    debug3_f("signed message bad length: %llu", smlen);
195
0
    return -1;
196
0
  }
197
17
  if (smlen - crypto_sign_ed25519_BYTES > SIZE_MAX) {
198
0
    debug3_f("signed message too long: %llu", smlen);
199
0
    return -1;
200
0
  }
201
  /* Signature is first crypto_sign_ed25519_BYTES, message follows */
202
17
  msg = sm + crypto_sign_ed25519_BYTES;
203
17
  msglen = smlen - crypto_sign_ed25519_BYTES;
204
205
  /* Make sure the message buffer is big enough. */
206
17
  if (*mlen < msglen) {
207
0
    debug_f("message bad length: %llu", *mlen);
208
0
    return -1;
209
0
  }
210
211
  /* Create EVP_PKEY from public key */
212
17
  if ((pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
213
17
      pk, crypto_sign_ed25519_PUBLICKEYBYTES)) == NULL) {
214
0
    debug3_f("EVP_PKEY_new_raw_public_key failed");
215
0
    goto out;
216
0
  }
217
218
17
  if ((mdctx = EVP_MD_CTX_new()) == NULL) {
219
0
    debug3_f("EVP_MD_CTX_new failed");
220
0
    goto out;
221
0
  }
222
17
  if (EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) <= 0) {
223
0
    debug3_f("EVP_DigestVerifyInit failed");
224
0
    goto out;
225
0
  }
226
17
  if (EVP_DigestVerify(mdctx, sm, crypto_sign_ed25519_BYTES,
227
17
      msg, msglen) != 1) {
228
17
    debug3_f("EVP_DigestVerify failed");
229
17
    goto out;
230
17
  }
231
232
  /* Copy message out */
233
0
  *mlen = msglen;
234
0
  memmove(m, msg, msglen);
235
236
0
  ret = 0;
237
17
out:
238
17
  EVP_MD_CTX_free(mdctx);
239
17
  EVP_PKEY_free(pkey);
240
17
  return ret;
241
0
}
242
243
#endif /* OPENSSL_HAS_ED25519 */