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 ripple {
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 ripple::verify(
194 st, HashPrefix::manifest, masterKey, sfMasterSignature);
195}
196
199{
202 st.set(sit);
203 return st.getHash(HashPrefix::manifest);
204}
205
206bool
208{
209 /*
210 The maximum possible sequence number means that the master key
211 has been revoked.
212 */
213 return revoked(sequence);
214}
215
216bool
218{
219 // The maximum possible sequence number means that the master key has
220 // been revoked.
222}
223
226{
229 st.set(sit);
230 if (!get(st, sfSignature))
231 return std::nullopt;
232 return st.getFieldVL(sfSignature);
233}
234
235Blob
237{
240 st.set(sit);
241 return st.getFieldVL(sfMasterSignature);
242}
243
246{
247 try
248 {
249 std::string tokenStr;
250
251 tokenStr.reserve(std::accumulate(
252 blob.cbegin(),
253 blob.cend(),
254 std::size_t(0),
255 [](std::size_t init, std::string const& s) {
256 return init + s.size();
257 }));
258
259 for (auto const& line : blob)
260 tokenStr += boost::algorithm::trim_copy(line);
261
262 tokenStr = base64_decode(tokenStr);
263
264 Json::Reader r;
265 Json::Value token;
266
267 if (r.parse(tokenStr, token))
268 {
269 auto const m = token.get("manifest", Json::Value{});
270 auto const k = token.get("validation_secret_key", Json::Value{});
271
272 if (m.isString() && k.isString())
273 {
274 auto const key = strUnHex(k.asString());
275
276 if (key && key->size() == 32)
277 return ValidatorToken{m.asString(), makeSlice(*key)};
278 }
279 }
280
281 return std::nullopt;
282 }
283 catch (std::exception const& ex)
284 {
285 JLOG(journal.error())
286 << "Exception in " << __func__ << ": " << ex.what();
287 return std::nullopt;
288 }
289}
290
293{
295 auto const iter = map_.find(pk);
296
297 if (iter != map_.end() && !iter->second.revoked())
298 return iter->second.signingKey;
299
300 return pk;
301}
302
305{
307
308 if (auto const iter = signingToMasterKeys_.find(pk);
309 iter != signingToMasterKeys_.end())
310 return iter->second;
311
312 return pk;
313}
314
317{
319 auto const iter = map_.find(pk);
320
321 if (iter != map_.end() && !iter->second.revoked())
322 return iter->second.sequence;
323
324 return std::nullopt;
325}
326
329{
331 auto const iter = map_.find(pk);
332
333 if (iter != map_.end() && !iter->second.revoked())
334 return iter->second.domain;
335
336 return std::nullopt;
337}
338
341{
343 auto const iter = map_.find(pk);
344
345 if (iter != map_.end() && !iter->second.revoked())
346 return iter->second.serialized;
347
348 return std::nullopt;
349}
350
351bool
353{
355 auto const iter = map_.find(pk);
356
357 if (iter != map_.end())
358 return iter->second.revoked();
359
360 return false;
361}
362
365{
366 // Check the manifest against the conditions that do not require a
367 // `unique_lock` (write lock) on the `mutex_`. Since the signature can be
368 // relatively expensive, the `checkSignature` parameter determines if the
369 // signature should be checked. Since `prewriteCheck` is run twice (see
370 // comment below), `checkSignature` only needs to be set to true on the
371 // first run.
372 auto prewriteCheck =
373 [this, &m](auto const& iter, bool checkSignature, auto const& lock)
375 XRPL_ASSERT(
376 lock.owns_lock(),
377 "ripple::ManifestCache::applyManifest::prewriteCheck : locked");
378 (void)lock; // not used. parameter is present to ensure the mutex is
379 // locked when the lambda is called.
380 if (iter != map_.end() && m.sequence <= iter->second.sequence)
381 {
382 // We received a manifest whose sequence number is not strictly
383 // greater than the one we already know about. This can happen in
384 // several cases including when we receive manifests from a peer who
385 // doesn't have the latest data.
386 if (auto stream = j_.debug())
387 logMftAct(
388 stream,
389 "Stale",
390 m.masterKey,
391 m.sequence,
392 iter->second.sequence);
394 }
395
396 if (checkSignature && !m.verify())
397 {
398 if (auto stream = j_.warn())
399 logMftAct(stream, "Invalid", m.masterKey, m.sequence);
401 }
402
403 // If the master key associated with a manifest is or might be
404 // compromised and is, therefore, no longer trustworthy.
405 //
406 // A manifest revocation essentially marks a manifest as compromised. By
407 // setting the sequence number to the highest value possible, the
408 // manifest is effectively neutered and cannot be superseded by a forged
409 // one.
410 bool const revoked = m.revoked();
411
412 if (auto stream = j_.warn(); stream && revoked)
413 logMftAct(stream, "Revoked", m.masterKey, m.sequence);
414
415 // Sanity check: the master key of this manifest should not be used as
416 // the ephemeral key of another manifest:
417 if (auto const x = signingToMasterKeys_.find(m.masterKey);
418 x != signingToMasterKeys_.end())
419 {
420 JLOG(j_.warn()) << to_string(m)
421 << ": Master key already used as ephemeral key for "
422 << toBase58(TokenType::NodePublic, x->second);
423
425 }
426
427 if (!revoked)
428 {
429 if (!m.signingKey)
430 {
431 JLOG(j_.warn()) << to_string(m)
432 << ": is not revoked and the manifest has no "
433 "signing key. Hence, the manifest is "
434 "invalid";
436 }
437
438 // Sanity check: the ephemeral key of this manifest should not be
439 // used as the master or ephemeral key of another manifest:
440 if (auto const x = signingToMasterKeys_.find(*m.signingKey);
441 x != signingToMasterKeys_.end())
442 {
443 JLOG(j_.warn())
444 << to_string(m)
445 << ": Ephemeral key already used as ephemeral key for "
446 << toBase58(TokenType::NodePublic, x->second);
447
449 }
450
451 if (auto const x = map_.find(*m.signingKey); x != map_.end())
452 {
453 JLOG(j_.warn())
454 << to_string(m) << ": Ephemeral key used as master key for "
455 << to_string(x->second);
456
458 }
459 }
460
461 return std::nullopt;
462 };
463
464 {
466 if (auto d =
467 prewriteCheck(map_.find(m.masterKey), /*checkSig*/ true, sl))
468 return *d;
469 }
470
472 auto const iter = map_.find(m.masterKey);
473 // Since we released the previously held read lock, it's possible that the
474 // collections have been written to. This means we need to run
475 // `prewriteCheck` again. This re-does work, but `prewriteCheck` is
476 // relatively inexpensive to run, and doing it this way allows us to run
477 // `prewriteCheck` under a `shared_lock` above.
478 // Note, the signature has already been checked above, so it
479 // doesn't need to happen again (signature checks are somewhat expensive).
480 // Note: It's a mistake to use an upgradable lock. This is a recipe for
481 // deadlock.
482 if (auto d = prewriteCheck(iter, /*checkSig*/ false, sl))
483 return *d;
484
485 bool const revoked = m.revoked();
486 // This is the first manifest we are seeing for a master key. This should
487 // only ever happen once per validator run.
488 if (iter == map_.end())
489 {
490 if (auto stream = j_.info())
491 logMftAct(stream, "AcceptedNew", m.masterKey, m.sequence);
492
493 if (!revoked)
495
496 auto masterKey = m.masterKey;
497 map_.emplace(std::move(masterKey), std::move(m));
499 }
500
501 // An ephemeral key was revoked and superseded by a new key. This is
502 // expected, but should happen infrequently.
503 if (auto stream = j_.info())
504 logMftAct(
505 stream,
506 "AcceptedUpdate",
507 m.masterKey,
508 m.sequence,
509 iter->second.sequence);
510
511 signingToMasterKeys_.erase(*iter->second.signingKey);
512
513 if (!revoked)
515
516 iter->second = std::move(m);
517
518 // Something has changed. Keep track of it.
519 seq_++;
520
522}
523
524void
526{
527 auto db = dbCon.checkoutDb();
528 ripple::getManifests(*db, dbTable, *this, j_);
529}
530
531bool
533 DatabaseCon& dbCon,
534 std::string const& dbTable,
535 std::string const& configManifest,
536 std::vector<std::string> const& configRevocation)
537{
538 load(dbCon, dbTable);
539
540 if (!configManifest.empty())
541 {
542 auto mo = deserializeManifest(base64_decode(configManifest));
543 if (!mo)
544 {
545 JLOG(j_.error()) << "Malformed validator_token in config";
546 return false;
547 }
548
549 if (mo->revoked())
550 {
551 JLOG(j_.warn()) << "Configured manifest revokes public key";
552 }
553
554 if (applyManifest(std::move(*mo)) == ManifestDisposition::invalid)
555 {
556 JLOG(j_.error()) << "Manifest in config was rejected";
557 return false;
558 }
559 }
560
561 if (!configRevocation.empty())
562 {
563 std::string revocationStr;
564 revocationStr.reserve(std::accumulate(
565 configRevocation.cbegin(),
566 configRevocation.cend(),
567 std::size_t(0),
568 [](std::size_t init, std::string const& s) {
569 return init + s.size();
570 }));
571
572 for (auto const& line : configRevocation)
573 revocationStr += boost::algorithm::trim_copy(line);
574
575 auto mo = deserializeManifest(base64_decode(revocationStr));
576
577 if (!mo || !mo->revoked() ||
579 {
580 JLOG(j_.error()) << "Invalid validator key revocation in config";
581 return false;
582 }
583 }
584
585 return true;
586}
587
588void
590 DatabaseCon& dbCon,
591 std::string const& dbTable,
592 std::function<bool(PublicKey const&)> const& isTrusted)
593{
595 auto db = dbCon.checkoutDb();
596
597 saveManifests(*db, dbTable, isTrusted, map_, j_);
598}
599} // namespace ripple
T accumulate(T... args)
T assign(T... args)
T cbegin(T... args)
Unserialize a JSON document into a Value.
Definition json_reader.h:20
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::optional< std::uint32_t > getSequence(PublicKey const &pk) const
Returns master key's current manifest sequence.
Definition Manifest.cpp:316
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 mainfest corresponding to a given public key.
Definition Manifest.cpp:340
std::optional< PublicKey > getSigningKey(PublicKey const &pk) const
Returns master key's current signing key.
Definition Manifest.cpp:292
std::shared_mutex mutex_
Definition Manifest.h:240
bool revoked(PublicKey const &pk) const
Returns true if master key has been revoked in a manifest.
Definition Manifest.cpp:352
beast::Journal j_
Definition Manifest.h:239
ManifestDisposition applyManifest(Manifest m)
Add manifest to cache.
Definition Manifest.cpp:364
hash_map< PublicKey, Manifest > map_
Active manifests stored by master public key.
Definition Manifest.h:243
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:532
std::atomic< std::uint32_t > seq_
Definition Manifest.h:248
void save(DatabaseCon &dbCon, std::string const &dbTable, std::function< bool(PublicKey const &)> const &isTrusted)
Save cached manifests to database.
Definition Manifest.cpp:589
PublicKey getMasterKey(PublicKey const &pk) const
Returns ephemeral signing key's master public key.
Definition Manifest.cpp:304
std::optional< std::string > getDomain(PublicKey const &pk) const
Returns domain claimed by a given public key.
Definition Manifest.cpp:328
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
void set(SOTemplate const &)
Definition STObject.cpp:137
uint256 getHash(HashPrefix prefix) const
Definition STObject.cpp:376
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
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
std::optional< Manifest > deserializeManifest(Slice s, beast::Journal journal)
Constructs Manifest from serialized string.
Definition Manifest.cpp:35
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
bool verify(PublicKey const &publicKey, Slice const &m, Slice const &sig, bool mustBeFullyCanonical=true) noexcept
Verify a signature on a message.
std::string base64_decode(std::string_view data)
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
Stream & logMftAct(Stream &s, std::string const &action, PublicKey const &pk, std::uint32_t seq)
Definition Manifest.cpp:149
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.
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
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.
std::optional< ValidatorToken > loadValidatorToken(std::vector< std::string > const &blob, beast::Journal journal)
Definition Manifest.cpp:245
SField const sfGeneric
@ manifest
Manifest.
@ soeOPTIONAL
Definition SOTemplate.h:17
@ soeREQUIRED
Definition SOTemplate.h:16
@ soeDEFAULT
Definition SOTemplate.h:18
bool isProperlyFormedTomlDomain(std::string_view domain)
Determines if the given string looks like a TOML-file hosting domain.
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
T reserve(T... args)
T size(T... args)
std::string serialized
The manifest in serialized form.
Definition Manifest.h:64
std::uint32_t sequence
The sequence number of this manifest.
Definition Manifest.h:76
static bool revoked(std::uint32_t sequence)
Returns true if manifest revokes master key.
Definition Manifest.cpp:217
bool revoked() const
Returns true if manifest revokes master key.
Definition Manifest.cpp:207
bool verify() const
Returns true if manifest signature is valid.
Definition Manifest.cpp:177
uint256 hash() const
Returns hash of serialized manifest data.
Definition Manifest.cpp:198
std::optional< Blob > getSignature() const
Returns manifest signature.
Definition Manifest.cpp:225
std::optional< PublicKey > signingKey
The ephemeral key associated with this manifest.
Definition Manifest.h:73
Blob getMasterSignature() const
Returns manifest master key signature.
Definition Manifest.cpp:236
PublicKey masterKey
The master key associated with this manifest.
Definition Manifest.h:67
T to_string(T... args)
T what(T... args)