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