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