xrpld
Loading...
Searching...
No Matches
libxrpl/server/Manifest.cpp
1#include <xrpl/server/Manifest.h>
2
3#include <xrpl/basics/Blob.h>
4#include <xrpl/basics/Log.h>
5#include <xrpl/basics/Slice.h>
6#include <xrpl/basics/StringUtilities.h>
7#include <xrpl/basics/base64.h>
8#include <xrpl/basics/base_uint.h>
9#include <xrpl/basics/contract.h>
10#include <xrpl/beast/utility/Journal.h>
11#include <xrpl/beast/utility/instrumentation.h>
12#include <xrpl/json/json_reader.h>
13#include <xrpl/json/json_value.h>
14#include <xrpl/protocol/HashPrefix.h>
15#include <xrpl/protocol/PublicKey.h>
16#include <xrpl/protocol/SField.h>
17#include <xrpl/protocol/SOTemplate.h>
18#include <xrpl/protocol/STExchange.h>
19#include <xrpl/protocol/STObject.h>
20#include <xrpl/protocol/Serializer.h>
21#include <xrpl/protocol/Sign.h>
22#include <xrpl/protocol/tokens.h>
23#include <xrpl/rdb/DatabaseCon.h>
24#include <xrpl/server/Wallet.h>
25
26#include <boost/algorithm/string/trim.hpp>
27
28#include <cstddef>
29#include <cstdint>
30#include <exception>
31#include <functional>
32#include <limits>
33#include <mutex>
34#include <numeric>
35#include <optional>
36#include <shared_mutex>
37#include <stdexcept>
38#include <string>
39#include <utility>
40#include <vector>
41
42namespace xrpl {
43
44std::string
46{
47 auto const mk = toBase58(TokenType::NodePublic, m.masterKey);
48
49 if (m.revoked())
50 return "Revocation Manifest " + mk;
51
52 if (!m.signingKey)
53 Throw<std::runtime_error>("No SigningKey in manifest " + mk);
54
55 return "Manifest " + mk + " (" + std::to_string(m.sequence) + ": " +
57}
58
61{
62 if (s.empty())
63 return std::nullopt;
64
65 static SOTemplate const kManifestFormat{
66 // A manifest must include:
67 // - the master public key
68 {sfPublicKey, SoeRequired},
69
70 // - a signature with that public key
71 {sfMasterSignature, SoeRequired},
72
73 // - a sequence number
74 {sfSequence, SoeRequired},
75
76 // It may, optionally, contain:
77 // - a version number which defaults to 0
78 {sfVersion, SoeDefault},
79
80 // - a domain name
81 {sfDomain, SoeOptional},
82
83 // - an ephemeral signing key that can be changed as necessary
84 {sfSigningPubKey, SoeOptional},
85
86 // - a signature using the ephemeral signing key, if it is present
87 {sfSignature, SoeOptional},
88 };
89
90 try
91 {
92 SerialIter sit{s};
93 STObject st{sit, sfGeneric};
94
95 st.applyTemplate(kManifestFormat);
96
97 // We only understand "version 0" manifests at this time:
98 if (st.isFieldPresent(sfVersion) && st.getFieldU16(sfVersion) != 0)
99 return std::nullopt;
100
101 auto const pk = st.getFieldVL(sfPublicKey);
102
103 if (!publicKeyType(makeSlice(pk)))
104 return std::nullopt;
105
106 PublicKey const masterKey = PublicKey(makeSlice(pk));
107 std::uint32_t const seq = st.getFieldU32(sfSequence);
108
109 std::string domain;
110
111 std::optional<PublicKey> signingKey;
112
113 if (st.isFieldPresent(sfDomain))
114 {
115 auto const d = st.getFieldVL(sfDomain);
116
117 domain.assign(reinterpret_cast<char const*>(d.data()), d.size());
118
119 if (!isProperlyFormedTomlDomain(domain))
120 return std::nullopt;
121 }
122
123 bool const hasEphemeralKey = st.isFieldPresent(sfSigningPubKey);
124 bool const hasEphemeralSig = st.isFieldPresent(sfSignature);
125
126 if (Manifest::revoked(seq))
127 {
128 // Revocation manifests should not specify a new signing key
129 // or a signing key signature.
130 if (hasEphemeralKey)
131 return std::nullopt;
132
133 if (hasEphemeralSig)
134 return std::nullopt;
135 }
136 else
137 {
138 // Regular manifests should contain a signing key and an
139 // associated signature.
140 if (!hasEphemeralKey)
141 return std::nullopt;
142
143 if (!hasEphemeralSig)
144 return std::nullopt;
145
146 auto const spk = st.getFieldVL(sfSigningPubKey);
147
148 if (!publicKeyType(makeSlice(spk)))
149 return std::nullopt;
150
151 signingKey.emplace(makeSlice(spk));
152
153 // The signing and master keys can't be the same
154 if (*signingKey == masterKey)
155 return std::nullopt;
156 }
157
158 std::string const serialized(reinterpret_cast<char const*>(s.data()), s.size());
159
160 // If the manifest is revoked, then the signingKey will be unseated
161 return Manifest(serialized, masterKey, signingKey, seq, domain);
162 }
163 catch (std::exception const& ex)
164 {
165 JLOG(journal.error()) << "Exception in " << __func__ << ": " << ex.what();
166 return std::nullopt;
167 }
168}
169
170template <class Stream>
171Stream&
172logMftAct(Stream& s, std::string const& action, PublicKey const& pk, std::uint32_t seq)
173{
174 s << "Manifest: " << action << ";Pk: " << toBase58(TokenType::NodePublic, pk) << ";Seq: " << seq
175 << ";";
176 return s;
177}
178
179template <class Stream>
180Stream&
182 Stream& s,
183 std::string const& action,
184 PublicKey const& pk,
185 std::uint32_t seq,
186 std::uint32_t oldSeq)
187{
188 s << "Manifest: " << action << ";Pk: " << toBase58(TokenType::NodePublic, pk) << ";Seq: " << seq
189 << ";OldSeq: " << oldSeq << ";";
190 return s;
191}
192
193bool
195{
197 SerialIter sit(serialized.data(), serialized.size());
198 st.set(sit);
199
200 // The manifest must either have a signing key or be revoked. This check
201 // prevents us from accessing an unseated signingKey in the next check.
202 if (!revoked() && !signingKey)
203 return false;
204
205 // Signing key and signature are not required for
206 // master key revocations
208 return false;
209
210 return xrpl::verify(st, HashPrefix::Manifest, masterKey, sfMasterSignature);
211}
212
215{
217 SerialIter sit(serialized.data(), serialized.size());
218 st.set(sit);
219 return st.getHash(HashPrefix::Manifest);
220}
221
222bool
224{
225 /*
226 The maximum possible sequence number means that the master key
227 has been revoked.
228 */
229 return revoked(sequence);
230}
231
232bool
234{
235 // The maximum possible sequence number means that the master key has
236 // been revoked.
238}
239
242{
244 SerialIter sit(serialized.data(), serialized.size());
245 st.set(sit);
246 if (!get(st, sfSignature))
247 return std::nullopt;
248 return st.getFieldVL(sfSignature);
249}
250
251Blob
253{
255 SerialIter sit(serialized.data(), serialized.size());
256 st.set(sit);
257 return st.getFieldVL(sfMasterSignature);
258}
259
262{
263 try
264 {
265 std::string tokenStr;
266
267 tokenStr.reserve(
269 blob.cbegin(),
270 blob.cend(),
271 std::size_t(0),
272 [](std::size_t init, std::string const& s) { return init + s.size(); }));
273
274 for (auto const& line : blob)
275 tokenStr += boost::algorithm::trim_copy(line);
276
277 tokenStr = base64Decode(tokenStr);
278
279 json::Reader r;
280 json::Value token;
281
282 if (r.parse(tokenStr, token))
283 {
284 auto const m = token.get("manifest", json::Value{});
285 auto const k = token.get("validation_secret_key", json::Value{});
286
287 if (m.isString() && k.isString())
288 {
289 auto const key = strUnHex(k.asString());
290
291 if (key && key->size() == 32)
292 {
293 return ValidatorToken{
294 .manifest = m.asString(), .validationSecret = makeSlice(*key)};
295 }
296 }
297 }
298
299 return std::nullopt;
300 }
301 catch (std::exception const& ex)
302 {
303 JLOG(journal.error()) << "Exception in " << __func__ << ": " << ex.what();
304 return std::nullopt;
305 }
306}
307
310{
311 std::shared_lock const lock{mutex_};
312 auto const iter = map_.find(pk);
313
314 if (iter != map_.end() && !iter->second.revoked())
315 return iter->second.signingKey;
316
317 return pk;
318}
319
322{
323 std::shared_lock const lock{mutex_};
324
325 if (auto const iter = signingToMasterKeys_.find(pk); iter != signingToMasterKeys_.end())
326 return iter->second;
327
328 return pk;
329}
330
333{
334 std::shared_lock const lock{mutex_};
335 auto const iter = map_.find(pk);
336
337 if (iter != map_.end() && !iter->second.revoked())
338 return iter->second.sequence;
339
340 return std::nullopt;
341}
342
345{
346 std::shared_lock const lock{mutex_};
347 auto const iter = map_.find(pk);
348
349 if (iter != map_.end() && !iter->second.revoked())
350 return iter->second.domain;
351
352 return std::nullopt;
353}
354
357{
358 std::shared_lock const lock{mutex_};
359 auto const iter = map_.find(pk);
360
361 if (iter != map_.end() && !iter->second.revoked())
362 return iter->second.serialized;
363
364 return std::nullopt;
365}
366
367bool
369{
370 std::shared_lock const lock{mutex_};
371 auto const iter = map_.find(pk);
372
373 if (iter != map_.end())
374 return iter->second.revoked();
375
376 return false;
377}
378
381{
382 // Check the manifest against the conditions that do not require a
383 // `unique_lock` (write lock) on the `mutex_`. Since the signature can be
384 // relatively expensive, the `checkSignature` parameter determines if the
385 // signature should be checked. Since `prewriteCheck` is run twice (see
386 // comment below), `checkSignature` only needs to be set to true on the
387 // first run.
388 auto prewriteCheck = [this, &m](auto const& iter, bool checkSignature, auto const& lock)
390 XRPL_ASSERT(lock.owns_lock(), "xrpl::ManifestCache::applyManifest::prewriteCheck : locked");
391 (void)lock; // not used. parameter is present to ensure the mutex is
392 // locked when the lambda is called.
393 if (iter != map_.end() && m.sequence <= iter->second.sequence)
394 {
395 // We received a manifest whose sequence number is not strictly
396 // greater than the one we already know about. This can happen in
397 // several cases including when we receive manifests from a peer who
398 // doesn't have the latest data.
399 if (auto stream = j_.debug())
400 logMftAct(stream, "Stale", m.masterKey, m.sequence, iter->second.sequence);
402 }
403
404 if (checkSignature && !m.verify())
405 {
406 if (auto stream = j_.warn())
407 logMftAct(stream, "Invalid", m.masterKey, m.sequence);
409 }
410
411 // If the master key associated with a manifest is or might be
412 // compromised and is, therefore, no longer trustworthy.
413 //
414 // A manifest revocation essentially marks a manifest as compromised. By
415 // setting the sequence number to the highest value possible, the
416 // manifest is effectively neutered and cannot be superseded by a forged
417 // one.
418 bool const revoked = m.revoked();
419
420 if (auto stream = j_.warn(); stream && revoked)
421 logMftAct(stream, "Revoked", m.masterKey, m.sequence);
422
423 // Sanity check: the master key of this manifest should not be used as
424 // the ephemeral key of another manifest:
425 if (auto const x = signingToMasterKeys_.find(m.masterKey); x != signingToMasterKeys_.end())
426 {
427 JLOG(j_.warn()) << to_string(m) << ": Master key already used as ephemeral key for "
428 << toBase58(TokenType::NodePublic, x->second);
429
431 }
432
433 if (!revoked)
434 {
435 if (!m.signingKey)
436 {
437 JLOG(j_.warn()) << to_string(m)
438 << ": is not revoked and the manifest has no "
439 "signing key. Hence, the manifest is "
440 "invalid";
442 }
443
444 // Sanity check: the ephemeral key of this manifest should not be
445 // used as the master or ephemeral key of another manifest:
446 if (auto const x = signingToMasterKeys_.find(*m.signingKey);
447 x != signingToMasterKeys_.end())
448 {
449 JLOG(j_.warn()) << to_string(m)
450 << ": Ephemeral key already used as ephemeral key for "
451 << toBase58(TokenType::NodePublic, x->second);
452
454 }
455
456 if (auto const x = map_.find(*m.signingKey); x != map_.end())
457 {
458 JLOG(j_.warn()) << to_string(m) << ": Ephemeral key used as master key for "
459 << to_string(x->second);
460
462 }
463 }
464
465 return std::nullopt;
466 };
467
468 {
469 std::shared_lock const sl{mutex_};
470 if (auto d = prewriteCheck(map_.find(m.masterKey), /*checkSig*/ true, sl))
471 return *d;
472 }
473
474 std::unique_lock const sl{mutex_};
475 auto const iter = map_.find(m.masterKey);
476 // Since we released the previously held read lock, it's possible that the
477 // collections have been written to. This means we need to run
478 // `prewriteCheck` again. This re-does work, but `prewriteCheck` is
479 // relatively inexpensive to run, and doing it this way allows us to run
480 // `prewriteCheck` under a `shared_lock` above.
481 // Note, the signature has already been checked above, so it
482 // doesn't need to happen again (signature checks are somewhat expensive).
483 // Note: It's a mistake to use an upgradable lock. This is a recipe for
484 // deadlock.
485 if (auto d = prewriteCheck(iter, /*checkSig*/ false, sl))
486 return *d;
487
488 bool const revoked = m.revoked();
489 // This is the first manifest we are seeing for a master key. This should
490 // only ever happen once per validator run.
491 if (iter == map_.end())
492 {
493 if (auto stream = j_.info())
494 logMftAct(stream, "AcceptedNew", m.masterKey, m.sequence);
495
496 if (!revoked)
497 {
498 signingToMasterKeys_.emplace(
499 *m.signingKey, m.masterKey); // NOLINT(bugprone-unchecked-optional-access)
500 // non-revoked manifest always has signingKey
501 }
502
503 auto masterKey = m.masterKey;
504 map_.emplace(std::move(masterKey), std::move(m));
505
506 // Something has changed. Keep track of it.
507 seq_++;
508
510 }
511
512 // An ephemeral key was revoked and superseded by a new key. This is
513 // expected, but should happen infrequently.
514 if (auto stream = j_.info())
515 logMftAct(stream, "AcceptedUpdate", m.masterKey, m.sequence, iter->second.sequence);
516
518 *iter->second.signingKey); // NOLINT(bugprone-unchecked-optional-access) prewriteCheck
519 // ensures old manifest is not revoked
520
521 if (!revoked)
522 {
523 signingToMasterKeys_.emplace(
524 *m.signingKey, m.masterKey); // NOLINT(bugprone-unchecked-optional-access) non-revoked
525 // manifest always has signingKey
526 }
527
528 iter->second = std::move(m);
529
530 // Something has changed. Keep track of it.
531 seq_++;
532
534}
535
536void
538{
539 auto db = dbCon.checkoutDb();
540 xrpl::getManifests(*db, dbTable, *this, j_);
541}
542
543bool
545 DatabaseCon& dbCon,
546 std::string const& dbTable,
547 std::string const& configManifest,
548 std::vector<std::string> const& configRevocation)
549{
550 load(dbCon, dbTable);
551
552 if (!configManifest.empty())
553 {
554 auto mo = deserializeManifest(base64Decode(configManifest));
555 if (!mo)
556 {
557 JLOG(j_.error()) << "Malformed validator_token in config";
558 return false;
559 }
560
561 if (mo->revoked())
562 {
563 JLOG(j_.warn()) << "Configured manifest revokes public key";
564 }
565
566 if (applyManifest(std::move(*mo)) == ManifestDisposition::Invalid)
567 {
568 JLOG(j_.error()) << "Manifest in config was rejected";
569 return false;
570 }
571 }
572
573 if (!configRevocation.empty())
574 {
575 std::string revocationStr;
576 revocationStr.reserve(
578 configRevocation.cbegin(),
579 configRevocation.cend(),
580 std::size_t(0),
581 [](std::size_t init, std::string const& s) { return init + s.size(); }));
582
583 for (auto const& line : configRevocation)
584 revocationStr += boost::algorithm::trim_copy(line);
585
586 auto mo = deserializeManifest(base64Decode(revocationStr));
587
588 if (!mo || !mo->revoked() || applyManifest(std::move(*mo)) == ManifestDisposition::Invalid)
589 {
590 JLOG(j_.error()) << "Invalid validator key revocation in config";
591 return false;
592 }
593 }
594
595 return true;
596}
597
598void
600 DatabaseCon& dbCon,
601 std::string const& dbTable,
602 std::function<bool(PublicKey const&)> const& isTrusted)
603{
604 std::shared_lock const lock{mutex_};
605 auto db = dbCon.checkoutDb();
606
607 saveManifests(*db, dbTable, isTrusted, map_, j_);
608}
609} // namespace xrpl
T accumulate(T... args)
T assign(T... args)
T cbegin(T... args)
A generic endpoint for log messages.
Definition Journal.h:38
Stream error() const
Definition Journal.h:315
Unserialize a JSON document into a Value.
Definition json_reader.h:17
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:130
Value get(UInt index, Value const &defaultValue) const
If the array contains at least index+1 elements, returns the element value, otherwise returns default...
LockedSociSession checkoutDb()
std::atomic< std::uint32_t > seq_
Definition Manifest.h:247
std::shared_mutex mutex_
Definition Manifest.h:239
bool load(DatabaseCon &dbCon, std::string const &dbTable, std::string const &configManifest, std::vector< std::string > const &configRevocation)
Populate manifest cache with manifests in database and config.
std::optional< PublicKey > getSigningKey(PublicKey const &pk) const
Returns master key's current signing key.
ManifestDisposition applyManifest(Manifest m)
Add manifest to cache.
std::optional< std::string > getDomain(PublicKey const &pk) const
Returns domain claimed by a given public key.
PublicKey getMasterKey(PublicKey const &pk) const
Returns ephemeral signing key's master public key.
hash_map< PublicKey, PublicKey > signingToMasterKeys_
Master public keys stored by current ephemeral public key.
Definition Manifest.h:245
std::optional< std::string > getManifest(PublicKey const &pk) const
Returns manifest corresponding to a given public key.
hash_map< PublicKey, Manifest > map_
Active manifests stored by master public key.
Definition Manifest.h:242
std::optional< std::uint32_t > getSequence(PublicKey const &pk) const
Returns master key's current manifest sequence.
void save(DatabaseCon &dbCon, std::string const &dbTable, std::function< bool(PublicKey const &)> const &isTrusted)
Save cached manifests to database.
beast::Journal j_
Definition Manifest.h:238
bool revoked(PublicKey const &pk) const
Returns true if master key has been revoked in a manifest.
A public key.
Definition PublicKey.h:42
Defines the fields and their attributes within a STObject.
Definition SOTemplate.h:96
Blob getFieldVL(SField const &field) const
Definition STObject.cpp:639
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:591
void applyTemplate(SOTemplate const &type)
Definition STObject.cpp:155
uint256 getHash(HashPrefix prefix) const
Definition STObject.cpp:365
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:454
void set(SOTemplate const &)
Definition STObject.cpp:135
std::uint16_t getFieldU16(SField const &field) const
Definition STObject.cpp:585
An immutable linear range of bytes.
Definition Slice.h:26
bool empty() const noexcept
Return true if the byte range is empty.
Definition Slice.h:50
std::uint8_t const * data() const noexcept
Return a pointer to beginning of the storage.
Definition Slice.h:78
std::size_t size() const noexcept
Returns the number of bytes in the storage.
Definition Slice.h:61
T emplace(T... args)
T empty(T... args)
T cend(T... args)
T max(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
Stream & logMftAct(Stream &s, std::string const &action, PublicKey const &pk, std::uint32_t seq)
std::string base64Decode(std::string_view data)
void saveManifests(soci::session &session, std::string const &dbTable, std::function< bool(PublicKey const &)> const &isTrusted, hash_map< PublicKey, Manifest > const &map, beast::Journal j)
saveManifests Saves all given manifests to the database.
Definition Wallet.cpp:99
bool isProperlyFormedTomlDomain(std::string_view domain)
Determines if the given string looks like a TOML-file hosting domain.
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
@ SoeDefault
Definition SOTemplate.h:20
@ SoeOptional
Definition SOTemplate.h:19
@ SoeRequired
Definition SOTemplate.h:18
bool verify(PublicKey const &publicKey, Slice const &m, Slice const &sig) noexcept
Verify a signature on a message.
SField const sfGeneric
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:93
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
std::optional< Manifest > deserializeManifest(Slice s, beast::Journal journal)
Constructs Manifest from serialized string.
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
@ Manifest
Manifest.
Definition HashPrefix.h:63
void getManifests(soci::session &session, std::string const &dbTable, ManifestCache &cache, beast::Journal j)
getManifests Loads a manifest from the wallet database and stores it in the cache.
Definition Wallet.cpp:55
std::vector< unsigned char > Blob
Storage for linear binary data.
Definition Blob.h:10
BaseUInt< 256 > uint256
Definition base_uint.h:562
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:49
std::enable_if_t< std::is_same_v< T, char >||std::is_same_v< T, unsigned char >, Slice > makeSlice(std::array< T, N > const &a)
Definition Slice.h:215
std::optional< ValidatorToken > loadValidatorToken(std::vector< std::string > const &blob, beast::Journal journal=beast::Journal(beast::Journal::getNullSink()))
ManifestDisposition
Definition Manifest.h:195
@ BadMasterKey
The master key is not acceptable to us.
Definition Manifest.h:203
@ Accepted
Manifest is valid.
Definition Manifest.h:197
@ Invalid
Timely, but invalid signature.
Definition Manifest.h:209
@ BadEphemeralKey
The ephemeral key is not acceptable to us.
Definition Manifest.h:206
@ Stale
Sequence is too old.
Definition Manifest.h:200
T reserve(T... args)
static bool revoked(std::uint32_t sequence)
Returns true if manifest revokes master key.
PublicKey masterKey
The master key associated with this manifest.
Definition Manifest.h:67
std::string serialized
The manifest in serialized form.
Definition Manifest.h:64
Blob getMasterSignature() const
Returns manifest master key signature.
std::optional< Blob > getSignature() const
Returns manifest signature.
std::optional< PublicKey > signingKey
The ephemeral key associated with this manifest.
Definition Manifest.h:73
std::uint32_t sequence
The sequence number of this manifest.
Definition Manifest.h:76
bool revoked() const
Returns true if manifest revokes master key.
uint256 hash() const
Returns hash of serialized manifest data.
bool verify() const
Returns true if manifest signature is valid.
T to_string(T... args)
T what(T... args)