/src/botan/src/lib/pubkey/sm2/sm2.cpp
Line | Count | Source |
1 | | /* |
2 | | * SM2 Signatures |
3 | | * (C) 2017,2018 Ribose Inc |
4 | | * (C) 2018,2024 Jack Lloyd |
5 | | * |
6 | | * Botan is released under the Simplified BSD License (see license.txt) |
7 | | */ |
8 | | |
9 | | #include <botan/sm2.h> |
10 | | |
11 | | #include <botan/hash.h> |
12 | | #include <botan/internal/keypair.h> |
13 | | #include <botan/internal/loadstor.h> |
14 | | #include <botan/internal/pk_ops_impl.h> |
15 | | |
16 | | namespace Botan { |
17 | | |
18 | 0 | std::string SM2_PublicKey::algo_name() const { |
19 | 0 | return "SM2"; |
20 | 0 | } |
21 | | |
22 | 0 | std::unique_ptr<Public_Key> SM2_PrivateKey::public_key() const { |
23 | 0 | return std::make_unique<SM2_Signature_PublicKey>(domain(), _public_ec_point()); |
24 | 0 | } |
25 | | |
26 | 0 | bool SM2_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const { |
27 | 0 | if(!EC_PrivateKey::check_key(rng, strong)) { |
28 | 0 | return false; |
29 | 0 | } |
30 | | |
31 | | // SM2 has an oddity in private key generation when compared to |
32 | | // other EC*DSA style signature algorithms described in ISO14888-3: |
33 | | // the private key x MUST be in [0, q-1) instead of [0, q). |
34 | | // |
35 | | // The lower bound is already checked by the default impl |
36 | 0 | if(private_value() >= domain().get_order() - 1) { |
37 | 0 | return false; |
38 | 0 | } |
39 | | |
40 | 0 | if(!strong) { |
41 | 0 | return true; |
42 | 0 | } |
43 | | |
44 | 0 | return KeyPair::signature_consistency_check(rng, *this, "user@example.com,SM3"); |
45 | 0 | } |
46 | | |
47 | | SM2_PrivateKey::SM2_PrivateKey(const AlgorithmIdentifier& alg_id, std::span<const uint8_t> key_bits) : |
48 | 0 | EC_PrivateKey(alg_id, key_bits), |
49 | 0 | m_da_inv((this->_private_key() + EC_Scalar::one(domain())).invert()), |
50 | 0 | m_da_inv_legacy(m_da_inv.to_bigint()) {}Unexecuted instantiation: Botan::SM2_PrivateKey::SM2_PrivateKey(Botan::AlgorithmIdentifier const&, std::__1::span<unsigned char const, 18446744073709551615ul>) Unexecuted instantiation: Botan::SM2_PrivateKey::SM2_PrivateKey(Botan::AlgorithmIdentifier const&, std::__1::span<unsigned char const, 18446744073709551615ul>) |
51 | | |
52 | | SM2_PrivateKey::SM2_PrivateKey(EC_Group group, EC_Scalar x) : |
53 | 0 | EC_PrivateKey(std::move(group), std::move(x)), |
54 | 0 | m_da_inv((this->_private_key() + EC_Scalar::one(domain())).invert()), |
55 | 0 | m_da_inv_legacy(m_da_inv.to_bigint()) {}Unexecuted instantiation: Botan::SM2_PrivateKey::SM2_PrivateKey(Botan::EC_Group, Botan::EC_Scalar) Unexecuted instantiation: Botan::SM2_PrivateKey::SM2_PrivateKey(Botan::EC_Group, Botan::EC_Scalar) |
56 | | |
57 | | SM2_PrivateKey::SM2_PrivateKey(RandomNumberGenerator& rng, EC_Group group) : |
58 | 0 | EC_PrivateKey(rng, std::move(group)), |
59 | 0 | m_da_inv((this->_private_key() + EC_Scalar::one(domain())).invert()), |
60 | 0 | m_da_inv_legacy(m_da_inv.to_bigint()) {}Unexecuted instantiation: Botan::SM2_PrivateKey::SM2_PrivateKey(Botan::RandomNumberGenerator&, Botan::EC_Group) Unexecuted instantiation: Botan::SM2_PrivateKey::SM2_PrivateKey(Botan::RandomNumberGenerator&, Botan::EC_Group) |
61 | | |
62 | | SM2_PrivateKey::SM2_PrivateKey(RandomNumberGenerator& rng, EC_Group group, const BigInt& x) : |
63 | 0 | EC_PrivateKey(rng, std::move(group), x), |
64 | 0 | m_da_inv((this->_private_key() + EC_Scalar::one(domain())).invert()), |
65 | 0 | m_da_inv_legacy(m_da_inv.to_bigint()) {}Unexecuted instantiation: Botan::SM2_PrivateKey::SM2_PrivateKey(Botan::RandomNumberGenerator&, Botan::EC_Group, Botan::BigInt const&) Unexecuted instantiation: Botan::SM2_PrivateKey::SM2_PrivateKey(Botan::RandomNumberGenerator&, Botan::EC_Group, Botan::BigInt const&) |
66 | | |
67 | | std::vector<uint8_t> sm2_compute_za(HashFunction& hash, |
68 | | std::string_view user_id, |
69 | | const EC_Group& group, |
70 | 0 | const EC_AffinePoint& pubkey) { |
71 | 0 | if(user_id.size() >= 8192) { |
72 | 0 | throw Invalid_Argument("SM2 user id too long to represent"); |
73 | 0 | } |
74 | | |
75 | 0 | const uint16_t uid_len = static_cast<uint16_t>(8 * user_id.size()); |
76 | |
|
77 | 0 | hash.update(get_byte<0>(uid_len)); |
78 | 0 | hash.update(get_byte<1>(uid_len)); |
79 | 0 | hash.update(user_id); |
80 | |
|
81 | 0 | const size_t p_bytes = group.get_p_bytes(); |
82 | |
|
83 | 0 | hash.update(group.get_a().serialize(p_bytes)); |
84 | 0 | hash.update(group.get_b().serialize(p_bytes)); |
85 | 0 | hash.update(group.get_g_x().serialize(p_bytes)); |
86 | 0 | hash.update(group.get_g_y().serialize(p_bytes)); |
87 | 0 | hash.update(pubkey.xy_bytes()); |
88 | |
|
89 | 0 | return hash.final<std::vector<uint8_t>>(); |
90 | 0 | } |
91 | | |
92 | | namespace { |
93 | | |
94 | | /** |
95 | | * SM2 signature operation |
96 | | */ |
97 | | class SM2_Signature_Operation final : public PK_Ops::Signature { |
98 | | public: |
99 | | SM2_Signature_Operation(const SM2_PrivateKey& sm2, std::string_view ident, std::string_view hash) : |
100 | 0 | m_group(sm2.domain()), m_x(sm2._private_key()), m_da_inv(sm2._get_da_inv()) { |
101 | 0 | if(hash == "Raw") { |
102 | | // m_hash is null, m_za is empty |
103 | 0 | } else { |
104 | 0 | m_hash = HashFunction::create_or_throw(hash); |
105 | | // ZA=H256(ENTLA || IDA || a || b || xG || yG || xA || yA) |
106 | 0 | m_za = sm2_compute_za(*m_hash, ident, m_group, sm2._public_ec_point()); |
107 | 0 | m_hash->update(m_za); |
108 | 0 | } |
109 | 0 | } |
110 | | |
111 | 0 | size_t signature_length() const override { return 2 * m_group.get_order_bytes(); } |
112 | | |
113 | 0 | void update(std::span<const uint8_t> input) override { |
114 | 0 | if(m_hash) { |
115 | 0 | m_hash->update(input); |
116 | 0 | } else { |
117 | 0 | m_digest.insert(m_digest.end(), input.begin(), input.end()); |
118 | 0 | } |
119 | 0 | } |
120 | | |
121 | | std::vector<uint8_t> sign(RandomNumberGenerator& rng) override; |
122 | | |
123 | 0 | std::string hash_function() const override { return m_hash ? m_hash->name() : "Raw"; } |
124 | | |
125 | | private: |
126 | | const EC_Group m_group; |
127 | | const EC_Scalar m_x; |
128 | | const EC_Scalar m_da_inv; |
129 | | |
130 | | std::vector<uint8_t> m_za; |
131 | | secure_vector<uint8_t> m_digest; |
132 | | std::unique_ptr<HashFunction> m_hash; |
133 | | }; |
134 | | |
135 | 0 | std::vector<uint8_t> SM2_Signature_Operation::sign(RandomNumberGenerator& rng) { |
136 | 0 | const auto e = [&]() { |
137 | 0 | if(m_hash) { |
138 | 0 | auto ie = EC_Scalar::from_bytes_mod_order(m_group, m_hash->final()); |
139 | | // prepend ZA for next signature if any |
140 | 0 | m_hash->update(m_za); |
141 | 0 | return ie; |
142 | 0 | } else { |
143 | 0 | auto ie = EC_Scalar::from_bytes_mod_order(m_group, m_digest); |
144 | 0 | m_digest.clear(); |
145 | 0 | return ie; |
146 | 0 | } |
147 | 0 | }(); |
148 | |
|
149 | 0 | const auto k = EC_Scalar::random(m_group, rng); |
150 | |
|
151 | 0 | const auto r = EC_Scalar::gk_x_mod_order(k, rng) + e; |
152 | 0 | const auto s = (k - r * m_x) * m_da_inv; |
153 | |
|
154 | 0 | return EC_Scalar::serialize_pair(r, s); |
155 | 0 | } |
156 | | |
157 | | /** |
158 | | * SM2 verification operation |
159 | | */ |
160 | | class SM2_Verification_Operation final : public PK_Ops::Verification { |
161 | | public: |
162 | | SM2_Verification_Operation(const SM2_PublicKey& sm2, std::string_view ident, std::string_view hash) : |
163 | 0 | m_group(sm2.domain()), m_gy_mul(sm2._public_ec_point()) { |
164 | 0 | if(hash == "Raw") { |
165 | | // m_hash is null, m_za is empty |
166 | 0 | } else { |
167 | 0 | m_hash = HashFunction::create_or_throw(hash); |
168 | | // ZA=H256(ENTLA || IDA || a || b || xG || yG || xA || yA) |
169 | 0 | m_za = sm2_compute_za(*m_hash, ident, m_group, sm2._public_ec_point()); |
170 | 0 | m_hash->update(m_za); |
171 | 0 | } |
172 | 0 | } |
173 | | |
174 | 0 | void update(std::span<const uint8_t> input) override { |
175 | 0 | if(m_hash) { |
176 | 0 | m_hash->update(input); |
177 | 0 | } else { |
178 | 0 | m_digest.insert(m_digest.end(), input.begin(), input.end()); |
179 | 0 | } |
180 | 0 | } |
181 | | |
182 | | bool is_valid_signature(std::span<const uint8_t> sig) override; |
183 | | |
184 | 0 | std::string hash_function() const override { return m_hash ? m_hash->name() : "Raw"; } |
185 | | |
186 | | private: |
187 | | const EC_Group m_group; |
188 | | const EC_Group::Mul2Table m_gy_mul; |
189 | | secure_vector<uint8_t> m_digest; |
190 | | std::vector<uint8_t> m_za; |
191 | | std::unique_ptr<HashFunction> m_hash; |
192 | | }; |
193 | | |
194 | 0 | bool SM2_Verification_Operation::is_valid_signature(std::span<const uint8_t> sig) { |
195 | 0 | const auto e = [&]() { |
196 | 0 | if(m_hash) { |
197 | 0 | auto ie = EC_Scalar::from_bytes_mod_order(m_group, m_hash->final()); |
198 | | // prepend ZA for next signature if any |
199 | 0 | m_hash->update(m_za); |
200 | 0 | return ie; |
201 | 0 | } else { |
202 | 0 | auto ie = EC_Scalar::from_bytes_mod_order(m_group, m_digest); |
203 | 0 | m_digest.clear(); |
204 | 0 | return ie; |
205 | 0 | } |
206 | 0 | }(); |
207 | |
|
208 | 0 | if(auto rs = EC_Scalar::deserialize_pair(m_group, sig)) { |
209 | 0 | const auto& [r, s] = rs.value(); |
210 | |
|
211 | 0 | if(r.is_nonzero() && s.is_nonzero()) { |
212 | 0 | const auto t = r + s; |
213 | 0 | if(t.is_nonzero()) { |
214 | | // Check if r - e = x_coord(g*s + y*t) % n |
215 | 0 | return m_gy_mul.mul2_vartime_x_mod_order_eq(r - e, s, t); |
216 | 0 | } |
217 | 0 | } |
218 | 0 | } |
219 | 0 | return false; |
220 | 0 | } |
221 | | |
222 | 0 | void parse_sm2_param_string(std::string_view params, std::string& userid, std::string& hash) { |
223 | | // GM/T 0009-2012 specifies this as the default userid |
224 | 0 | const std::string default_userid = "1234567812345678"; |
225 | | |
226 | | // defaults: |
227 | 0 | userid = default_userid; |
228 | 0 | hash = "SM3"; |
229 | | |
230 | | /* |
231 | | * SM2 parameters have the following possible formats: |
232 | | * Ident [since 2.2.0] |
233 | | * Ident,Hash [since 2.3.0] |
234 | | */ |
235 | |
|
236 | 0 | auto comma = params.find(','); |
237 | 0 | if(comma == std::string::npos) { |
238 | 0 | userid = params; |
239 | 0 | } else { |
240 | 0 | userid = params.substr(0, comma); |
241 | 0 | hash = params.substr(comma + 1, std::string::npos); |
242 | 0 | } |
243 | 0 | } |
244 | | |
245 | | } // namespace |
246 | | |
247 | 0 | std::unique_ptr<Private_Key> SM2_PublicKey::generate_another(RandomNumberGenerator& rng) const { |
248 | 0 | return std::make_unique<SM2_PrivateKey>(rng, domain()); |
249 | 0 | } |
250 | | |
251 | | std::unique_ptr<PK_Ops::Verification> SM2_PublicKey::create_verification_op(std::string_view params, |
252 | 0 | std::string_view provider) const { |
253 | 0 | if(provider == "base" || provider.empty()) { |
254 | 0 | std::string userid; |
255 | 0 | std::string hash; |
256 | 0 | parse_sm2_param_string(params, userid, hash); |
257 | 0 | return std::make_unique<SM2_Verification_Operation>(*this, userid, hash); |
258 | 0 | } |
259 | | |
260 | 0 | throw Provider_Not_Found(algo_name(), provider); |
261 | 0 | } |
262 | | |
263 | | std::unique_ptr<PK_Ops::Signature> SM2_PrivateKey::create_signature_op(RandomNumberGenerator& /*rng*/, |
264 | | std::string_view params, |
265 | 0 | std::string_view provider) const { |
266 | 0 | if(provider == "base" || provider.empty()) { |
267 | 0 | std::string userid; |
268 | 0 | std::string hash; |
269 | 0 | parse_sm2_param_string(params, userid, hash); |
270 | 0 | return std::make_unique<SM2_Signature_Operation>(*this, userid, hash); |
271 | 0 | } |
272 | | |
273 | 0 | throw Provider_Not_Found(algo_name(), provider); |
274 | 0 | } |
275 | | |
276 | | } // namespace Botan |