rippled
Loading...
Searching...
No Matches
libxrpl/server/Manifest.cpp
1#include <xrpl/basics/Log.h>
2#include <xrpl/basics/StringUtilities.h>
3#include <xrpl/basics/base64.h>
4#include <xrpl/json/json_reader.h>
5#include <xrpl/protocol/PublicKey.h>
6#include <xrpl/protocol/Sign.h>
7#include <xrpl/rdb/DatabaseCon.h>
8#include <xrpl/server/Manifest.h>
9#include <xrpl/server/Wallet.h>
10
11#include <boost/algorithm/string/trim.hpp>
12
13#include <numeric>
14#include <stdexcept>
15
16namespace xrpl {
17
20{
21 auto const mk = toBase58(TokenType::NodePublic, m.masterKey);
22
23 if (m.revoked())
24 return "Revocation Manifest " + mk;
25
26 if (!m.signingKey)
27 Throw<std::runtime_error>("No SigningKey in manifest " + mk);
28
29 return "Manifest " + mk + " (" + std::to_string(m.sequence) + ": " +
31}
32
35{
36 if (s.empty())
37 return std::nullopt;
38
39 static SOTemplate const manifestFormat{
40 // A manifest must include:
41 // - the master public key
42 {sfPublicKey, soeREQUIRED},
43
44 // - a signature with that public key
45 {sfMasterSignature, soeREQUIRED},
46
47 // - a sequence number
48 {sfSequence, soeREQUIRED},
49
50 // It may, optionally, contain:
51 // - a version number which defaults to 0
52 {sfVersion, soeDEFAULT},
53
54 // - a domain name
55 {sfDomain, soeOPTIONAL},
56
57 // - an ephemeral signing key that can be changed as necessary
58 {sfSigningPubKey, soeOPTIONAL},
59
60 // - a signature using the ephemeral signing key, if it is present
61 {sfSignature, soeOPTIONAL},
62 };
63
64 try
65 {
66 SerialIter sit{s};
67 STObject st{sit, sfGeneric};
68
69 st.applyTemplate(manifestFormat);
70
71 // We only understand "version 0" manifests at this time:
72 if (st.isFieldPresent(sfVersion) && st.getFieldU16(sfVersion) != 0)
73 return std::nullopt;
74
75 auto const pk = st.getFieldVL(sfPublicKey);
76
77 if (!publicKeyType(makeSlice(pk)))
78 return std::nullopt;
79
80 PublicKey const masterKey = PublicKey(makeSlice(pk));
81 std::uint32_t const seq = st.getFieldU32(sfSequence);
82
83 std::string domain;
84
85 std::optional<PublicKey> signingKey;
86
87 if (st.isFieldPresent(sfDomain))
88 {
89 auto const d = st.getFieldVL(sfDomain);
90
91 domain.assign(reinterpret_cast<char const*>(d.data()), d.size());
92
93 if (!isProperlyFormedTomlDomain(domain))
94 return std::nullopt;
95 }
96
97 bool const hasEphemeralKey = st.isFieldPresent(sfSigningPubKey);
98 bool const hasEphemeralSig = st.isFieldPresent(sfSignature);
99
100 if (Manifest::revoked(seq))
101 {
102 // Revocation manifests should not specify a new signing key
103 // or a signing key signature.
104 if (hasEphemeralKey)
105 return std::nullopt;
106
107 if (hasEphemeralSig)
108 return std::nullopt;
109 }
110 else
111 {
112 // Regular manifests should contain a signing key and an
113 // associated signature.
114 if (!hasEphemeralKey)
115 return std::nullopt;
116
117 if (!hasEphemeralSig)
118 return std::nullopt;
119
120 auto const spk = st.getFieldVL(sfSigningPubKey);
121
122 if (!publicKeyType(makeSlice(spk)))
123 return std::nullopt;
124
125 signingKey.emplace(makeSlice(spk));
126
127 // The signing and master keys can't be the same
128 if (*signingKey == masterKey)
129 return std::nullopt;
130 }
131
132 std::string const serialized(reinterpret_cast<char const*>(s.data()), s.size());
133
134 // If the manifest is revoked, then the signingKey will be unseated
135 return Manifest(serialized, masterKey, signingKey, seq, domain);
136 }
137 catch (std::exception const& ex)
138 {
139 JLOG(journal.error()) << "Exception in " << __func__ << ": " << ex.what();
140 return std::nullopt;
141 }
142}
143
144template <class Stream>
145Stream&
146logMftAct(Stream& s, std::string const& action, PublicKey const& pk, std::uint32_t seq)
147{
148 s << "Manifest: " << action << ";Pk: " << toBase58(TokenType::NodePublic, pk) << ";Seq: " << seq
149 << ";";
150 return s;
151}
152
153template <class Stream>
154Stream&
156 Stream& s,
157 std::string const& action,
158 PublicKey const& pk,
159 std::uint32_t seq,
160 std::uint32_t oldSeq)
161{
162 s << "Manifest: " << action << ";Pk: " << toBase58(TokenType::NodePublic, pk) << ";Seq: " << seq
163 << ";OldSeq: " << oldSeq << ";";
164 return s;
165}
166
167bool
169{
172 st.set(sit);
173
174 // The manifest must either have a signing key or be revoked. This check
175 // prevents us from accessing an unseated signingKey in the next check.
176 if (!revoked() && !signingKey)
177 return false;
178
179 // Signing key and signature are not required for
180 // master key revocations
182 return false;
183
184 return xrpl::verify(st, HashPrefix::manifest, masterKey, sfMasterSignature);
185}
186
189{
192 st.set(sit);
193 return st.getHash(HashPrefix::manifest);
194}
195
196bool
198{
199 /*
200 The maximum possible sequence number means that the master key
201 has been revoked.
202 */
203 return revoked(sequence);
204}
205
206bool
208{
209 // The maximum possible sequence number means that the master key has
210 // been revoked.
212}
213
216{
219 st.set(sit);
220 if (!get(st, sfSignature))
221 return std::nullopt;
222 return st.getFieldVL(sfSignature);
223}
224
225Blob
227{
230 st.set(sit);
231 return st.getFieldVL(sfMasterSignature);
232}
233
236{
237 try
238 {
239 std::string tokenStr;
240
241 tokenStr.reserve(
243 blob.cbegin(),
244 blob.cend(),
245 std::size_t(0),
246 [](std::size_t init, std::string const& s) { return init + s.size(); }));
247
248 for (auto const& line : blob)
249 tokenStr += boost::algorithm::trim_copy(line);
250
251 tokenStr = base64_decode(tokenStr);
252
253 Json::Reader r;
254 Json::Value token;
255
256 if (r.parse(tokenStr, token))
257 {
258 auto const m = token.get("manifest", Json::Value{});
259 auto const k = token.get("validation_secret_key", Json::Value{});
260
261 if (m.isString() && k.isString())
262 {
263 auto const key = strUnHex(k.asString());
264
265 if (key && key->size() == 32)
266 return ValidatorToken{m.asString(), makeSlice(*key)};
267 }
268 }
269
270 return std::nullopt;
271 }
272 catch (std::exception const& ex)
273 {
274 JLOG(journal.error()) << "Exception in " << __func__ << ": " << ex.what();
275 return std::nullopt;
276 }
277}
278
281{
282 std::shared_lock const lock{mutex_};
283 auto const iter = map_.find(pk);
284
285 if (iter != map_.end() && !iter->second.revoked())
286 return iter->second.signingKey;
287
288 return pk;
289}
290
293{
294 std::shared_lock const lock{mutex_};
295
296 if (auto const iter = signingToMasterKeys_.find(pk); iter != signingToMasterKeys_.end())
297 return iter->second;
298
299 return pk;
300}
301
304{
305 std::shared_lock const lock{mutex_};
306 auto const iter = map_.find(pk);
307
308 if (iter != map_.end() && !iter->second.revoked())
309 return iter->second.sequence;
310
311 return std::nullopt;
312}
313
316{
317 std::shared_lock const lock{mutex_};
318 auto const iter = map_.find(pk);
319
320 if (iter != map_.end() && !iter->second.revoked())
321 return iter->second.domain;
322
323 return std::nullopt;
324}
325
328{
329 std::shared_lock const lock{mutex_};
330 auto const iter = map_.find(pk);
331
332 if (iter != map_.end() && !iter->second.revoked())
333 return iter->second.serialized;
334
335 return std::nullopt;
336}
337
338bool
340{
341 std::shared_lock const lock{mutex_};
342 auto const iter = map_.find(pk);
343
344 if (iter != map_.end())
345 return iter->second.revoked();
346
347 return false;
348}
349
352{
353 // Check the manifest against the conditions that do not require a
354 // `unique_lock` (write lock) on the `mutex_`. Since the signature can be
355 // relatively expensive, the `checkSignature` parameter determines if the
356 // signature should be checked. Since `prewriteCheck` is run twice (see
357 // comment below), `checkSignature` only needs to be set to true on the
358 // first run.
359 auto prewriteCheck = [this, &m](auto const& iter, bool checkSignature, auto const& lock)
361 XRPL_ASSERT(lock.owns_lock(), "xrpl::ManifestCache::applyManifest::prewriteCheck : locked");
362 (void)lock; // not used. parameter is present to ensure the mutex is
363 // locked when the lambda is called.
364 if (iter != map_.end() && m.sequence <= iter->second.sequence)
365 {
366 // We received a manifest whose sequence number is not strictly
367 // greater than the one we already know about. This can happen in
368 // several cases including when we receive manifests from a peer who
369 // doesn't have the latest data.
370 if (auto stream = j_.debug())
371 logMftAct(stream, "Stale", m.masterKey, m.sequence, iter->second.sequence);
373 }
374
375 if (checkSignature && !m.verify())
376 {
377 if (auto stream = j_.warn())
378 logMftAct(stream, "Invalid", m.masterKey, m.sequence);
380 }
381
382 // If the master key associated with a manifest is or might be
383 // compromised and is, therefore, no longer trustworthy.
384 //
385 // A manifest revocation essentially marks a manifest as compromised. By
386 // setting the sequence number to the highest value possible, the
387 // manifest is effectively neutered and cannot be superseded by a forged
388 // one.
389 bool const revoked = m.revoked();
390
391 if (auto stream = j_.warn(); stream && revoked)
392 logMftAct(stream, "Revoked", m.masterKey, m.sequence);
393
394 // Sanity check: the master key of this manifest should not be used as
395 // the ephemeral key of another manifest:
396 if (auto const x = signingToMasterKeys_.find(m.masterKey); x != signingToMasterKeys_.end())
397 {
398 JLOG(j_.warn()) << to_string(m) << ": Master key already used as ephemeral key for "
399 << toBase58(TokenType::NodePublic, x->second);
400
402 }
403
404 if (!revoked)
405 {
406 if (!m.signingKey)
407 {
408 JLOG(j_.warn()) << to_string(m)
409 << ": is not revoked and the manifest has no "
410 "signing key. Hence, the manifest is "
411 "invalid";
413 }
414
415 // Sanity check: the ephemeral key of this manifest should not be
416 // used as the master or ephemeral key of another manifest:
417 if (auto const x = signingToMasterKeys_.find(*m.signingKey);
418 x != signingToMasterKeys_.end())
419 {
420 JLOG(j_.warn()) << to_string(m)
421 << ": Ephemeral key already used as ephemeral key for "
422 << toBase58(TokenType::NodePublic, x->second);
423
425 }
426
427 if (auto const x = map_.find(*m.signingKey); x != map_.end())
428 {
429 JLOG(j_.warn()) << to_string(m) << ": Ephemeral key used as master key for "
430 << to_string(x->second);
431
433 }
434 }
435
436 return std::nullopt;
437 };
438
439 {
440 std::shared_lock const sl{mutex_};
441 if (auto d = prewriteCheck(map_.find(m.masterKey), /*checkSig*/ true, sl))
442 return *d;
443 }
444
445 std::unique_lock const sl{mutex_};
446 auto const iter = map_.find(m.masterKey);
447 // Since we released the previously held read lock, it's possible that the
448 // collections have been written to. This means we need to run
449 // `prewriteCheck` again. This re-does work, but `prewriteCheck` is
450 // relatively inexpensive to run, and doing it this way allows us to run
451 // `prewriteCheck` under a `shared_lock` above.
452 // Note, the signature has already been checked above, so it
453 // doesn't need to happen again (signature checks are somewhat expensive).
454 // Note: It's a mistake to use an upgradable lock. This is a recipe for
455 // deadlock.
456 if (auto d = prewriteCheck(iter, /*checkSig*/ false, sl))
457 return *d;
458
459 bool const revoked = m.revoked();
460 // This is the first manifest we are seeing for a master key. This should
461 // only ever happen once per validator run.
462 if (iter == map_.end())
463 {
464 if (auto stream = j_.info())
465 logMftAct(stream, "AcceptedNew", m.masterKey, m.sequence);
466
467 if (!revoked)
469
470 auto masterKey = m.masterKey;
471 map_.emplace(std::move(masterKey), std::move(m));
472
473 // Something has changed. Keep track of it.
474 seq_++;
475
477 }
478
479 // An ephemeral key was revoked and superseded by a new key. This is
480 // expected, but should happen infrequently.
481 if (auto stream = j_.info())
482 logMftAct(stream, "AcceptedUpdate", m.masterKey, m.sequence, iter->second.sequence);
483
484 signingToMasterKeys_.erase(*iter->second.signingKey);
485
486 if (!revoked)
488
489 iter->second = std::move(m);
490
491 // Something has changed. Keep track of it.
492 seq_++;
493
495}
496
497void
499{
500 auto db = dbCon.checkoutDb();
501 xrpl::getManifests(*db, dbTable, *this, j_);
502}
503
504bool
506 DatabaseCon& dbCon,
507 std::string const& dbTable,
508 std::string const& configManifest,
509 std::vector<std::string> const& configRevocation)
510{
511 load(dbCon, dbTable);
512
513 if (!configManifest.empty())
514 {
515 auto mo = deserializeManifest(base64_decode(configManifest));
516 if (!mo)
517 {
518 JLOG(j_.error()) << "Malformed validator_token in config";
519 return false;
520 }
521
522 if (mo->revoked())
523 {
524 JLOG(j_.warn()) << "Configured manifest revokes public key";
525 }
526
527 if (applyManifest(std::move(*mo)) == ManifestDisposition::invalid)
528 {
529 JLOG(j_.error()) << "Manifest in config was rejected";
530 return false;
531 }
532 }
533
534 if (!configRevocation.empty())
535 {
536 std::string revocationStr;
537 revocationStr.reserve(
539 configRevocation.cbegin(),
540 configRevocation.cend(),
541 std::size_t(0),
542 [](std::size_t init, std::string const& s) { return init + s.size(); }));
543
544 for (auto const& line : configRevocation)
545 revocationStr += boost::algorithm::trim_copy(line);
546
547 auto mo = deserializeManifest(base64_decode(revocationStr));
548
549 if (!mo || !mo->revoked() || applyManifest(std::move(*mo)) == ManifestDisposition::invalid)
550 {
551 JLOG(j_.error()) << "Invalid validator key revocation in config";
552 return false;
553 }
554 }
555
556 return true;
557}
558
559void
561 DatabaseCon& dbCon,
562 std::string const& dbTable,
563 std::function<bool(PublicKey const&)> const& isTrusted)
564{
565 std::shared_lock const lock{mutex_};
566 auto db = dbCon.checkoutDb();
567
568 saveManifests(*db, dbTable, isTrusted, map_, j_);
569}
570} // namespace xrpl
T accumulate(T... args)
T assign(T... args)
T cbegin(T... args)
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...
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:319
Stream debug() const
Definition Journal.h:301
Stream info() const
Definition Journal.h:307
Stream warn() const
Definition Journal.h:313
LockedSociSession checkoutDb()
std::atomic< std::uint32_t > seq_
Definition Manifest.h:246
std::shared_mutex mutex_
Definition Manifest.h:238
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:244
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:241
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:237
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:92
Blob getFieldVL(SField const &field) const
Definition STObject.cpp:641
uint256 getHash(HashPrefix prefix) const
Definition STObject.cpp:367
void set(SOTemplate const &)
Definition STObject.cpp:134
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 data(T... args)
T emplace(T... args)
T empty(T... args)
T cend(T... args)
T is_same_v
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)
void getManifests(soci::session &session, std::string const &dbTable, ManifestCache &mCache, beast::Journal j)
getManifests Loads a manifest from the wallet database and stores it in the cache.
Definition Wallet.cpp:25
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:69
bool isProperlyFormedTomlDomain(std::string_view domain)
Determines if the given string looks like a TOML-file hosting domain.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
@ soeOPTIONAL
Definition SOTemplate.h:17
@ soeDEFAULT
Definition SOTemplate.h:18
@ soeREQUIRED
Definition SOTemplate.h:16
std::string base64_decode(std::string_view data)
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:92
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.
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, 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:194
@ badMasterKey
The master key is not acceptable to us.
@ stale
Sequence is too old.
@ accepted
Manifest is valid.
@ badEphemeralKey
The ephemeral key is not acceptable to us.
@ invalid
Timely, but invalid signature.
T reserve(T... args)
T size(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:66
std::string serialized
The manifest in serialized form.
Definition Manifest.h:63
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:72
std::uint32_t sequence
The sequence number of this manifest.
Definition Manifest.h:75
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)