Coverage Report

Created: 2026-06-28 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/boringssl/crypto/xwing/xwing.cc
Line
Count
Source
1
// Copyright 2025 The BoringSSL Authors
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     https://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include <openssl/xwing.h>
16
17
#include <openssl/bytestring.h>
18
#include <openssl/curve25519.h>
19
#include <openssl/mlkem.h>
20
#include <openssl/rand.h>
21
22
#include "../fipsmodule/bcm_interface.h"
23
#include "../fipsmodule/keccak/internal.h"
24
25
26
using namespace bssl;
27
28
struct private_key {
29
  MLKEM768_private_key mlkem_private_key;
30
  uint8_t x25519_private_key[32];
31
  uint8_t seed[XWING_PRIVATE_KEY_BYTES];
32
};
33
34
static_assert(sizeof(XWING_private_key) == sizeof(private_key));
35
static_assert(alignof(XWING_private_key) == alignof(private_key));
36
37
static const private_key *private_key_from_external(
38
0
    const XWING_private_key *external) {
39
0
  return reinterpret_cast<const private_key *>(external);
40
0
}
41
0
static private_key *private_key_from_external(XWING_private_key *external) {
42
0
  return reinterpret_cast<private_key *>(external);
43
0
}
44
45
0
static void xwing_expand_private_key(private_key *inout_private_key) {
46
0
  BORINGSSL_keccak_st context;
47
0
  BORINGSSL_keccak_init(&context, boringssl_shake256);
48
0
  BORINGSSL_keccak_absorb(&context, inout_private_key->seed,
49
0
                          sizeof(inout_private_key->seed));
50
51
  // ML-KEM-768
52
0
  uint8_t mlkem_seed[64];
53
0
  BORINGSSL_keccak_squeeze(&context, mlkem_seed, sizeof(mlkem_seed));
54
0
  MLKEM768_private_key_from_seed(&inout_private_key->mlkem_private_key,
55
0
                                 mlkem_seed, sizeof(mlkem_seed));
56
57
  // X25519
58
0
  BORINGSSL_keccak_squeeze(&context, inout_private_key->x25519_private_key,
59
0
                           sizeof(inout_private_key->x25519_private_key));
60
0
}
61
62
0
static int xwing_parse_private_key(private_key *out_private_key, CBS *in) {
63
0
  if (!CBS_copy_bytes(in, out_private_key->seed,
64
0
                      sizeof(out_private_key->seed))) {
65
0
    return 0;
66
0
  }
67
68
0
  xwing_expand_private_key(out_private_key);
69
0
  return 1;
70
0
}
71
72
0
static int xwing_marshal_private_key(CBB *out, const private_key *private_key) {
73
0
  return CBB_add_bytes(out, private_key->seed, sizeof(private_key->seed));
74
0
}
75
76
static int xwing_public_from_private(
77
    uint8_t out_encoded_public_key[XWING_PUBLIC_KEY_BYTES],
78
0
    const private_key *private_key) {
79
0
  CBB cbb;
80
0
  if (!CBB_init_fixed(&cbb, out_encoded_public_key, XWING_PUBLIC_KEY_BYTES)) {
81
0
    return 0;
82
0
  }
83
84
  // ML-KEM-768
85
0
  MLKEM768_public_key mlkem_public_key;
86
0
  MLKEM768_public_from_private(&mlkem_public_key,
87
0
                               &private_key->mlkem_private_key);
88
89
0
  if (!MLKEM768_marshal_public_key(&cbb, &mlkem_public_key)) {
90
0
    return 0;
91
0
  }
92
93
  // X25519
94
0
  uint8_t *buf;
95
0
  if (!CBB_add_space(&cbb, &buf, 32)) {
96
0
    return 0;
97
0
  }
98
0
  X25519_public_from_private(buf, private_key->x25519_private_key);
99
100
0
  if (CBB_len(&cbb) != XWING_PUBLIC_KEY_BYTES) {
101
0
    return 0;
102
0
  }
103
0
  return 1;
104
0
}
105
106
static void xwing_combiner(
107
    uint8_t out_shared_secret[XWING_SHARED_SECRET_BYTES],
108
    const uint8_t mlkem_shared_secret[MLKEM_SHARED_SECRET_BYTES],
109
    const uint8_t x25519_shared_secret[32], const uint8_t x25519_ciphertext[32],
110
0
    const uint8_t x25519_public_key[32]) {
111
0
  BORINGSSL_keccak_st context;
112
0
  BORINGSSL_keccak_init(&context, boringssl_sha3_256);
113
114
0
  BORINGSSL_keccak_absorb(&context, mlkem_shared_secret,
115
0
                          MLKEM_SHARED_SECRET_BYTES);
116
0
  BORINGSSL_keccak_absorb(&context, x25519_shared_secret, 32);
117
0
  BORINGSSL_keccak_absorb(&context, x25519_ciphertext, 32);
118
0
  BORINGSSL_keccak_absorb(&context, x25519_public_key, 32);
119
120
0
  uint8_t xwing_label[6] = {0x5c, 0x2e, 0x2f, 0x2f, 0x5e, 0x5c};
121
0
  BORINGSSL_keccak_absorb(&context, xwing_label, sizeof(xwing_label));
122
123
0
  BORINGSSL_keccak_squeeze(&context, out_shared_secret,
124
0
                           XWING_SHARED_SECRET_BYTES);
125
0
}
126
127
// Public API.
128
129
int XWING_parse_private_key(struct XWING_private_key *out_private_key,
130
0
                            CBS *in) {
131
0
  if (!xwing_parse_private_key(private_key_from_external(out_private_key),
132
0
                               in) ||
133
0
      CBS_len(in) != 0) {
134
0
    return 0;
135
0
  }
136
0
  return 1;
137
0
}
138
139
int XWING_marshal_private_key(CBB *out,
140
0
                              const struct XWING_private_key *private_key) {
141
0
  return xwing_marshal_private_key(out, private_key_from_external(private_key));
142
0
}
143
144
int XWING_generate_key(uint8_t out_encoded_public_key[XWING_PUBLIC_KEY_BYTES],
145
0
                       struct XWING_private_key *out_private_key) {
146
0
  private_key *private_key = private_key_from_external(out_private_key);
147
0
  RAND_bytes(private_key->seed, sizeof(private_key->seed));
148
149
0
  xwing_expand_private_key(private_key);
150
151
0
  return XWING_public_from_private(out_encoded_public_key, out_private_key);
152
0
}
153
154
int XWING_public_from_private(
155
    uint8_t out_encoded_public_key[XWING_PUBLIC_KEY_BYTES],
156
0
    const struct XWING_private_key *private_key) {
157
0
  return xwing_public_from_private(out_encoded_public_key,
158
0
                                   private_key_from_external(private_key));
159
0
}
160
161
int XWING_encap(uint8_t out_ciphertext[XWING_CIPHERTEXT_BYTES],
162
                uint8_t out_shared_secret[XWING_SHARED_SECRET_BYTES],
163
0
                const uint8_t encoded_public_key[XWING_PUBLIC_KEY_BYTES]) {
164
0
  uint8_t eseed[64];
165
0
  RAND_bytes(eseed, sizeof(eseed));
166
167
0
  return XWING_encap_external_entropy(out_ciphertext, out_shared_secret,
168
0
                                      encoded_public_key, eseed);
169
0
}
170
171
int XWING_encap_external_entropy(
172
    uint8_t out_ciphertext[XWING_CIPHERTEXT_BYTES],
173
    uint8_t out_shared_secret[XWING_SHARED_SECRET_BYTES],
174
    const uint8_t encoded_public_key[XWING_PUBLIC_KEY_BYTES],
175
0
    const uint8_t eseed[64]) {
176
  // X25519
177
0
  static_assert(XWING_PUBLIC_KEY_BYTES >= MLKEM768_PUBLIC_KEY_BYTES + 32);
178
0
  const uint8_t *x25519_public_key =
179
0
      encoded_public_key + MLKEM768_PUBLIC_KEY_BYTES;
180
0
  const uint8_t *x25519_ephemeral_private_key = eseed + 32;
181
0
  uint8_t *x25519_ciphertext = out_ciphertext + MLKEM768_CIPHERTEXT_BYTES;
182
0
  X25519_public_from_private(x25519_ciphertext, x25519_ephemeral_private_key);
183
184
0
  uint8_t x25519_shared_secret[32];
185
0
  if (!X25519(x25519_shared_secret, x25519_ephemeral_private_key,
186
0
              x25519_public_key)) {
187
0
    return 0;
188
0
  }
189
190
  // ML-KEM-768
191
0
  CBS mlkem_cbs;
192
0
  static_assert(MLKEM768_PUBLIC_KEY_BYTES <= XWING_PUBLIC_KEY_BYTES);
193
0
  CBS_init(&mlkem_cbs, encoded_public_key, MLKEM768_PUBLIC_KEY_BYTES);
194
195
0
  MLKEM768_public_key mlkem_public_key;
196
0
  if (!MLKEM768_parse_public_key(&mlkem_public_key, &mlkem_cbs)) {
197
0
    return 0;
198
0
  }
199
200
0
  uint8_t *mlkem_ciphertext = out_ciphertext;
201
0
  uint8_t mlkem_shared_secret[MLKEM_SHARED_SECRET_BYTES];
202
0
  BCM_mlkem768_encap_external_entropy(mlkem_ciphertext, mlkem_shared_secret,
203
0
                                      &mlkem_public_key, eseed);
204
205
  // Combine the shared secrets
206
0
  xwing_combiner(out_shared_secret, mlkem_shared_secret, x25519_shared_secret,
207
0
                 x25519_ciphertext, x25519_public_key);
208
0
  return 1;
209
0
}
210
211
static int xwing_decap(uint8_t out_shared_secret[XWING_SHARED_SECRET_BYTES],
212
                       const uint8_t ciphertext[XWING_CIPHERTEXT_BYTES],
213
0
                       const private_key *private_key) {
214
0
  static_assert(XWING_CIPHERTEXT_BYTES >= MLKEM768_CIPHERTEXT_BYTES + 32);
215
0
  const uint8_t *mlkem_ciphertext = ciphertext;
216
0
  const uint8_t *x25519_ciphertext = ciphertext + MLKEM768_CIPHERTEXT_BYTES;
217
218
  // ML-KEM-768
219
0
  uint8_t mlkem_shared_secret[MLKEM_SHARED_SECRET_BYTES];
220
0
  if (!MLKEM768_decap(mlkem_shared_secret, mlkem_ciphertext,
221
0
                      MLKEM768_CIPHERTEXT_BYTES,
222
0
                      &private_key->mlkem_private_key)) {
223
0
    goto err;
224
0
  }
225
226
  // X25519
227
0
  uint8_t x25519_public_key[32];
228
0
  X25519_public_from_private(x25519_public_key,
229
0
                             private_key->x25519_private_key);
230
231
0
  uint8_t x25519_shared_secret[32];
232
0
  if (!X25519(x25519_shared_secret, private_key->x25519_private_key,
233
0
              x25519_ciphertext)) {
234
0
    goto err;
235
0
  }
236
237
  // Combine the shared secrets
238
0
  xwing_combiner(out_shared_secret, mlkem_shared_secret, x25519_shared_secret,
239
0
                 x25519_ciphertext, x25519_public_key);
240
0
  return 1;
241
242
0
err:
243
  // In case of error, fill the shared secret with random bytes so that if the
244
  // caller forgets to check the return code:
245
  // - no intermediate information leaks,
246
  // - the shared secret is unpredictable, so for example any data encrypted
247
  //   with it wouldn't be trivially decryptable by an attacker.
248
0
  RAND_bytes(out_shared_secret, XWING_SHARED_SECRET_BYTES);
249
0
  return 0;
250
0
}
251
252
int XWING_decap(uint8_t out_shared_secret[XWING_SHARED_SECRET_BYTES],
253
                const uint8_t ciphertext[XWING_CIPHERTEXT_BYTES],
254
0
                const struct XWING_private_key *private_key) {
255
0
  return xwing_decap(out_shared_secret, ciphertext,
256
0
                     private_key_from_external(private_key));
257
0
}