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