Coverage Report

Created: 2026-02-14 06:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rnp/src/lib/crypto/dilithium_exdsa_composite.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2023, [MTG AG](https://www.mtg.de).
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without modification,
6
 * are permitted provided that the following conditions are met:
7
 *
8
 * 1.  Redistributions of source code must retain the above copyright notice,
9
 *     this list of conditions and the following disclaimer.
10
 *
11
 * 2.  Redistributions in binary form must reproduce the above copyright notice,
12
 *     this list of conditions and the following disclaimer in the documentation
13
 *     and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
19
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
 */
26
27
#include "dilithium_exdsa_composite.h"
28
#include "types.h"
29
#include "logging.h"
30
31
pgp_dilithium_exdsa_composite_key_t::~pgp_dilithium_exdsa_composite_key_t()
32
42.7k
{
33
42.7k
}
34
35
void
36
pgp_dilithium_exdsa_composite_key_t::initialized_or_throw() const
37
4.67k
{
38
4.67k
    if (!is_initialized()) {
39
0
        RNP_LOG("Trying to use uninitialized mldsa-ecdsa/eddsa key");
40
0
        throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
41
0
    }
42
4.67k
}
43
44
rnp_result_t
45
pgp_dilithium_exdsa_composite_key_t::gen_keypair(rnp::RNG *                 rng,
46
                                                 pgp_dilithium_exdsa_key_t *key,
47
                                                 pgp_pubkey_alg_t           alg)
48
0
{
49
0
    rnp_result_t          res;
50
0
    pgp_curve_t           curve = pk_alg_to_curve_id(alg);
51
0
    dilithium_parameter_e dilithium_id = pk_alg_to_dilithium_id(alg);
52
53
0
    exdsa_key_t exdsa_key_pair;
54
55
0
    res = ec_key_t::generate_exdsa_key_pair(rng, &exdsa_key_pair, curve);
56
0
    if (res != RNP_SUCCESS) {
57
0
        RNP_LOG("generating mldsa exdsa composite key failed when generating exdsa key");
58
0
        return res;
59
0
    }
60
61
0
    auto dilithium_key_pair = dilithium_generate_keypair(rng, dilithium_id);
62
63
0
    key->priv = pgp_dilithium_exdsa_composite_private_key_t(
64
0
      exdsa_key_pair.priv.get_encoded(), dilithium_key_pair.second.get_encoded(), alg);
65
0
    key->pub = pgp_dilithium_exdsa_composite_public_key_t(
66
0
      exdsa_key_pair.pub.get_encoded(), dilithium_key_pair.first.get_encoded(), alg);
67
68
0
    return RNP_SUCCESS;
69
0
}
70
71
size_t
72
pgp_dilithium_exdsa_composite_key_t::exdsa_curve_privkey_size(pgp_curve_t curve)
73
158
{
74
158
    switch (curve) {
75
17
    case PGP_CURVE_ED25519:
76
17
        return 32;
77
    /* TODO */
78
    // case PGP_CURVE_ED448:
79
    //   return 56;
80
10
    case PGP_CURVE_NIST_P_256:
81
10
        return 32;
82
42
    case PGP_CURVE_NIST_P_384:
83
42
        return 48;
84
16
    case PGP_CURVE_BP256:
85
16
        return 32;
86
73
    case PGP_CURVE_BP384:
87
73
        return 48;
88
0
    default:
89
0
        RNP_LOG("invalid curve given");
90
0
        throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
91
158
    }
92
158
}
93
94
size_t
95
pgp_dilithium_exdsa_composite_key_t::exdsa_curve_pubkey_size(pgp_curve_t curve)
96
28.9k
{
97
28.9k
    switch (curve) {
98
15.4k
    case PGP_CURVE_ED25519:
99
15.4k
        return 32;
100
    /* TODO */
101
    //  case PGP_CURVE_ED448:
102
    //    return 56;
103
5.82k
    case PGP_CURVE_NIST_P_256:
104
5.82k
        return 65;
105
1.84k
    case PGP_CURVE_NIST_P_384:
106
1.84k
        return 97;
107
3.17k
    case PGP_CURVE_BP256:
108
3.17k
        return 65;
109
2.61k
    case PGP_CURVE_BP384:
110
2.61k
        return 97;
111
0
    default:
112
0
        RNP_LOG("invalid curve given");
113
0
        throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
114
28.9k
    }
115
28.9k
}
116
117
size_t
118
pgp_dilithium_exdsa_composite_key_t::exdsa_curve_signature_size(pgp_curve_t curve)
119
5.86k
{
120
5.86k
    switch (curve) {
121
856
    case PGP_CURVE_ED25519:
122
856
        return 64;
123
    /* TODO */
124
    //  case PGP_CURVE_ED448:
125
    //    return 114;
126
1.01k
    case PGP_CURVE_NIST_P_256:
127
1.01k
        return 64;
128
1.25k
    case PGP_CURVE_NIST_P_384:
129
1.25k
        return 96;
130
1.39k
    case PGP_CURVE_BP256:
131
1.39k
        return 64;
132
1.33k
    case PGP_CURVE_BP384:
133
1.33k
        return 96;
134
0
    default:
135
0
        RNP_LOG("invalid curve given");
136
0
        throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
137
5.86k
    }
138
5.86k
}
139
140
dilithium_parameter_e
141
pgp_dilithium_exdsa_composite_key_t::pk_alg_to_dilithium_id(pgp_pubkey_alg_t pk_alg)
142
34.9k
{
143
34.9k
    switch (pk_alg) {
144
16.3k
    case PGP_PKA_DILITHIUM3_ED25519:
145
16.3k
        FALLTHROUGH_STATEMENT;
146
23.1k
    case PGP_PKA_DILITHIUM3_P256:
147
23.1k
        FALLTHROUGH_STATEMENT;
148
27.7k
    case PGP_PKA_DILITHIUM3_BP256:
149
27.7k
        return dilithium_L3;
150
4.02k
    case PGP_PKA_DILITHIUM5_BP384:
151
4.02k
        FALLTHROUGH_STATEMENT;
152
7.16k
    case PGP_PKA_DILITHIUM5_P384:
153
7.16k
        return dilithium_L5;
154
0
    default:
155
0
        RNP_LOG("invalid PK alg given");
156
0
        throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
157
34.9k
    }
158
34.9k
}
159
160
pgp_curve_t
161
pgp_dilithium_exdsa_composite_key_t::pk_alg_to_curve_id(pgp_pubkey_alg_t pk_alg)
162
44.1k
{
163
44.1k
    switch (pk_alg) {
164
21.3k
    case PGP_PKA_DILITHIUM3_ED25519:
165
21.3k
        return PGP_CURVE_ED25519;
166
8.68k
    case PGP_PKA_DILITHIUM3_P256:
167
8.68k
        return PGP_CURVE_NIST_P_256;
168
5.58k
    case PGP_PKA_DILITHIUM3_BP256:
169
5.58k
        return PGP_CURVE_BP256;
170
4.85k
    case PGP_PKA_DILITHIUM5_BP384:
171
4.85k
        return PGP_CURVE_BP384;
172
3.63k
    case PGP_PKA_DILITHIUM5_P384:
173
3.63k
        return PGP_CURVE_NIST_P_384;
174
    /*case PGP_PKA_DILITHIUM5_ED448:
175
      throw rnp::rnp_exception(RNP_ERROR_NOT_IMPLEMENTED);*/
176
0
    default:
177
0
        RNP_LOG("invalid PK alg given");
178
0
        throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
179
44.1k
    }
180
44.1k
}
181
182
pgp_dilithium_exdsa_composite_public_key_t::pgp_dilithium_exdsa_composite_public_key_t(
183
  const uint8_t *key_encoded, size_t key_encoded_len, pgp_pubkey_alg_t pk_alg)
184
0
    : pk_alg_(pk_alg)
185
0
{
186
0
    parse_component_keys(std::vector<uint8_t>(key_encoded, key_encoded + key_encoded_len));
187
0
}
188
189
pgp_dilithium_exdsa_composite_public_key_t::pgp_dilithium_exdsa_composite_public_key_t(
190
  std::vector<uint8_t> const &key_encoded, pgp_pubkey_alg_t pk_alg)
191
9.14k
    : pk_alg_(pk_alg)
192
9.14k
{
193
9.14k
    parse_component_keys(key_encoded);
194
9.14k
}
195
196
pgp_dilithium_exdsa_composite_public_key_t::pgp_dilithium_exdsa_composite_public_key_t(
197
  std::vector<uint8_t> const &exdsa_key_encoded,
198
  std::vector<uint8_t> const &dilithium_key_encoded,
199
  pgp_pubkey_alg_t            pk_alg)
200
0
    : pk_alg_(pk_alg), dilithium_key_(dilithium_key_encoded, pk_alg_to_dilithium_id(pk_alg)),
201
0
      exdsa_key_(exdsa_key_encoded, pk_alg_to_curve_id(pk_alg))
202
0
{
203
0
    if (exdsa_curve_pubkey_size(pk_alg_to_curve_id(pk_alg)) != exdsa_key_encoded.size() ||
204
0
        dilithium_pubkey_size(pk_alg_to_dilithium_id(pk_alg)) !=
205
0
          dilithium_key_encoded.size()) {
206
0
        RNP_LOG("exdsa or mldsa key length mismatch");
207
0
        throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
208
0
    }
209
0
    is_initialized_ = true;
210
0
}
211
212
pgp_dilithium_exdsa_composite_private_key_t::pgp_dilithium_exdsa_composite_private_key_t(
213
  const uint8_t *key_encoded, size_t key_encoded_len, pgp_pubkey_alg_t pk_alg)
214
46
    : pk_alg_(pk_alg)
215
46
{
216
46
    parse_component_keys(std::vector<uint8_t>(key_encoded, key_encoded + key_encoded_len));
217
46
}
218
219
pgp_dilithium_exdsa_composite_private_key_t::pgp_dilithium_exdsa_composite_private_key_t(
220
  std::vector<uint8_t> const &key_encoded, pgp_pubkey_alg_t pk_alg)
221
0
    : pk_alg_(pk_alg)
222
0
{
223
0
    parse_component_keys(key_encoded);
224
0
}
225
226
/* copy assignment operator is used on key materials struct and thus needs to be defined for
227
 * this class as well */
228
pgp_dilithium_exdsa_composite_private_key_t &
229
pgp_dilithium_exdsa_composite_private_key_t::operator=(
230
  const pgp_dilithium_exdsa_composite_private_key_t &other)
231
5.92k
{
232
5.92k
    pgp_dilithium_exdsa_composite_key_t::operator=(other);
233
5.92k
    pk_alg_ = other.pk_alg_;
234
5.92k
    if (other.is_initialized() && other.dilithium_key_) {
235
46
        dilithium_key_ =
236
46
          std::make_unique<pgp_dilithium_private_key_t>(pgp_dilithium_private_key_t(
237
46
            other.dilithium_key_->get_encoded(), other.dilithium_key_->param()));
238
46
    }
239
5.92k
    if (other.is_initialized() && other.exdsa_key_) {
240
46
        exdsa_key_ = std::make_unique<exdsa_private_key_t>(
241
46
          exdsa_private_key_t(other.exdsa_key_->get_encoded(), other.exdsa_key_->get_curve()));
242
46
    }
243
244
5.92k
    return *this;
245
5.92k
}
246
247
pgp_dilithium_exdsa_composite_private_key_t::pgp_dilithium_exdsa_composite_private_key_t(
248
  const pgp_dilithium_exdsa_composite_private_key_t &other)
249
5.87k
{
250
5.87k
    *this = other;
251
5.87k
}
252
253
pgp_dilithium_exdsa_composite_private_key_t::pgp_dilithium_exdsa_composite_private_key_t(
254
  std::vector<uint8_t> const &exdsa_key_encoded,
255
  std::vector<uint8_t> const &dilithium_key_encoded,
256
  pgp_pubkey_alg_t            pk_alg)
257
0
    : pk_alg_(pk_alg)
258
0
{
259
0
    if (exdsa_curve_privkey_size(pk_alg_to_curve_id(pk_alg)) != exdsa_key_encoded.size() ||
260
0
        dilithium_privkey_size(pk_alg_to_dilithium_id(pk_alg)) !=
261
0
          dilithium_key_encoded.size()) {
262
0
        RNP_LOG("exdsa or mldsa key length mismatch");
263
0
        throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
264
0
    }
265
266
0
    dilithium_key_ = std::make_unique<pgp_dilithium_private_key_t>(
267
0
      pgp_dilithium_private_key_t(dilithium_key_encoded, pk_alg_to_dilithium_id(pk_alg)));
268
0
    exdsa_key_ = std::make_unique<exdsa_private_key_t>(
269
0
      exdsa_private_key_t(exdsa_key_encoded, pk_alg_to_curve_id(pk_alg)));
270
0
    is_initialized_ = true;
271
0
}
272
273
size_t
274
pgp_dilithium_exdsa_composite_private_key_t::encoded_size(pgp_pubkey_alg_t pk_alg)
275
112
{
276
112
    dilithium_parameter_e dilithium_param = pk_alg_to_dilithium_id(pk_alg);
277
112
    pgp_curve_t           curve = pk_alg_to_curve_id(pk_alg);
278
112
    return exdsa_curve_privkey_size(curve) + dilithium_privkey_size(dilithium_param);
279
112
}
280
281
void
282
pgp_dilithium_exdsa_composite_private_key_t::parse_component_keys(
283
  std::vector<uint8_t> key_encoded)
284
46
{
285
46
    if (key_encoded.size() != encoded_size(pk_alg_)) {
286
0
        RNP_LOG("ML-DSA composite key format invalid: length mismatch");
287
0
        throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
288
0
    }
289
290
46
    dilithium_parameter_e dilithium_param = pk_alg_to_dilithium_id(pk_alg_);
291
46
    pgp_curve_t           curve = pk_alg_to_curve_id(pk_alg_);
292
46
    size_t                split_at = exdsa_curve_privkey_size(pk_alg_to_curve_id(pk_alg_));
293
294
46
    dilithium_key_ = std::make_unique<pgp_dilithium_private_key_t>(pgp_dilithium_private_key_t(
295
46
      key_encoded.data() + split_at, key_encoded.size() - split_at, dilithium_param));
296
46
    exdsa_key_ = std::make_unique<exdsa_private_key_t>(
297
46
      exdsa_private_key_t(key_encoded.data(), split_at, curve));
298
299
46
    is_initialized_ = true;
300
46
}
301
302
std::vector<uint8_t>
303
pgp_dilithium_exdsa_composite_private_key_t::get_encoded() const
304
0
{
305
0
    initialized_or_throw();
306
0
    std::vector<uint8_t> result;
307
0
    std::vector<uint8_t> exdsa_key_encoded = exdsa_key_->get_encoded();
308
0
    std::vector<uint8_t> dilithium_key_encoded = dilithium_key_->get_encoded();
309
310
0
    result.insert(result.end(), std::begin(exdsa_key_encoded), std::end(exdsa_key_encoded));
311
0
    result.insert(
312
0
      result.end(), std::begin(dilithium_key_encoded), std::end(dilithium_key_encoded));
313
0
    return result;
314
0
};
315
316
rnp_result_t
317
pgp_dilithium_exdsa_composite_private_key_t::sign(rnp::RNG *                       rng,
318
                                                  pgp_dilithium_exdsa_signature_t *sig,
319
                                                  pgp_hash_alg_t                   hash_alg,
320
                                                  const uint8_t *                  msg,
321
                                                  size_t msg_len) const
322
0
{
323
0
    initialized_or_throw();
324
0
    std::vector<uint8_t> dilithium_sig;
325
0
    std::vector<uint8_t> exdsa_sig;
326
0
    rnp_result_t         ret;
327
328
0
    try {
329
0
        dilithium_sig = dilithium_key_->sign(rng, msg, msg_len);
330
0
    } catch (const std::exception &e) {
331
0
        RNP_LOG("%s", e.what());
332
0
        return RNP_ERROR_SIGNING_FAILED;
333
0
    }
334
0
    ret = exdsa_key_->sign(rng, exdsa_sig, msg, msg_len, hash_alg);
335
0
    if (ret != RNP_SUCCESS) {
336
0
        RNP_LOG("exdsa sign failed");
337
0
        return RNP_ERROR_SIGNING_FAILED;
338
0
    }
339
340
0
    sig->sig.assign(exdsa_sig.data(), exdsa_sig.data() + exdsa_sig.size());
341
0
    sig->sig.insert(sig->sig.end(), dilithium_sig.begin(), dilithium_sig.end());
342
343
0
    return RNP_SUCCESS;
344
0
}
345
346
void
347
pgp_dilithium_exdsa_composite_private_key_t::secure_clear()
348
890
{
349
    // private key buffer is stored in a secure_vector and will be securely erased by the
350
    // destructor.
351
890
    dilithium_key_.reset();
352
890
    exdsa_key_.reset();
353
890
    is_initialized_ = false;
354
890
}
355
356
size_t
357
pgp_dilithium_exdsa_composite_public_key_t::encoded_size(pgp_pubkey_alg_t pk_alg)
358
19.7k
{
359
19.7k
    dilithium_parameter_e dilithium_param = pk_alg_to_dilithium_id(pk_alg);
360
19.7k
    pgp_curve_t           curve = pk_alg_to_curve_id(pk_alg);
361
19.7k
    return exdsa_curve_pubkey_size(curve) + dilithium_pubkey_size(dilithium_param);
362
19.7k
}
363
364
void
365
pgp_dilithium_exdsa_composite_public_key_t::parse_component_keys(
366
  std::vector<uint8_t> key_encoded)
367
9.14k
{
368
9.14k
    if (key_encoded.size() != encoded_size(pk_alg_)) {
369
0
        RNP_LOG("ML-DSA composite key format invalid: length mismatch");
370
0
        throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
371
0
    }
372
373
9.14k
    dilithium_parameter_e dilithium_param = pk_alg_to_dilithium_id(pk_alg_);
374
9.14k
    pgp_curve_t           curve = pk_alg_to_curve_id(pk_alg_);
375
9.14k
    size_t                split_at = exdsa_curve_pubkey_size(pk_alg_to_curve_id(pk_alg_));
376
377
9.14k
    dilithium_key_ = pgp_dilithium_public_key_t(
378
9.14k
      key_encoded.data() + split_at, key_encoded.size() - split_at, dilithium_param);
379
9.14k
    exdsa_key_ = exdsa_public_key_t(key_encoded.data(), split_at, curve);
380
381
9.14k
    is_initialized_ = true;
382
9.14k
}
383
384
std::vector<uint8_t>
385
pgp_dilithium_exdsa_composite_public_key_t::get_encoded() const
386
4.67k
{
387
4.67k
    initialized_or_throw();
388
4.67k
    std::vector<uint8_t> result;
389
4.67k
    std::vector<uint8_t> exdsa_key_encoded = exdsa_key_.get_encoded();
390
4.67k
    std::vector<uint8_t> dilithium_key_encoded = dilithium_key_.get_encoded();
391
392
4.67k
    result.insert(result.end(), std::begin(exdsa_key_encoded), std::end(exdsa_key_encoded));
393
4.67k
    result.insert(
394
4.67k
      result.end(), std::begin(dilithium_key_encoded), std::end(dilithium_key_encoded));
395
4.67k
    return result;
396
4.67k
};
397
398
rnp_result_t
399
pgp_dilithium_exdsa_composite_public_key_t::verify(const pgp_dilithium_exdsa_signature_t *sig,
400
                                                   pgp_hash_alg_t hash_alg,
401
                                                   const uint8_t *hash,
402
                                                   size_t         hash_len) const
403
0
{
404
0
    initialized_or_throw();
405
0
    std::vector<uint8_t> dilithium_sig;
406
0
    std::vector<uint8_t> exdsa_sig;
407
408
0
    if (sig->sig.size() != sig->composite_signature_size(pk_alg_)) {
409
0
        RNP_LOG("invalid signature size for mldsa exdsa composite algorithm %d", pk_alg_);
410
0
        return RNP_ERROR_VERIFICATION_FAILED;
411
0
    }
412
413
0
    size_t split_at = exdsa_curve_signature_size(pk_alg_to_curve_id(pk_alg_));
414
0
    exdsa_sig = std::vector<uint8_t>(sig->sig.data(), sig->sig.data() + split_at);
415
0
    dilithium_sig =
416
0
      std::vector<uint8_t>(sig->sig.data() + split_at, sig->sig.data() + sig->sig.size());
417
418
0
    if (exdsa_key_.verify(exdsa_sig, hash, hash_len, hash_alg) != RNP_SUCCESS ||
419
0
        !dilithium_key_.verify_signature(
420
0
          hash, hash_len, dilithium_sig.data(), dilithium_sig.size())) {
421
0
        RNP_LOG("could not verify composite signature");
422
0
        return RNP_ERROR_VERIFICATION_FAILED;
423
0
    }
424
425
0
    return RNP_SUCCESS;
426
0
}
427
428
bool
429
pgp_dilithium_exdsa_composite_public_key_t::is_valid(rnp::RNG *rng) const
430
0
{
431
0
    if (!is_initialized()) {
432
0
        return false;
433
0
    }
434
0
    return (exdsa_key_.is_valid(rng) && dilithium_key_.is_valid(rng));
435
0
}
436
437
bool
438
pgp_dilithium_exdsa_composite_private_key_t::is_valid(rnp::RNG *rng) const
439
0
{
440
0
    if (!is_initialized()) {
441
0
        return false;
442
0
    }
443
0
    return (exdsa_key_->is_valid(rng) && dilithium_key_->is_valid(rng));
444
0
}
445
446
rnp_result_t
447
dilithium_exdsa_validate_key(rnp::RNG *rng, const pgp_dilithium_exdsa_key_t *key, bool secret)
448
0
{
449
0
    bool valid;
450
451
0
    valid = key->pub.is_valid(rng);
452
0
    if (secret) {
453
0
        valid = valid && key->priv.is_valid(rng);
454
0
    }
455
0
    if (!valid) {
456
0
        return RNP_ERROR_GENERIC;
457
0
    }
458
459
0
    return RNP_SUCCESS;
460
0
}