Coverage Report

Created: 2026-06-15 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/botan/src/lib/pubkey/eckcdsa/eckcdsa.cpp
Line
Count
Source
1
/*
2
* ECKCDSA (ISO/IEC 14888-3:2006/Cor.2:2009)
3
* (C) 2016 René Korthaus, Sirrix AG
4
* (C) 2018,2024 Jack Lloyd
5
* (C) 2023 Philippe Lieser - Rohde & Schwarz Cybersecurity
6
*
7
* Botan is released under the Simplified BSD License (see license.txt)
8
*/
9
10
#include <botan/eckcdsa.h>
11
12
#include <botan/ec_group.h>
13
#include <botan/hash.h>
14
#include <botan/mem_ops.h>
15
#include <botan/rng.h>
16
#include <botan/internal/concat_util.h>
17
#include <botan/internal/fmt.h>
18
#include <botan/internal/keypair.h>
19
#include <botan/internal/parsing.h>
20
#include <botan/internal/pk_ops_impl.h>
21
#include <botan/internal/scan_name.h>
22
23
namespace Botan {
24
25
0
std::unique_ptr<Public_Key> ECKCDSA_PrivateKey::public_key() const {
26
0
   return std::make_unique<ECKCDSA_PublicKey>(domain(), _public_ec_point());
27
0
}
28
29
0
bool ECKCDSA_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const {
30
0
   if(!EC_PrivateKey::check_key(rng, strong)) {
31
0
      return false;
32
0
   }
33
34
0
   if(!strong) {
35
0
      return true;
36
0
   }
37
38
0
   return KeyPair::signature_consistency_check(rng, *this, "SHA-256");
39
0
}
40
41
namespace {
42
43
0
std::unique_ptr<HashFunction> eckcdsa_signature_hash(std::string_view padding) {
44
0
   if(auto hash = HashFunction::create(padding)) {
45
0
      return hash;
46
0
   }
47
48
0
   const SCAN_Name req(padding);
49
50
0
   if(req.algo_name() == "EMSA1" && req.arg_count() == 1) {
51
0
      if(auto hash = HashFunction::create(req.arg(0))) {
52
0
         return hash;
53
0
      }
54
0
   }
55
56
   // intentionally not supporting Raw for ECKCDSA, we need to know
57
   // the length in advance which complicates the logic for Raw
58
59
0
   throw Algorithm_Not_Found(padding);
60
0
}
61
62
0
std::unique_ptr<HashFunction> eckcdsa_signature_hash(const AlgorithmIdentifier& alg_id) {
63
0
   const auto oid_info = split_on(alg_id.oid().to_formatted_string(), '/');
64
65
0
   if(oid_info.size() != 2 || oid_info[0] != "ECKCDSA") {
66
0
      throw Decoding_Error(fmt("Unexpected AlgorithmIdentifier OID {} in association with ECKCDSA key", alg_id.oid()));
67
0
   }
68
69
0
   if(!alg_id.parameters_are_empty()) {
70
0
      throw Decoding_Error("Unexpected non-empty AlgorithmIdentifier parameters for ECKCDSA");
71
0
   }
72
73
0
   return HashFunction::create_or_throw(oid_info[1]);
74
0
}
75
76
0
std::vector<uint8_t> eckcdsa_prefix(const EC_AffinePoint& point, size_t hash_block_size) {
77
0
   auto prefix = point.xy_bytes<std::vector<uint8_t>>();
78
79
   // Either truncate or zero-extend to match the hash block size
80
0
   prefix.resize(hash_block_size);
81
82
0
   return prefix;
83
0
}
84
85
/**
86
 * @brief Truncate hash output if needed.
87
 *
88
 * If the output length of the hash function exceeds the size of the group order,
89
 * ISO/IEC 14888-3:2018 specifies a truncation of the hash output
90
 * when calculating the witness R (the first part of the signature) and H.
91
 *
92
 * The truncation is specified as follows:
93
 *
94
 * R = I2BS(beta', BS2I(gamma, R) mod 2^beta')
95
 * H = I2BS(beta', BS2I(gamma, H) mod 2^beta')
96
 *
97
 * where
98
 * - gamma: the output bit-length of the hash-function
99
 * - beta: the bit-length of the prime number q (i.e. the group order size)
100
 * - beta' = 8 * ceil(beta / 8)
101
 *
102
 * This essentially means a truncation on the byte level
103
 * happens from the low side of the hash.
104
 *
105
 * @param[in,out] digest The hash output to potentially truncate.
106
 * @param[in] group_order_bytes Size of the group order.
107
 */
108
0
void truncate_hash_if_needed(std::vector<uint8_t>& digest, size_t group_order_bytes) {
109
0
   if(digest.size() > group_order_bytes) {
110
0
      const size_t bytes_to_truncate = digest.size() - group_order_bytes;
111
0
      digest.erase(digest.begin(), digest.begin() + bytes_to_truncate);
112
0
   }
113
0
}
114
115
/**
116
* ECKCDSA signature operation
117
*/
118
class ECKCDSA_Signature_Operation final : public PK_Ops::Signature {
119
   public:
120
      ECKCDSA_Signature_Operation(const ECKCDSA_PrivateKey& eckcdsa, std::string_view padding) :
121
0
            m_group(eckcdsa.domain()),
122
0
            m_x(eckcdsa._private_key()),
123
0
            m_hash(eckcdsa_signature_hash(padding)),
124
0
            m_prefix(eckcdsa_prefix(eckcdsa._public_ec_point(), m_hash->hash_block_size())),
125
0
            m_prefix_used(false) {}
126
127
0
      void update(std::span<const uint8_t> input) override {
128
0
         if(!m_prefix_used) {
129
0
            m_hash->update(m_prefix);
130
0
            m_prefix_used = true;
131
0
         }
132
0
         m_hash->update(input);
133
0
      }
134
135
0
      std::vector<uint8_t> sign(RandomNumberGenerator& rng) override {
136
0
         m_prefix_used = false;
137
0
         std::vector<uint8_t> digest = m_hash->final_stdvec();
138
0
         truncate_hash_if_needed(digest, m_group.get_order_bytes());
139
0
         return raw_sign(digest, rng);
140
0
      }
141
142
0
      size_t signature_length() const override { return 2 * m_group.get_order_bytes(); }
143
144
      AlgorithmIdentifier algorithm_identifier() const override;
145
146
0
      std::string hash_function() const override { return m_hash->name(); }
147
148
   private:
149
      std::vector<uint8_t> raw_sign(std::span<const uint8_t> msg, RandomNumberGenerator& rng);
150
151
      const EC_Group m_group;
152
      const EC_Scalar m_x;
153
      std::unique_ptr<HashFunction> m_hash;
154
      std::vector<uint8_t> m_prefix;
155
      bool m_prefix_used;
156
};
157
158
0
AlgorithmIdentifier ECKCDSA_Signature_Operation::algorithm_identifier() const {
159
0
   const std::string full_name = "ECKCDSA/" + m_hash->name();
160
0
   const OID oid = OID::from_string(full_name);
161
0
   return AlgorithmIdentifier(oid, AlgorithmIdentifier::USE_EMPTY_PARAM);
162
0
}
163
164
0
std::vector<uint8_t> ECKCDSA_Signature_Operation::raw_sign(std::span<const uint8_t> msg, RandomNumberGenerator& rng) {
165
0
   const auto k = EC_Scalar::random(m_group, rng);
166
167
   // We cannot use gk_x_mod_order because ECKCDSA, unlike ECDSA or ECGDSA, does
168
   // not reduce the x coordinate modulo the group order.
169
0
   m_hash->update(EC_AffinePoint::g_mul(k, rng).x_bytes());
170
0
   auto c = m_hash->final_stdvec();
171
0
   truncate_hash_if_needed(c, m_group.get_order_bytes());
172
173
0
   const auto r = c;
174
175
0
   xor_buf(c, msg);
176
0
   const auto w = EC_Scalar::from_bytes_mod_order(m_group, c);
177
178
0
   const auto s = m_x * (k - w);
179
0
   if(s.is_zero()) {
180
0
      throw Internal_Error("During ECKCDSA signature generation created zero s");
181
0
   }
182
183
0
   return concat(r, s.serialize());
184
0
}
185
186
/**
187
* ECKCDSA verification operation
188
*/
189
class ECKCDSA_Verification_Operation final : public PK_Ops::Verification {
190
   public:
191
      ECKCDSA_Verification_Operation(const ECKCDSA_PublicKey& eckcdsa, std::string_view padding) :
192
0
            m_group(eckcdsa.domain()),
193
0
            m_gy_mul(eckcdsa._public_ec_point()),
194
0
            m_hash(eckcdsa_signature_hash(padding)),
195
0
            m_prefix(eckcdsa_prefix(eckcdsa._public_ec_point(), m_hash->hash_block_size())),
196
0
            m_prefix_used(false) {}
197
198
      ECKCDSA_Verification_Operation(const ECKCDSA_PublicKey& eckcdsa, const AlgorithmIdentifier& alg_id) :
199
0
            m_group(eckcdsa.domain()),
200
0
            m_gy_mul(eckcdsa._public_ec_point()),
201
0
            m_hash(eckcdsa_signature_hash(alg_id)),
202
0
            m_prefix(eckcdsa_prefix(eckcdsa._public_ec_point(), m_hash->hash_block_size())),
203
0
            m_prefix_used(false) {}
204
205
      void update(std::span<const uint8_t> msg) override;
206
207
      bool is_valid_signature(std::span<const uint8_t> sig) override;
208
209
0
      std::string hash_function() const override { return m_hash->name(); }
210
211
   private:
212
      bool verify(std::span<const uint8_t> msg, std::span<const uint8_t> sig);
213
214
      const EC_Group m_group;
215
      const EC_Group::Mul2Table m_gy_mul;
216
      std::unique_ptr<HashFunction> m_hash;
217
      std::vector<uint8_t> m_prefix;
218
      bool m_prefix_used;
219
};
220
221
0
void ECKCDSA_Verification_Operation::update(std::span<const uint8_t> msg) {
222
0
   if(!m_prefix_used) {
223
0
      m_prefix_used = true;
224
0
      m_hash->update(m_prefix.data(), m_prefix.size());
225
0
   }
226
0
   m_hash->update(msg);
227
0
}
228
229
0
bool ECKCDSA_Verification_Operation::is_valid_signature(std::span<const uint8_t> sig) {
230
0
   m_prefix_used = false;
231
0
   std::vector<uint8_t> digest = m_hash->final_stdvec();
232
0
   truncate_hash_if_needed(digest, m_group.get_order_bytes());
233
0
   return verify(digest, sig);
234
0
}
235
236
0
bool ECKCDSA_Verification_Operation::verify(std::span<const uint8_t> msg, std::span<const uint8_t> sig) {
237
0
   const size_t order_bytes = m_group.get_order_bytes();
238
239
0
   const size_t size_r = std::min(msg.size(), order_bytes);
240
0
   if(sig.size() != size_r + order_bytes) {
241
0
      return false;
242
0
   }
243
244
0
   auto r = sig.first(size_r);
245
246
0
   if(auto s = EC_Scalar::deserialize(m_group, sig.last(order_bytes))) {
247
0
      std::vector<uint8_t> r_xor_e(r.size());
248
0
      xor_buf(r_xor_e, r, msg.first(size_r));
249
250
0
      const auto w = EC_Scalar::from_bytes_mod_order(m_group, r_xor_e);
251
252
0
      if(auto q = m_gy_mul.mul2_vartime(w, s.value())) {
253
0
         std::vector<uint8_t> v = m_hash->process<std::vector<uint8_t>>(q->x_bytes());
254
0
         truncate_hash_if_needed(v, m_group.get_order_bytes());
255
0
         return constant_time_compare(v, r);
256
0
      }
257
0
   }
258
259
0
   return false;
260
0
}
261
262
}  // namespace
263
264
0
std::optional<size_t> ECKCDSA_PublicKey::_signature_element_size_for_DER_encoding() const {
265
0
   return domain().get_order_bytes();
266
0
}
267
268
0
std::unique_ptr<Private_Key> ECKCDSA_PublicKey::generate_another(RandomNumberGenerator& rng) const {
269
0
   return std::make_unique<ECKCDSA_PrivateKey>(rng, domain());
270
0
}
271
272
std::unique_ptr<PK_Ops::Verification> ECKCDSA_PublicKey::create_verification_op(std::string_view params,
273
0
                                                                                std::string_view provider) const {
274
0
   if(provider == "base" || provider.empty()) {
275
0
      return std::make_unique<ECKCDSA_Verification_Operation>(*this, params);
276
0
   }
277
0
   throw Provider_Not_Found(algo_name(), provider);
278
0
}
279
280
std::unique_ptr<PK_Ops::Verification> ECKCDSA_PublicKey::create_x509_verification_op(
281
0
   const AlgorithmIdentifier& signature_algorithm, std::string_view provider) const {
282
0
   if(provider == "base" || provider.empty()) {
283
0
      return std::make_unique<ECKCDSA_Verification_Operation>(*this, signature_algorithm);
284
0
   }
285
286
0
   throw Provider_Not_Found(algo_name(), provider);
287
0
}
288
289
std::unique_ptr<PK_Ops::Signature> ECKCDSA_PrivateKey::create_signature_op(RandomNumberGenerator& /*rng*/,
290
                                                                           std::string_view params,
291
0
                                                                           std::string_view provider) const {
292
0
   if(provider == "base" || provider.empty()) {
293
0
      return std::make_unique<ECKCDSA_Signature_Operation>(*this, params);
294
0
   }
295
0
   throw Provider_Not_Found(algo_name(), provider);
296
0
}
297
298
}  // namespace Botan