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