Line data Source code
1 : /**
2 : * Copyright Soramitsu Co., Ltd. All Rights Reserved.
3 : * SPDX-License-Identifier: Apache-2.0
4 : */
5 :
6 : #include "validators/field_validator.hpp"
7 :
8 : #include <limits>
9 :
10 : #include <boost/algorithm/string_regex.hpp>
11 : #include <boost/format.hpp>
12 : #include "cryptography/crypto_provider/crypto_defaults.hpp"
13 : #include "cryptography/crypto_provider/crypto_verifier.hpp"
14 : #include "interfaces/common_objects/amount.hpp"
15 : #include "interfaces/common_objects/peer.hpp"
16 : #include "interfaces/queries/query_payload_meta.hpp"
17 : #include "validators/field_validator.hpp"
18 :
19 : // TODO: 15.02.18 nickaleks Change structure to compositional IR-978
20 :
21 : namespace shared_model {
22 : namespace validation {
23 :
24 : const std::string FieldValidator::account_name_pattern_ =
25 77 : R"#([a-z_0-9]{1,32})#";
26 : const std::string FieldValidator::asset_name_pattern_ =
27 77 : R"#([a-z_0-9]{1,32})#";
28 : const std::string FieldValidator::domain_pattern_ =
29 77 : R"#(([a-zA-Z]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)#";
30 : const std::string FieldValidator::ip_v4_pattern_ =
31 77 : R"#(^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3})#"
32 : R"#(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])))#";
33 : const std::string FieldValidator::peer_address_pattern_ = "(("
34 77 : + ip_v4_pattern_ + ")|(" + domain_pattern_ + ")):"
35 77 : + R"#((6553[0-5]|655[0-2]\d|65[0-4]\d\d|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)$)#";
36 : const std::string FieldValidator::account_id_pattern_ =
37 77 : account_name_pattern_ + R"#(\@)#" + domain_pattern_;
38 : const std::string FieldValidator::asset_id_pattern_ =
39 77 : asset_name_pattern_ + R"#(\#)#" + domain_pattern_;
40 : const std::string FieldValidator::detail_key_pattern_ =
41 77 : R"([A-Za-z0-9_]{1,64})";
42 : const std::string FieldValidator::role_id_pattern_ = R"#([a-z_0-9]{1,32})#";
43 :
44 : const size_t FieldValidator::public_key_size =
45 77 : crypto::DefaultCryptoAlgorithmType::kPublicKeyLength;
46 : const size_t FieldValidator::signature_size =
47 77 : crypto::DefaultCryptoAlgorithmType::kSignatureLength;
48 : const size_t FieldValidator::hash_size =
49 77 : crypto::DefaultCryptoAlgorithmType::kHashLength;
50 : /// limit for the set account detail size in bytes
51 : const size_t FieldValidator::value_size = 4 * 1024 * 1024;
52 : const size_t FieldValidator::description_size = 64;
53 :
54 : const std::regex FieldValidator::account_name_regex_(account_name_pattern_);
55 : const std::regex FieldValidator::asset_name_regex_(asset_name_pattern_);
56 : const std::regex FieldValidator::domain_regex_(domain_pattern_);
57 : const std::regex FieldValidator::ip_v4_regex_(ip_v4_pattern_);
58 : const std::regex FieldValidator::peer_address_regex_(peer_address_pattern_);
59 : const std::regex FieldValidator::account_id_regex_(account_id_pattern_);
60 : const std::regex FieldValidator::asset_id_regex_(asset_id_pattern_);
61 : const std::regex FieldValidator::detail_key_regex_(detail_key_pattern_);
62 : const std::regex FieldValidator::role_id_regex_(role_id_pattern_);
63 :
64 : FieldValidator::FieldValidator(time_t future_gap,
65 : TimeFunction time_provider)
66 7086 : : future_gap_(future_gap), time_provider_(time_provider) {}
67 :
68 : void FieldValidator::validateAccountId(
69 : ReasonsGroupType &reason,
70 : const interface::types::AccountIdType &account_id) const {
71 5340 : if (not std::regex_match(account_id, account_id_regex_)) {
72 : auto message =
73 61 : (boost::format("Wrongly formed account_id, passed value: '%s'. "
74 : "Field should match regex '%s'")
75 61 : % account_id % account_id_pattern_)
76 61 : .str();
77 61 : reason.second.push_back(std::move(message));
78 61 : }
79 5340 : }
80 :
81 : void FieldValidator::validateAssetId(
82 : ReasonsGroupType &reason,
83 : const interface::types::AssetIdType &asset_id) const {
84 838 : if (not std::regex_match(asset_id, asset_id_regex_)) {
85 19 : auto message = (boost::format("Wrongly formed asset_id, passed value: "
86 : "'%s'. Field should match regex '%s'")
87 19 : % asset_id % asset_id_pattern_)
88 19 : .str();
89 19 : reason.second.push_back(std::move(message));
90 19 : }
91 838 : }
92 :
93 : void FieldValidator::validatePeer(ReasonsGroupType &reason,
94 : const interface::Peer &peer) const {
95 4473 : validatePeerAddress(reason, peer.address());
96 4473 : validatePubkey(reason, peer.pubkey());
97 4473 : }
98 :
99 : void FieldValidator::validateAmount(ReasonsGroupType &reason,
100 : const interface::Amount &amount) const {
101 763 : if (amount.intValue() <= 0) {
102 : auto message =
103 10 : (boost::format("Amount must be greater than 0, passed value: %d")
104 10 : % amount.intValue())
105 10 : .str();
106 10 : reason.second.push_back(message);
107 10 : }
108 763 : }
109 :
110 : void FieldValidator::validatePubkey(
111 : ReasonsGroupType &reason,
112 : const interface::types::PubkeyType &pubkey) const {
113 10604 : auto opt_reason = shared_model::validation::validatePubkey(pubkey);
114 10604 : if (opt_reason) {
115 15 : reason.second.push_back(std::move(*opt_reason));
116 15 : }
117 10604 : }
118 :
119 : void FieldValidator::validatePeerAddress(
120 : ReasonsGroupType &reason,
121 : const interface::types::AddressType &address) const {
122 4473 : if (not std::regex_match(address, peer_address_regex_)) {
123 : auto message =
124 18 : (boost::format("Wrongly formed peer address, passed value: '%s'. "
125 : "Field should have a valid 'host:port' format where "
126 : "host is IPv4 or a "
127 : "hostname following RFC1035, RFC1123 specifications")
128 18 : % address)
129 18 : .str();
130 18 : reason.second.push_back(std::move(message));
131 18 : }
132 4473 : }
133 :
134 : void FieldValidator::validateRoleId(
135 : ReasonsGroupType &reason,
136 : const interface::types::RoleIdType &role_id) const {
137 6847 : if (not std::regex_match(role_id, role_id_regex_)) {
138 16 : auto message = (boost::format("Wrongly formed role_id, passed value: "
139 : "'%s'. Field should match regex '%s'")
140 16 : % role_id % role_id_pattern_)
141 16 : .str();
142 16 : reason.second.push_back(std::move(message));
143 16 : }
144 6847 : }
145 :
146 : void FieldValidator::validateAccountName(
147 : ReasonsGroupType &reason,
148 : const interface::types::AccountNameType &account_name) const {
149 2133 : if (not std::regex_match(account_name, account_name_regex_)) {
150 : auto message =
151 6 : (boost::format("Wrongly formed account_name, passed value: '%s'. "
152 : "Field should match regex '%s'")
153 6 : % account_name % account_name_pattern_)
154 6 : .str();
155 6 : reason.second.push_back(std::move(message));
156 6 : }
157 2133 : }
158 :
159 : void FieldValidator::validateDomainId(
160 : ReasonsGroupType &reason,
161 : const interface::types::DomainIdType &domain_id) const {
162 3827 : if (not std::regex_match(domain_id, domain_regex_)) {
163 30 : auto message = (boost::format("Wrongly formed domain_id, passed value: "
164 : "'%s'. Field should match regex '%s'")
165 30 : % domain_id % domain_pattern_)
166 30 : .str();
167 30 : reason.second.push_back(std::move(message));
168 30 : }
169 3827 : }
170 :
171 : void FieldValidator::validateAssetName(
172 : ReasonsGroupType &reason,
173 : const interface::types::AssetNameType &asset_name) const {
174 762 : if (not std::regex_match(asset_name, asset_name_regex_)) {
175 : auto message =
176 15 : (boost::format("Wrongly formed asset_name, passed value: '%s'. "
177 : "Field should match regex '%s'")
178 15 : % asset_name % asset_name_pattern_)
179 15 : .str();
180 15 : reason.second.push_back(std::move(message));
181 15 : }
182 762 : }
183 :
184 : void FieldValidator::validateAccountDetailKey(
185 : ReasonsGroupType &reason,
186 : const interface::types::AccountDetailKeyType &key) const {
187 228 : if (not std::regex_match(key, detail_key_regex_)) {
188 7 : auto message = (boost::format("Wrongly formed key, passed value: '%s'. "
189 : "Field should match regex '%s'")
190 7 : % key % detail_key_pattern_)
191 7 : .str();
192 7 : reason.second.push_back(std::move(message));
193 7 : }
194 228 : }
195 :
196 : void FieldValidator::validateAccountDetailValue(
197 : ReasonsGroupType &reason,
198 : const interface::types::AccountDetailValueType &value) const {
199 226 : if (value.size() > value_size) {
200 : auto message =
201 1 : (boost::format("Detail value size should be less or equal '%d'")
202 1 : % value_size)
203 1 : .str();
204 1 : reason.second.push_back(std::move(message));
205 1 : }
206 226 : }
207 :
208 : void FieldValidator::validatePrecision(
209 : ReasonsGroupType &reason,
210 : const interface::types::PrecisionType &precision) const {
211 : /* The following validation is pointless since PrecisionType is already
212 : * uint8_t, but it is going to be changed and the validation will become
213 : * meaningful.
214 : */
215 764 : interface::types::PrecisionType min = std::numeric_limits<uint8_t>::min();
216 764 : interface::types::PrecisionType max = std::numeric_limits<uint8_t>::max();
217 764 : if (precision < min or precision > max) {
218 : auto message =
219 0 : (boost::format(
220 : "Precision value (%d) is out of allowed range [%d; %d]")
221 0 : % precision % min % max)
222 0 : .str();
223 0 : reason.second.push_back(std::move(message));
224 0 : }
225 764 : }
226 :
227 : void FieldValidator::validateRolePermission(
228 : ReasonsGroupType &reason,
229 : const interface::permissions::Role &permission) const {
230 23377 : if (not isValid(permission)) {
231 0 : reason.second.emplace_back("Provided role permission does not exist");
232 0 : }
233 23377 : }
234 :
235 : void FieldValidator::validateGrantablePermission(
236 : ReasonsGroupType &reason,
237 : const interface::permissions::Grantable &permission) const {
238 138 : if (not isValid(permission)) {
239 1 : reason.second.emplace_back("Provided grantable permission does not exist");
240 1 : }
241 138 : }
242 :
243 : void FieldValidator::validateQuorum(
244 : ReasonsGroupType &reason,
245 : const interface::types::QuorumType &quorum) const {
246 3929 : if (quorum == 0 or quorum > 128) {
247 6 : reason.second.emplace_back("Quorum should be within range (0, 128]");
248 6 : }
249 3929 : }
250 :
251 : void FieldValidator::validateCreatorAccountId(
252 : ReasonsGroupType &reason,
253 : const interface::types::AccountIdType &account_id) const {
254 3958 : if (not std::regex_match(account_id, account_id_regex_)) {
255 : auto message =
256 23 : (boost::format("Wrongly formed creator_account_id, passed value: "
257 : "'%s'. Field should match regex '%s'")
258 23 : % account_id % account_id_pattern_)
259 23 : .str();
260 23 : reason.second.push_back(std::move(message));
261 23 : }
262 3958 : }
263 :
264 : void FieldValidator::validateCreatedTime(
265 : ReasonsGroupType &reason,
266 : interface::types::TimestampType timestamp,
267 : interface::types::TimestampType now) const {
268 3961 : if (now + future_gap_ < timestamp) {
269 12 : auto message = (boost::format("bad timestamp: sent from future, "
270 : "timestamp: %llu, now: %llu")
271 12 : % timestamp % now)
272 12 : .str();
273 12 : reason.second.push_back(std::move(message));
274 12 : }
275 :
276 3961 : if (now > kMaxDelay + timestamp) {
277 : auto message =
278 17 : (boost::format("bad timestamp: too old, timestamp: %llu, now: %llu")
279 17 : % timestamp % now)
280 17 : .str();
281 17 : reason.second.push_back(std::move(message));
282 17 : }
283 3961 : }
284 :
285 : void FieldValidator::validateCreatedTime(
286 : ReasonsGroupType &reason,
287 : interface::types::TimestampType timestamp) const {
288 2233 : validateCreatedTime(reason, timestamp, time_provider_());
289 2233 : }
290 :
291 : void FieldValidator::validateCounter(
292 : ReasonsGroupType &reason,
293 : const interface::types::CounterType &counter) const {
294 215 : if (counter <= 0) {
295 : auto message =
296 15 : (boost::format("Counter should be > 0, passed value: %d") % counter)
297 15 : .str();
298 15 : reason.second.push_back(message);
299 15 : }
300 215 : }
301 :
302 : void FieldValidator::validateSignatures(
303 : ReasonsGroupType &reason,
304 : const interface::types::SignatureRangeType &signatures,
305 : const crypto::Blob &source) const {
306 3209 : if (boost::empty(signatures)) {
307 5 : reason.second.emplace_back("Signatures cannot be empty");
308 5 : }
309 6425 : for (const auto &signature : signatures) {
310 3216 : const auto &sign = signature.signedData();
311 3216 : const auto &pkey = signature.publicKey();
312 3216 : bool is_valid = true;
313 :
314 3216 : if (sign.blob().size() != signature_size) {
315 4 : reason.second.push_back(
316 4 : (boost::format("Invalid signature: %s") % sign.hex()).str());
317 4 : is_valid = false;
318 4 : }
319 :
320 3216 : if (pkey.blob().size() != public_key_size) {
321 5 : reason.second.push_back(
322 5 : (boost::format("Invalid pubkey: %s") % pkey.hex()).str());
323 5 : is_valid = false;
324 5 : }
325 :
326 3216 : if (is_valid
327 3216 : && not shared_model::crypto::CryptoVerifier<>::verify(
328 3201 : sign, source, pkey)) {
329 5 : reason.second.push_back((boost::format("Wrong signature [%s;%s]")
330 5 : % sign.hex() % pkey.hex())
331 5 : .str());
332 5 : }
333 : }
334 3209 : }
335 :
336 : void FieldValidator::validateQueryPayloadMeta(
337 : ReasonsGroupType &reason,
338 1 : const interface::QueryPayloadMeta &meta) const {}
339 :
340 : void FieldValidator::validateDescription(
341 : shared_model::validation::ReasonsGroupType &reason,
342 : const shared_model::interface::types::DescriptionType &description)
343 : const {
344 295 : if (description.size() > description_size) {
345 2 : reason.second.push_back(
346 2 : (boost::format("Description size should be less or equal '%d'")
347 2 : % description_size)
348 2 : .str());
349 2 : }
350 295 : }
351 : void FieldValidator::validateBatchMeta(
352 : shared_model::validation::ReasonsGroupType &reason,
353 91 : const interface::BatchMeta &batch_meta) const {}
354 :
355 : void FieldValidator::validateHeight(
356 : shared_model::validation::ReasonsGroupType &reason,
357 : const shared_model::interface::types::HeightType &height) const {
358 1698 : if (height <= 0) {
359 : auto message =
360 0 : (boost::format("Height should be > 0, passed value: %d") % height)
361 0 : .str();
362 0 : reason.second.push_back(message);
363 0 : }
364 1698 : }
365 :
366 : void FieldValidator::validateHash(ReasonsGroupType &reason,
367 : const crypto::Hash &hash) const {
368 300 : if (hash.size() != hash_size) {
369 1 : reason.second.push_back(
370 1 : (boost::format("Hash has invalid size: %d") % hash.size()).str());
371 1 : }
372 300 : }
373 :
374 : boost::optional<ConcreteReasonType> validatePubkey(
375 : const interface::types::PubkeyType &pubkey) {
376 10610 : if (pubkey.blob().size() != FieldValidator::public_key_size) {
377 16 : return (boost::format("Public key has wrong size, passed size: "
378 : "%d. Expected size: %d")
379 16 : % pubkey.blob().size() % FieldValidator::public_key_size)
380 16 : .str();
381 : }
382 10594 : return boost::none;
383 10610 : }
384 :
385 : } // namespace validation
386 : } // namespace shared_model
|