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(reinterpret_cast<char const*>(s.data()), s.size());
134
135 // If the manifest is revoked, then the signingKey will be unseated
136 return Manifest(serialized, masterKey, signingKey, seq, domain);
137 }
138 catch (std::exception const& ex)
139 {
140 JLOG(journal.error()) << "Exception in " << __func__ << ": " << ex.what();
141 return std::nullopt;
142 }
143}
144
145template <class Stream>
146Stream&
147logMftAct(Stream& s, std::string const& action, PublicKey const& pk, std::uint32_t seq)
148{
149 s << "Manifest: " << action << ";Pk: " << toBase58(TokenType::NodePublic, pk) << ";Seq: " << seq << ";";
150 return s;
151}
152
153template <class Stream>
154Stream&
155logMftAct(Stream& s, std::string const& action, PublicKey const& pk, std::uint32_t seq, std::uint32_t oldSeq)
156{
157 s << "Manifest: " << action << ";Pk: " << toBase58(TokenType::NodePublic, pk) << ";Seq: " << seq
158 << ";OldSeq: " << oldSeq << ";";
159 return s;
160}
161
162bool
164{
167 st.set(sit);
168
169 // The manifest must either have a signing key or be revoked. This check
170 // prevents us from accessing an unseated signingKey in the next check.
171 if (!revoked() && !signingKey)
172 return false;
173
174 // Signing key and signature are not required for
175 // master key revocations
177 return false;
178
179 return xrpl::verify(st, HashPrefix::manifest, masterKey, sfMasterSignature);
180}
181
184{
187 st.set(sit);
188 return st.getHash(HashPrefix::manifest);
189}
190
191bool
193{
194 /*
195 The maximum possible sequence number means that the master key
196 has been revoked.
197 */
198 return revoked(sequence);
199}
200
201bool
203{
204 // The maximum possible sequence number means that the master key has
205 // been revoked.
207}
208
211{
214 st.set(sit);
215 if (!get(st, sfSignature))
216 return std::nullopt;
217 return st.getFieldVL(sfSignature);
218}
219
220Blob
222{
225 st.set(sit);
226 return st.getFieldVL(sfMasterSignature);
227}
228
231{
232 try
233 {
234 std::string tokenStr;
235
236 tokenStr.reserve(
237 std::accumulate(blob.cbegin(), blob.cend(), std::size_t(0), [](std::size_t init, std::string const& s) {
238 return init + s.size();
239 }));
240
241 for (auto const& line : blob)
242 tokenStr += boost::algorithm::trim_copy(line);
243
244 tokenStr = base64_decode(tokenStr);
245
246 Json::Reader r;
247 Json::Value token;
248
249 if (r.parse(tokenStr, token))
250 {
251 auto const m = token.get("manifest", Json::Value{});
252 auto const k = token.get("validation_secret_key", Json::Value{});
253
254 if (m.isString() && k.isString())
255 {
256 auto const key = strUnHex(k.asString());
257
258 if (key && key->size() == 32)
259 return ValidatorToken{m.asString(), makeSlice(*key)};
260 }
261 }
262
263 return std::nullopt;
264 }
265 catch (std::exception const& ex)
266 {
267 JLOG(journal.error()) << "Exception in " << __func__ << ": " << ex.what();
268 return std::nullopt;
269 }
270}
271
274{
276 auto const iter = map_.find(pk);
277
278 if (iter != map_.end() && !iter->second.revoked())
279 return iter->second.signingKey;
280
281 return pk;
282}
283
286{
288
289 if (auto const iter = signingToMasterKeys_.find(pk); iter != signingToMasterKeys_.end())
290 return iter->second;
291
292 return pk;
293}
294
297{
299 auto const iter = map_.find(pk);
300
301 if (iter != map_.end() && !iter->second.revoked())
302 return iter->second.sequence;
303
304 return std::nullopt;
305}
306
309{
311 auto const iter = map_.find(pk);
312
313 if (iter != map_.end() && !iter->second.revoked())
314 return iter->second.domain;
315
316 return std::nullopt;
317}
318
321{
323 auto const iter = map_.find(pk);
324
325 if (iter != map_.end() && !iter->second.revoked())
326 return iter->second.serialized;
327
328 return std::nullopt;
329}
330
331bool
333{
335 auto const iter = map_.find(pk);
336
337 if (iter != map_.end())
338 return iter->second.revoked();
339
340 return false;
341}
342
345{
346 // Check the manifest against the conditions that do not require a
347 // `unique_lock` (write lock) on the `mutex_`. Since the signature can be
348 // relatively expensive, the `checkSignature` parameter determines if the
349 // signature should be checked. Since `prewriteCheck` is run twice (see
350 // comment below), `checkSignature` only needs to be set to true on the
351 // first run.
352 auto prewriteCheck =
353 [this, &m](auto const& iter, bool checkSignature, auto const& lock) -> std::optional<ManifestDisposition> {
354 XRPL_ASSERT(lock.owns_lock(), "xrpl::ManifestCache::applyManifest::prewriteCheck : locked");
355 (void)lock; // not used. parameter is present to ensure the mutex is
356 // locked when the lambda is called.
357 if (iter != map_.end() && m.sequence <= iter->second.sequence)
358 {
359 // We received a manifest whose sequence number is not strictly
360 // greater than the one we already know about. This can happen in
361 // several cases including when we receive manifests from a peer who
362 // doesn't have the latest data.
363 if (auto stream = j_.debug())
364 logMftAct(stream, "Stale", m.masterKey, m.sequence, iter->second.sequence);
366 }
367
368 if (checkSignature && !m.verify())
369 {
370 if (auto stream = j_.warn())
371 logMftAct(stream, "Invalid", m.masterKey, m.sequence);
373 }
374
375 // If the master key associated with a manifest is or might be
376 // compromised and is, therefore, no longer trustworthy.
377 //
378 // A manifest revocation essentially marks a manifest as compromised. By
379 // setting the sequence number to the highest value possible, the
380 // manifest is effectively neutered and cannot be superseded by a forged
381 // one.
382 bool const revoked = m.revoked();
383
384 if (auto stream = j_.warn(); stream && revoked)
385 logMftAct(stream, "Revoked", m.masterKey, m.sequence);
386
387 // Sanity check: the master key of this manifest should not be used as
388 // the ephemeral key of another manifest:
389 if (auto const x = signingToMasterKeys_.find(m.masterKey); x != signingToMasterKeys_.end())
390 {
391 JLOG(j_.warn()) << to_string(m) << ": Master key already used as ephemeral key for "
392 << toBase58(TokenType::NodePublic, x->second);
393
395 }
396
397 if (!revoked)
398 {
399 if (!m.signingKey)
400 {
401 JLOG(j_.warn()) << to_string(m)
402 << ": is not revoked and the manifest has no "
403 "signing key. Hence, the manifest is "
404 "invalid";
406 }
407
408 // Sanity check: the ephemeral key of this manifest should not be
409 // used as the master or ephemeral key of another manifest:
410 if (auto const x = signingToMasterKeys_.find(*m.signingKey); x != signingToMasterKeys_.end())
411 {
412 JLOG(j_.warn()) << to_string(m) << ": Ephemeral key already used as ephemeral key for "
413 << toBase58(TokenType::NodePublic, x->second);
414
416 }
417
418 if (auto const x = map_.find(*m.signingKey); x != map_.end())
419 {
420 JLOG(j_.warn()) << to_string(m) << ": Ephemeral key used as master key for " << to_string(x->second);
421
423 }
424 }
425
426 return std::nullopt;
427 };
428
429 {
431 if (auto d = prewriteCheck(map_.find(m.masterKey), /*checkSig*/ true, sl))
432 return *d;
433 }
434
436 auto const iter = map_.find(m.masterKey);
437 // Since we released the previously held read lock, it's possible that the
438 // collections have been written to. This means we need to run
439 // `prewriteCheck` again. This re-does work, but `prewriteCheck` is
440 // relatively inexpensive to run, and doing it this way allows us to run
441 // `prewriteCheck` under a `shared_lock` above.
442 // Note, the signature has already been checked above, so it
443 // doesn't need to happen again (signature checks are somewhat expensive).
444 // Note: It's a mistake to use an upgradable lock. This is a recipe for
445 // deadlock.
446 if (auto d = prewriteCheck(iter, /*checkSig*/ false, sl))
447 return *d;
448
449 bool const revoked = m.revoked();
450 // This is the first manifest we are seeing for a master key. This should
451 // only ever happen once per validator run.
452 if (iter == map_.end())
453 {
454 if (auto stream = j_.info())
455 logMftAct(stream, "AcceptedNew", m.masterKey, m.sequence);
456
457 if (!revoked)
459
460 auto masterKey = m.masterKey;
461 map_.emplace(std::move(masterKey), std::move(m));
462
463 // Something has changed. Keep track of it.
464 seq_++;
465
467 }
468
469 // An ephemeral key was revoked and superseded by a new key. This is
470 // expected, but should happen infrequently.
471 if (auto stream = j_.info())
472 logMftAct(stream, "AcceptedUpdate", m.masterKey, m.sequence, iter->second.sequence);
473
474 signingToMasterKeys_.erase(*iter->second.signingKey);
475
476 if (!revoked)
478
479 iter->second = std::move(m);
480
481 // Something has changed. Keep track of it.
482 seq_++;
483
485}
486
487void
489{
490 auto db = dbCon.checkoutDb();
491 xrpl::getManifests(*db, dbTable, *this, j_);
492}
493
494bool
496 DatabaseCon& dbCon,
497 std::string const& dbTable,
498 std::string const& configManifest,
499 std::vector<std::string> const& configRevocation)
500{
501 load(dbCon, dbTable);
502
503 if (!configManifest.empty())
504 {
505 auto mo = deserializeManifest(base64_decode(configManifest));
506 if (!mo)
507 {
508 JLOG(j_.error()) << "Malformed validator_token in config";
509 return false;
510 }
511
512 if (mo->revoked())
513 {
514 JLOG(j_.warn()) << "Configured manifest revokes public key";
515 }
516
517 if (applyManifest(std::move(*mo)) == ManifestDisposition::invalid)
518 {
519 JLOG(j_.error()) << "Manifest in config was rejected";
520 return false;
521 }
522 }
523
524 if (!configRevocation.empty())
525 {
526 std::string revocationStr;
527 revocationStr.reserve(std::accumulate(
528 configRevocation.cbegin(),
529 configRevocation.cend(),
530 std::size_t(0),
531 [](std::size_t init, std::string const& s) { return init + s.size(); }));
532
533 for (auto const& line : configRevocation)
534 revocationStr += boost::algorithm::trim_copy(line);
535
536 auto mo = deserializeManifest(base64_decode(revocationStr));
537
538 if (!mo || !mo->revoked() || applyManifest(std::move(*mo)) == ManifestDisposition::invalid)
539 {
540 JLOG(j_.error()) << "Invalid validator key revocation in config";
541 return false;
542 }
543 }
544
545 return true;
546}
547
548void
550 DatabaseCon& dbCon,
551 std::string const& dbTable,
552 std::function<bool(PublicKey const&)> const& isTrusted)
553{
555 auto db = dbCon.checkoutDb();
556
557 saveManifests(*db, dbTable, isTrusted, map_, j_);
558}
559} // 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:318
Stream debug() const
Definition Journal.h:300
Stream info() const
Definition Journal.h:306
Stream warn() const
Definition Journal.h:312
LockedSociSession checkoutDb()
std::atomic< std::uint32_t > seq_
Definition Manifest.h:235
std::shared_mutex mutex_
Definition Manifest.h:227
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:495
std::optional< PublicKey > getSigningKey(PublicKey const &pk) const
Returns master key's current signing key.
Definition Manifest.cpp:273
ManifestDisposition applyManifest(Manifest m)
Add manifest to cache.
Definition Manifest.cpp:344
std::optional< std::string > getDomain(PublicKey const &pk) const
Returns domain claimed by a given public key.
Definition Manifest.cpp:308
PublicKey getMasterKey(PublicKey const &pk) const
Returns ephemeral signing key's master public key.
Definition Manifest.cpp:285
hash_map< PublicKey, PublicKey > signingToMasterKeys_
Master public keys stored by current ephemeral public key.
Definition Manifest.h:233
std::optional< std::string > getManifest(PublicKey const &pk) const
Returns manifest corresponding to a given public key.
Definition Manifest.cpp:320
hash_map< PublicKey, Manifest > map_
Active manifests stored by master public key.
Definition Manifest.h:230
std::optional< std::uint32_t > getSequence(PublicKey const &pk) const
Returns master key's current manifest sequence.
Definition Manifest.cpp:296
void save(DatabaseCon &dbCon, std::string const &dbTable, std::function< bool(PublicKey const &)> const &isTrusted)
Save cached manifests to database.
Definition Manifest.cpp:549
beast::Journal j_
Definition Manifest.h:226
bool revoked(PublicKey const &pk) const
Returns true if master key has been revoked in a manifest.
Definition Manifest.cpp:332
A public key.
Definition PublicKey.h:42
Defines the fields and their attributes within a STObject.
Definition SOTemplate.h:88
Blob getFieldVL(SField const &field) const
Definition STObject.cpp:624
uint256 getHash(HashPrefix prefix) const
Definition STObject.cpp:350
void set(SOTemplate const &)
Definition STObject.cpp:132
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:49
std::uint8_t const * data() const noexcept
Return a pointer to beginning of the storage.
Definition Slice.h:77
std::size_t size() const noexcept
Returns the number of bytes in the storage.
Definition Slice.h:60
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)
Definition Manifest.cpp:147
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:597
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:16
@ soeDEFAULT
Definition SOTemplate.h:17
@ soeREQUIRED
Definition SOTemplate.h:15
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.
Definition Manifest.cpp:35
std::optional< ValidatorToken > loadValidatorToken(std::vector< std::string > const &blob, beast::Journal journal)
Definition Manifest.cpp:230
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:62
@ 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:22
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:213
ManifestDisposition
Definition Manifest.h:183
@ 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:202
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.
Definition Manifest.cpp:221
std::optional< Blob > getSignature() const
Returns manifest signature.
Definition Manifest.cpp:210
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.
Definition Manifest.cpp:192
uint256 hash() const
Returns hash of serialized manifest data.
Definition Manifest.cpp:183
bool verify() const
Returns true if manifest signature is valid.
Definition Manifest.cpp:163
T to_string(T... args)
T what(T... args)