Coverage Report

Created: 2026-08-13 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssh/kexmlkem768ecdh.c
Line
Count
Source
1
/* $OpenBSD: kexmlkem768ecdh.c,v 1.4 2026/08/03 06:43:16 djm Exp $ */
2
/*
3
 * Copyright (c) 2025 Markus Friedl.  All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions
7
 * are met:
8
 * 1. Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 * 2. Redistributions in binary form must reproduce the above copyright
11
 *    notice, this list of conditions and the following disclaimer in the
12
 *    documentation and/or other materials provided with the distribution.
13
 *
14
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
 */
25
26
#include "includes.h"
27
28
#include <sys/types.h>
29
30
#include <stdio.h>
31
#include <stdint.h>
32
#include <stdbool.h>
33
#include <string.h>
34
#include <signal.h>
35
#include <endian.h>
36
37
#include "sshkey.h"
38
#include "kex.h"
39
#include "sshbuf.h"
40
#include "digest.h"
41
#include "ssherr.h"
42
43
#ifdef WITH_OPENSSL
44
int
45
kex_kem_mlkem768ecdh_keypair(struct kex *kex)
46
0
{
47
0
  struct sshbuf *buf = NULL;
48
0
  struct sshbuf *ec_blob = NULL;
49
0
  EC_KEY *client_key = NULL;
50
0
  const EC_GROUP *group;
51
0
  const EC_POINT *public_key;
52
0
  u_char *cp = NULL;
53
0
  size_t need;
54
0
  int r = SSH_ERR_INTERNAL_ERROR;
55
56
0
  if ((buf = sshbuf_new()) == NULL)
57
0
    return SSH_ERR_ALLOC_FAIL;
58
0
  need = MLKEM768_PUBLICKEYBYTES;
59
0
  if ((r = sshbuf_reserve(buf, need, &cp)) != 0)
60
0
    goto out;
61
0
  if (crypto_kem_mlkem768_keypair(cp, kex->mlkem768_client_key) != 0) {
62
0
    r = SSH_ERR_INTERNAL_ERROR;
63
0
    goto out;
64
0
  }
65
#ifdef DEBUG_KEXECDH
66
  dump_digest("client public key mlkem768:", cp,
67
      MLKEM768_PUBLICKEYBYTES);
68
#endif
69
0
  if ((client_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) {
70
0
    r = SSH_ERR_ALLOC_FAIL;
71
0
    goto out;
72
0
  }
73
0
  if (EC_KEY_generate_key(client_key) != 1) {
74
0
    r = SSH_ERR_LIBCRYPTO_ERROR;
75
0
    goto out;
76
0
  }
77
0
  group = EC_KEY_get0_group(client_key);
78
0
  public_key = EC_KEY_get0_public_key(client_key);
79
80
0
  if ((ec_blob = sshbuf_new()) == NULL) {
81
0
    r = SSH_ERR_ALLOC_FAIL;
82
0
    goto out;
83
0
  }
84
0
  if ((r = sshbuf_put_ec(ec_blob, public_key, group)) != 0 ||
85
0
      (r = sshbuf_get_u32(ec_blob, NULL)) != 0 ||
86
0
      (r = sshbuf_putb(buf, ec_blob)) != 0)
87
0
    goto out;
88
#ifdef DEBUG_KEXECDH
89
  fputs("client private key EC:\n", stderr);
90
  sshkey_dump_ec_key(client_key);
91
#endif
92
  /* success */
93
0
  r = 0;
94
0
  kex->ec_client_key = client_key;
95
0
  kex->ec_group = group;
96
0
  client_key = NULL; /* owned by the kex */
97
0
  kex->client_pub = buf;
98
0
  buf = NULL;
99
0
 out:
100
0
  sshbuf_free(buf);
101
0
  sshbuf_free(ec_blob);
102
0
  EC_KEY_free(client_key);
103
0
  return r;
104
0
}
105
106
int
107
kex_kem_mlkem768ecdh_enc(struct kex *kex,
108
   const struct sshbuf *client_blob, struct sshbuf **server_blobp,
109
   struct sshbuf **shared_secretp)
110
0
{
111
0
  const EC_GROUP *group;
112
0
  const EC_POINT *pub_key;
113
0
  EC_KEY *server_key = NULL;
114
0
  struct sshbuf *ec_pub = NULL;
115
0
  struct sshbuf *ec_blob = NULL;
116
0
  struct sshbuf *ec_shared = NULL;
117
0
  struct sshbuf *server_blob = NULL;
118
0
  struct sshbuf *buf = NULL;
119
0
  const u_char *client_pub;
120
0
  u_char hash[SSH_DIGEST_MAX_LENGTH];
121
0
  u_char ct[MLKEM768_CIPHERTEXTBYTES];
122
0
  u_char shared_secret[MLKEM768_BYTES];
123
0
  size_t need;
124
0
  int r = SSH_ERR_INTERNAL_ERROR;
125
126
0
  *server_blobp = NULL;
127
0
  *shared_secretp = NULL;
128
129
  /* client_blob contains both KEM and ECDH client pubkeys */
130
0
  need = MLKEM768_PUBLICKEYBYTES;
131
0
  if (sshbuf_len(client_blob) <= need) {
132
0
    r = SSH_ERR_SIGNATURE_INVALID;
133
0
    goto out;
134
0
  }
135
0
  client_pub = sshbuf_ptr(client_blob);
136
#ifdef DEBUG_KEXECDH
137
  dump_digest("client public key mlkem768:", client_pub,
138
      MLKEM768_PUBLICKEYBYTES);
139
#endif
140
141
  /* allocate buffer for concatenation of KEM key and ECDH shared key */
142
  /* the buffer will be hashed and the result is the shared secret */
143
0
  if ((buf = sshbuf_new()) == NULL) {
144
0
    r = SSH_ERR_ALLOC_FAIL;
145
0
    goto out;
146
0
  }
147
  /* allocate space for encrypted KEM key and ECDH pub key */
148
0
  if ((server_blob = sshbuf_new()) == NULL) {
149
0
    r = SSH_ERR_ALLOC_FAIL;
150
0
    goto out;
151
0
  }
152
  /* generate and encrypt KEM key with client key */
153
0
  if (crypto_kem_mlkem768_enc(ct, shared_secret, client_pub) != 0) {
154
0
    r = SSH_ERR_INTERNAL_ERROR;
155
0
    goto out;
156
0
  }
157
0
  if ((r = sshbuf_put(buf, shared_secret, sizeof(shared_secret))) != 0 ||
158
0
      (r = sshbuf_put(server_blob, ct, sizeof(ct))) != 0)
159
0
    goto out;
160
161
0
  client_pub += MLKEM768_PUBLICKEYBYTES;
162
0
  if ((ec_pub = sshbuf_from(client_pub, sshbuf_len(client_blob) -
163
0
      MLKEM768_PUBLICKEYBYTES)) == NULL) {
164
0
    r = SSH_ERR_ALLOC_FAIL;
165
0
    goto out;
166
0
  }
167
  /* generate ECDH key pair */
168
0
  if ((server_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) {
169
0
    r = SSH_ERR_ALLOC_FAIL;
170
0
    goto out;
171
0
  }
172
0
  if (EC_KEY_generate_key(server_key) != 1) {
173
0
    r = SSH_ERR_LIBCRYPTO_ERROR;
174
0
    goto out;
175
0
  }
176
0
  group = EC_KEY_get0_group(server_key);
177
#ifdef DEBUG_KEXECDH
178
  fputs("server private key EC:\n", stderr);
179
  sshkey_dump_ec_key(server_key);
180
#endif
181
  /* store server pubkey after ciphertext */
182
0
  pub_key = EC_KEY_get0_public_key(server_key);
183
0
  if ((ec_blob = sshbuf_new()) == NULL) {
184
0
    r = SSH_ERR_ALLOC_FAIL;
185
0
    goto out;
186
0
  }
187
0
  if ((r = sshbuf_put_ec(ec_blob, pub_key, group)) != 0 ||
188
0
      (r = sshbuf_get_u32(ec_blob, NULL)) != 0 ||
189
0
      (r = sshbuf_putb(server_blob, ec_blob)) != 0)
190
0
    goto out;
191
192
  /* append ECDH shared key */
193
0
  if ((r = kex_ecdh_dec_key_group(kex, ec_pub, server_key, group,
194
0
      1, &ec_shared)) != 0 ||
195
0
      (r = sshbuf_putb(buf, ec_shared)) != 0)
196
0
    goto out;
197
198
0
  if ((r = ssh_digest_buffer(kex->hash_alg, buf, hash, sizeof(hash))) != 0)
199
0
    goto out;
200
#ifdef DEBUG_KEXECDH
201
  dump_digest("server cipher text:", ct, sizeof(ct));
202
  dump_digest("server kem key:", shared_secret, sizeof(shared_secret));
203
  dump_digest("concatenation of KEM key and ECDH shared key:",
204
      sshbuf_ptr(buf), sshbuf_len(buf));
205
#endif
206
  /* string-encoded hash is resulting shared secret */
207
0
  sshbuf_reset(buf);
208
0
  if ((r = sshbuf_put_string(buf, hash,
209
0
      ssh_digest_bytes(kex->hash_alg))) != 0)
210
0
    goto out;
211
#ifdef DEBUG_KEXECDH
212
  dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
213
#endif
214
  /* success */
215
0
  r = 0;
216
0
  *server_blobp = server_blob;
217
0
  *shared_secretp = buf;
218
0
  server_blob = NULL;
219
0
  buf = NULL;
220
0
 out:
221
0
  explicit_bzero(hash, sizeof(hash));
222
0
  explicit_bzero(shared_secret, sizeof(shared_secret));
223
0
  EC_KEY_free(server_key);
224
0
  sshbuf_free(ec_pub);
225
0
  sshbuf_free(ec_blob);
226
0
  sshbuf_free(ec_shared);
227
0
  sshbuf_free(server_blob);
228
0
  sshbuf_free(buf);
229
0
  return r;
230
0
}
231
232
int
233
kex_kem_mlkem768ecdh_dec(struct kex *kex,
234
    const struct sshbuf *server_blob, struct sshbuf **shared_secretp)
235
0
{
236
0
  struct sshbuf *buf = NULL;
237
0
  struct sshbuf *ec_pub = NULL;
238
0
  struct sshbuf *ec_shared = NULL;
239
0
  u_char shared_secret[MLKEM768_BYTES];
240
0
  const u_char *ciphertext, *server_pub;
241
0
  u_char hash[SSH_DIGEST_MAX_LENGTH];
242
0
  size_t need;
243
0
  int r;
244
245
0
  *shared_secretp = NULL;
246
247
0
  need = MLKEM768_CIPHERTEXTBYTES;
248
0
  if (sshbuf_len(server_blob) <= need) {
249
0
    r = SSH_ERR_SIGNATURE_INVALID;
250
0
    goto out;
251
0
  }
252
0
  ciphertext = sshbuf_ptr(server_blob);
253
0
  server_pub = ciphertext + MLKEM768_CIPHERTEXTBYTES;
254
#ifdef DEBUG_KEXECDH
255
  dump_digest("server cipher text (dec):", ciphertext,
256
      MLKEM768_CIPHERTEXTBYTES);
257
#endif
258
  /* hash concatenation of KEM key and ECDH shared key */
259
0
  if ((buf = sshbuf_new()) == NULL) {
260
0
    r = SSH_ERR_ALLOC_FAIL;
261
0
    goto out;
262
0
  }
263
0
  if (crypto_kem_mlkem768_dec(shared_secret, ciphertext,
264
0
      kex->mlkem768_client_key) != 0) {
265
0
    r = SSH_ERR_INTERNAL_ERROR;
266
0
    goto out;
267
0
  }
268
0
  if ((r = sshbuf_put(buf, shared_secret, sizeof(shared_secret))) != 0)
269
0
    goto out;
270
0
  if ((ec_pub = sshbuf_from(server_pub, sshbuf_len(server_blob) - need))
271
0
      == NULL) {
272
0
    r = SSH_ERR_ALLOC_FAIL;
273
0
    goto out;
274
0
  }
275
0
  if ((r = kex_ecdh_dec_key_group(kex, ec_pub, kex->ec_client_key,
276
0
      kex->ec_group, 1, &ec_shared)) != 0 ||
277
0
      (r = sshbuf_putb(buf, ec_shared)) != 0)
278
0
    goto out;
279
0
  if ((r = ssh_digest_buffer(kex->hash_alg, buf,
280
0
      hash, sizeof(hash))) != 0)
281
0
    goto out;
282
#ifdef DEBUG_KEXECDH
283
  dump_digest("client kem key:", shared_secret, sizeof(shared_secret));
284
  dump_digest("concatenation of KEM key and ECDH shared key:",
285
      sshbuf_ptr(buf), sshbuf_len(buf));
286
#endif
287
0
  sshbuf_reset(buf);
288
0
  if ((r = sshbuf_put_string(buf, hash,
289
0
      ssh_digest_bytes(kex->hash_alg))) != 0)
290
0
    goto out;
291
#ifdef DEBUG_KEXECDH
292
  dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
293
#endif
294
  /* success */
295
0
  r = 0;
296
0
  *shared_secretp = buf;
297
0
  buf = NULL;
298
0
 out:
299
0
  explicit_bzero(hash, sizeof(hash));
300
0
  explicit_bzero(shared_secret, sizeof(shared_secret));
301
0
  EC_KEY_free(kex->ec_client_key);
302
  kex->ec_client_key = NULL;
303
0
  sshbuf_free(ec_pub);
304
0
  sshbuf_free(ec_shared);
305
0
  sshbuf_free(buf);
306
0
  return r;
307
0
}
308
#endif  /* WITH_OPENSSL */