Coverage Report

Created: 2026-10-17 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssh/kexmlkem768x25519.c
Line
Count
Source
1
/* $OpenBSD: kexmlkem768x25519.c,v 1.3 2026/06/14 03:59:34 djm Exp $ */
2
/*
3
 * Copyright (c) 2023 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
#include "log.h"
43
44
#include "crypto_api.h"
45
46
#ifdef USE_MLKEM768X25519
47
48
int
49
kex_kem_mlkem768x25519_keypair(struct kex *kex)
50
0
{
51
0
  struct sshbuf *buf = NULL;
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 + CURVE25519_SIZE;
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
  cp += MLKEM768_PUBLICKEYBYTES;
70
0
  kexc25519_keygen(kex->c25519_client_key, cp);
71
#ifdef DEBUG_KEXECDH
72
  dump_digest("client public key c25519:", cp, CURVE25519_SIZE);
73
#endif
74
  /* success */
75
0
  r = 0;
76
0
  kex->client_pub = buf;
77
0
  buf = NULL;
78
0
 out:
79
0
  sshbuf_free(buf);
80
0
  return r;
81
0
}
82
83
int
84
kex_kem_mlkem768x25519_enc(struct kex *kex,
85
   const struct sshbuf *client_blob, struct sshbuf **server_blobp,
86
   struct sshbuf **shared_secretp)
87
0
{
88
0
  struct sshbuf *server_blob = NULL;
89
0
  struct sshbuf *buf = NULL;
90
0
  const u_char *client_pub;
91
0
  u_char server_pub[CURVE25519_SIZE], server_key[CURVE25519_SIZE];
92
0
  u_char hash[SSH_DIGEST_MAX_LENGTH];
93
0
  u_char ct[MLKEM768_CIPHERTEXTBYTES];
94
0
  u_char shared_secret[MLKEM768_BYTES];
95
0
  size_t need;
96
0
  int r = SSH_ERR_INTERNAL_ERROR;
97
98
0
  *server_blobp = NULL;
99
0
  *shared_secretp = NULL;
100
101
  /* client_blob contains both KEM and ECDH client pubkeys */
102
0
  need = MLKEM768_PUBLICKEYBYTES + CURVE25519_SIZE;
103
0
  if (sshbuf_len(client_blob) != need) {
104
0
    r = SSH_ERR_SIGNATURE_INVALID;
105
0
    goto out;
106
0
  }
107
0
  client_pub = sshbuf_ptr(client_blob);
108
#ifdef DEBUG_KEXECDH
109
  dump_digest("client public key mlkem768:", client_pub,
110
      MLKEM768_PUBLICKEYBYTES);
111
  dump_digest("client public key 25519:",
112
      client_pub + MLKEM768_PUBLICKEYBYTES,
113
      CURVE25519_SIZE);
114
#endif
115
116
  /* allocate buffer for concatenation of KEM key and ECDH shared key */
117
  /* the buffer will be hashed and the result is the shared secret */
118
0
  if ((buf = sshbuf_new()) == NULL) {
119
0
    r = SSH_ERR_ALLOC_FAIL;
120
0
    goto out;
121
0
  }
122
  /* allocate space for encrypted KEM key and ECDH pub key */
123
0
  if ((server_blob = sshbuf_new()) == NULL) {
124
0
    r = SSH_ERR_ALLOC_FAIL;
125
0
    goto out;
126
0
  }
127
  /* generate and encrypt KEM key with client key */
128
0
  if (crypto_kem_mlkem768_enc(ct, shared_secret, client_pub) != 0) {
129
0
    r = SSH_ERR_INTERNAL_ERROR;
130
0
    goto out;
131
0
  }
132
  /* generate ECDH key pair, store server pubkey after ciphertext */
133
0
  kexc25519_keygen(server_key, server_pub);
134
0
  if ((r = sshbuf_put(buf, shared_secret, sizeof(shared_secret))) != 0 ||
135
0
      (r = sshbuf_put(server_blob, ct, sizeof(ct))) != 0 ||
136
0
      (r = sshbuf_put(server_blob, server_pub, sizeof(server_pub))) != 0)
137
0
    goto out;
138
  /* append ECDH shared key */
139
0
  client_pub += MLKEM768_PUBLICKEYBYTES;
140
0
  if ((r = kexc25519_shared_key_ext(server_key, client_pub, buf, 1)) < 0)
141
0
    goto out;
142
0
  if ((r = ssh_digest_buffer(kex->hash_alg, buf,
143
0
      hash, sizeof(hash))) != 0)
144
0
    goto out;
145
#ifdef DEBUG_KEXECDH
146
  dump_digest("server public key 25519:", server_pub, CURVE25519_SIZE);
147
  dump_digest("server cipher text:", ct, sizeof(ct));
148
  dump_digest("server kem key:", shared_secret, sizeof(shared_secret));
149
  dump_digest("concatenation of KEM key and ECDH shared key:",
150
      sshbuf_ptr(buf), sshbuf_len(buf));
151
#endif
152
  /* string-encoded hash is resulting shared secret */
153
0
  sshbuf_reset(buf);
154
0
  if ((r = sshbuf_put_string(buf, hash,
155
0
      ssh_digest_bytes(kex->hash_alg))) != 0)
156
0
    goto out;
157
#ifdef DEBUG_KEXECDH
158
  dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
159
#endif
160
  /* success */
161
0
  r = 0;
162
0
  *server_blobp = server_blob;
163
0
  *shared_secretp = buf;
164
0
  server_blob = NULL;
165
0
  buf = NULL;
166
0
 out:
167
0
  explicit_bzero(hash, sizeof(hash));
168
0
  explicit_bzero(server_key, sizeof(server_key));
169
0
  explicit_bzero(shared_secret, sizeof(shared_secret));
170
0
  sshbuf_free(server_blob);
171
0
  sshbuf_free(buf);
172
0
  return r;
173
0
}
174
175
int
176
kex_kem_mlkem768x25519_dec(struct kex *kex,
177
    const struct sshbuf *server_blob, struct sshbuf **shared_secretp)
178
0
{
179
0
  struct sshbuf *buf = NULL;
180
0
  u_char shared_secret[MLKEM768_BYTES];
181
0
  const u_char *ciphertext, *server_pub;
182
0
  u_char hash[SSH_DIGEST_MAX_LENGTH];
183
0
  size_t need;
184
0
  int r;
185
186
0
  *shared_secretp = NULL;
187
188
0
  need = MLKEM768_CIPHERTEXTBYTES + CURVE25519_SIZE;
189
0
  if (sshbuf_len(server_blob) != need) {
190
0
    r = SSH_ERR_SIGNATURE_INVALID;
191
0
    goto out;
192
0
  }
193
0
  ciphertext = sshbuf_ptr(server_blob);
194
0
  server_pub = ciphertext + MLKEM768_CIPHERTEXTBYTES;
195
  /* hash concatenation of KEM key and ECDH shared key */
196
0
  if ((buf = sshbuf_new()) == NULL) {
197
0
    r = SSH_ERR_ALLOC_FAIL;
198
0
    goto out;
199
0
  }
200
#ifdef DEBUG_KEXECDH
201
  dump_digest("server cipher text:", ciphertext,
202
      MLKEM768_CIPHERTEXTBYTES);
203
  dump_digest("server public key c25519:", server_pub, CURVE25519_SIZE);
204
#endif
205
0
  if (crypto_kem_mlkem768_dec(shared_secret, ciphertext,
206
0
      kex->mlkem768_client_key) != 0) {
207
0
    r = SSH_ERR_INTERNAL_ERROR;
208
0
    goto out;
209
0
  }
210
0
  if ((r = sshbuf_put(buf, shared_secret, sizeof(shared_secret))) != 0)
211
0
    goto out;
212
0
  if ((r = kexc25519_shared_key_ext(kex->c25519_client_key, server_pub,
213
0
      buf, 1)) < 0)
214
0
    goto out;
215
0
  if ((r = ssh_digest_buffer(kex->hash_alg, buf,
216
0
      hash, sizeof(hash))) != 0)
217
0
    goto out;
218
#ifdef DEBUG_KEXECDH
219
  dump_digest("client kem key:", shared_secret, sizeof(shared_secret));
220
  dump_digest("concatenation of KEM key and ECDH shared key:",
221
      sshbuf_ptr(buf), sshbuf_len(buf));
222
#endif
223
0
  sshbuf_reset(buf);
224
0
  if ((r = sshbuf_put_string(buf, hash,
225
0
      ssh_digest_bytes(kex->hash_alg))) != 0)
226
0
    goto out;
227
#ifdef DEBUG_KEXECDH
228
  dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
229
#endif
230
  /* success */
231
0
  r = 0;
232
0
  *shared_secretp = buf;
233
0
  buf = NULL;
234
0
 out:
235
0
  explicit_bzero(hash, sizeof(hash));
236
0
  explicit_bzero(shared_secret, sizeof(shared_secret));
237
0
  sshbuf_free(buf);
238
0
  return r;
239
0
}
240
#else /* USE_MLKEM768X25519 */
241
int
242
kex_kem_mlkem768x25519_keypair(struct kex *kex)
243
{
244
  return SSH_ERR_SIGN_ALG_UNSUPPORTED;
245
}
246
247
int
248
kex_kem_mlkem768x25519_enc(struct kex *kex,
249
   const struct sshbuf *client_blob, struct sshbuf **server_blobp,
250
   struct sshbuf **shared_secretp)
251
{
252
  return SSH_ERR_SIGN_ALG_UNSUPPORTED;
253
}
254
255
int
256
kex_kem_mlkem768x25519_dec(struct kex *kex,
257
    const struct sshbuf *server_blob, struct sshbuf **shared_secretp)
258
{
259
  return SSH_ERR_SIGN_ALG_UNSUPPORTED;
260
}
261
#endif /* USE_MLKEM768X25519 */