diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj index 9bc98acfa..98c252fd0 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj +++ b/Builds/VisualStudio2013/RippleD.vcxproj @@ -2480,6 +2480,12 @@ + + True + True + + + True True @@ -2526,6 +2532,10 @@ + + True + True + True True diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters index d78733c09..bb9eadb99 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters @@ -3138,6 +3138,12 @@ ripple\overlay\impl + + ripple\overlay\impl + + + ripple\overlay\impl + ripple\overlay\impl @@ -3192,6 +3198,9 @@ ripple\overlay + + ripple\overlay\tests + ripple\overlay\tests diff --git a/src/ripple/app/misc/HashRouter.cpp b/src/ripple/app/misc/HashRouter.cpp index b1a3d27ad..47ca8e0a3 100644 --- a/src/ripple/app/misc/HashRouter.cpp +++ b/src/ripple/app/misc/HashRouter.cpp @@ -110,9 +110,9 @@ private: Entry& findCreateEntry (uint256 const& , bool& created); - using LockType = std::mutex; - using ScopedLockType = std::lock_guard ; - LockType mLock; + using MutexType = std::mutex; + using ScopedLockType = std::lock_guard ; + MutexType mMutex; // Stores all suppressed hashes and their expiration time hash_map mSuppressionMap; @@ -156,7 +156,7 @@ HashRouter::Entry& HashRouter::findCreateEntry (uint256 const& index, bool& crea bool HashRouter::addSuppression (uint256 const& index) { - ScopedLockType sl (mLock); + ScopedLockType lock (mMutex); bool created; findCreateEntry (index, created); @@ -165,7 +165,7 @@ bool HashRouter::addSuppression (uint256 const& index) HashRouter::Entry HashRouter::getEntry (uint256 const& index) { - ScopedLockType sl (mLock); + ScopedLockType lock (mMutex); bool created; return findCreateEntry (index, created); @@ -173,7 +173,7 @@ HashRouter::Entry HashRouter::getEntry (uint256 const& index) bool HashRouter::addSuppressionPeer (uint256 const& index, PeerShortID peer) { - ScopedLockType sl (mLock); + ScopedLockType lock (mMutex); bool created; findCreateEntry (index, created).addPeer (peer); @@ -182,7 +182,7 @@ bool HashRouter::addSuppressionPeer (uint256 const& index, PeerShortID peer) bool HashRouter::addSuppressionPeer (uint256 const& index, PeerShortID peer, int& flags) { - ScopedLockType sl (mLock); + ScopedLockType lock (mMutex); bool created; Entry& s = findCreateEntry (index, created); @@ -193,7 +193,7 @@ bool HashRouter::addSuppressionPeer (uint256 const& index, PeerShortID peer, int int HashRouter::getFlags (uint256 const& index) { - ScopedLockType sl (mLock); + ScopedLockType lock (mMutex); bool created; return findCreateEntry (index, created).getFlags (); @@ -201,7 +201,7 @@ int HashRouter::getFlags (uint256 const& index) bool HashRouter::addSuppressionFlags (uint256 const& index, int flag) { - ScopedLockType sl (mLock); + ScopedLockType lock (mMutex); bool created; findCreateEntry (index, created).setFlag (flag); @@ -217,7 +217,7 @@ bool HashRouter::setFlag (uint256 const& index, int flag) // return: true = changed, false = unchanged assert (flag != 0); - ScopedLockType sl (mLock); + ScopedLockType lock (mMutex); bool created; Entry& s = findCreateEntry (index, created); @@ -231,7 +231,7 @@ bool HashRouter::setFlag (uint256 const& index, int flag) bool HashRouter::swapSet (uint256 const& index, std::set& peers, int flag) { - ScopedLockType sl (mLock); + ScopedLockType lock (mMutex); bool created; Entry& s = findCreateEntry (index, created); diff --git a/src/ripple/app/misc/UniqueNodeList.cpp b/src/ripple/app/misc/UniqueNodeList.cpp index dd73bba06..1a95407f8 100644 --- a/src/ripple/app/misc/UniqueNodeList.cpp +++ b/src/ripple/app/misc/UniqueNodeList.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,18 @@ strJoin (Iterator first, Iterator last, std::string strSeparator) return ossValues.str (); } +static +std::string +encodeCredential (AnyPublicKey const& pk, unsigned char type) +{ + Blob buffer; + buffer.reserve(1 + pk.size()); + buffer.push_back (type); + auto const data = pk.data(); + buffer.insert (buffer.end(), data, data + pk.size()); + return Base58::encodeWithCheck (buffer); +} + template void selectBlobsIntoStrings ( soci::session& s, @@ -214,6 +227,7 @@ private: // XXX Make this faster, make this the contents vector unsigned char or raw public key. // XXX Contents needs to based on score. hash_set mUNL; + hash_map ephemeralValidatorKeys_; boost::posix_time::ptime mtpScoreNext; // When to start scoring. boost::posix_time::ptime mtpScoreStart; // Time currently started scoring. @@ -244,6 +258,9 @@ public: // Get update times and start fetching and scoring as needed. void start(); + void insertEphemeralKey (AnyPublicKey pk, std::string comment); + void deleteEphemeralKey (AnyPublicKey const& pk); + // Add a trusted node. Called by RPC or other source. void nodeAddPublic (RippleAddress const& naNodePublic, ValidatorSource vsWhy, std::string const& strComment); @@ -468,6 +485,22 @@ void UniqueNodeListImp::start() //-------------------------------------------------------------------------- +void UniqueNodeListImp::insertEphemeralKey (AnyPublicKey pk, std::string comment) +{ + ScopedUNLLockType sl (mUNLLock); + + ephemeralValidatorKeys_.insert (std::make_pair(std::move(pk), std::move(comment))); +} + +void UniqueNodeListImp::deleteEphemeralKey (AnyPublicKey const& pk) +{ + ScopedUNLLockType sl (mUNLLock); + + ephemeralValidatorKeys_.erase (pk); +} + +//-------------------------------------------------------------------------- + // Add a trusted node. Called by RPC or other source. void UniqueNodeListImp::nodeAddPublic (RippleAddress const& naNodePublic, ValidatorSource vsWhy, std::string const& strComment) { @@ -612,9 +645,17 @@ void UniqueNodeListImp::nodeScore() bool UniqueNodeListImp::nodeInUNL (RippleAddress const& naNodePublic) { + auto const& blob = naNodePublic.getNodePublic(); + AnyPublicKey const pk (blob.data(), blob.size()); + ScopedUNLLockType sl (mUNLLock); - return mUNL.end () != mUNL.find (naNodePublic.humanNodePublic ()); + if (ephemeralValidatorKeys_.find (pk) != ephemeralValidatorKeys_.end()) + { + return true; + } + + return mUNL.find (naNodePublic.humanNodePublic()) != mUNL.end(); } //-------------------------------------------------------------------------- @@ -883,6 +924,18 @@ Json::Value UniqueNodeListImp::getUnlJson() ret.append (node); } + ScopedUNLLockType sl (mUNLLock); + + for (auto const& key : ephemeralValidatorKeys_) + { + Json::Value node (Json::objectValue); + + node["publicKey"] = encodeCredential (key.first, VER_NODE_PUBLIC); + node["comment"] = key.second; + + ret.append (node); + } + return ret; } diff --git a/src/ripple/app/misc/UniqueNodeList.h b/src/ripple/app/misc/UniqueNodeList.h index 58303cf0a..56fcd6ad8 100644 --- a/src/ripple/app/misc/UniqueNodeList.h +++ b/src/ripple/app/misc/UniqueNodeList.h @@ -20,7 +20,9 @@ #ifndef RIPPLE_APP_PEERS_UNIQUENODELIST_H_INCLUDED #define RIPPLE_APP_PEERS_UNIQUENODELIST_H_INCLUDED -#include +#include +#include +#include #include // #include #include @@ -53,6 +55,9 @@ public: // VFALCO TODO Roll this into the constructor so there is one less state. virtual void start () = 0; + virtual void insertEphemeralKey (AnyPublicKey pk, std::string comment) = 0; + virtual void deleteEphemeralKey (AnyPublicKey const& pk) = 0; + // VFALCO TODO rename all these, the "node" prefix is redundant (lol) virtual void nodeAddPublic (RippleAddress const& naNodePublic, ValidatorSource vsWhy, std::string const& strComment) = 0; virtual void nodeAddDomain (std::string strDomain, ValidatorSource vsWhy, std::string const& strComment = "") = 0; diff --git a/src/ripple/overlay/impl/Manifest.cpp b/src/ripple/overlay/impl/Manifest.cpp new file mode 100644 index 000000000..a0b35bfa6 --- /dev/null +++ b/src/ripple/overlay/impl/Manifest.cpp @@ -0,0 +1,297 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +void +ManifestCache::configValidatorKey(std::string const& line, beast::Journal const& journal) +{ + auto const words = beast::rfc2616::split(line.begin(), line.end(), ' '); + + if (words.size() != 2) + { + throw std::runtime_error ("[validator_keys] format is ` "); + } + + Blob key; + if (! Base58::decodeWithCheck (words[0], key)) + { + throw std::runtime_error ("Error decoding validator key"); + } + if (key.size() != 34) + { + throw std::runtime_error ("Expected 34-byte validator key"); + } + if (key[0] != VER_NODE_PUBLIC) + { + throw std::runtime_error ("Expected VER_NODE_PUBLIC (28)"); + } + if (key[1] != 0xED) + { + throw std::runtime_error ("Expected Ed25519 key (0xED)"); + } + + auto const masterKey = AnyPublicKey (key.data() + 1, key.size() - 1); + auto const& comment = words[1]; + + if (journal.debug) journal.debug + << masterKey << " " << comment; + + addTrustedKey (masterKey, comment); +} + +void +ManifestCache::configManifest (std::string s, beast::Journal const& journal) +{ + STObject st(sfGeneric); + try + { + SerialIter sit(s.data(), s.size()); + st.set(sit); + } + catch(...) + { + throw std::runtime_error("Malformed manifest in config"); + } + + auto const mseq = get(st, sfSequence); + auto const msig = get(st, sfSignature); + auto mpk = get(st, sfPublicKey); + auto mspk = get(st, sfSigningPubKey); + if (! mseq || ! msig || ! mpk || ! mspk) + { + throw std::runtime_error("Missing fields in manifest in config"); + } + auto const& pk = *mpk; + + if (! verify(st, HashPrefix::manifest, pk)) + { + throw std::runtime_error("Unverifiable manifest in config"); + } + + auto const result = applyManifest (std::move(s), journal); + + if (result != ManifestDisposition::accepted) + { + throw std::runtime_error("Our own validation manifest was not accepted"); + } +} + +void +ManifestCache::addTrustedKey (AnyPublicKey const& pk, std::string const& comment) +{ + std::lock_guard lock (mutex_); + + auto& value = map_[pk]; + + if (value.m) + { + throw std::runtime_error ("New trusted validator key already has a manifest"); + } + + value.comment = comment; +} + +ManifestDisposition +ManifestCache::preflightManifest_locked (AnyPublicKey const& pk, std::uint32_t seq, + beast::Journal const& journal) const +{ + auto const iter = map_.find(pk); + + if (iter == map_.end()) + { + /* + A manifest was received whose master key we don't trust. + Since rippled always sends all of its current manifests, + this will happen normally any time a peer connects. + */ + if (journal.debug) journal.debug + << "Ignoring manifest #" << seq << " from untrusted key " << pk; + return ManifestDisposition::untrusted; + } + + auto& old = iter->second.m; + + if (old && seq <= old->seq) + { + /* + A manifest was received for a validator we're tracking, but + its sequence number is no higher than the one already stored. + This will happen normally when a peer without the latest gossip + connects. + */ + if (journal.debug) journal.debug + << "Ignoring manifest #" << seq + << "which isn't newer than #" << old->seq; + return ManifestDisposition::stale; // not a newer manifest, ignore + } + + return ManifestDisposition::accepted; +} + +ManifestDisposition +ManifestCache::applyManifest (std::string s, beast::Journal const& journal) +{ + STObject st(sfGeneric); + try + { + SerialIter sit(s.data(), s.size()); + st.set(sit); + } + catch(...) + { + return ManifestDisposition::malformed; + } + + auto const opt_pk = get(st, sfPublicKey); + auto const opt_spk = get(st, sfSigningPubKey); + auto const opt_seq = get(st, sfSequence); + auto const opt_sig = get(st, sfSignature); + + if (! opt_pk || ! opt_spk || ! opt_seq || ! opt_sig) + { + return ManifestDisposition::incomplete; + } + + auto const pk = *opt_pk; + auto const spk = *opt_spk; + auto const seq = *opt_seq; + + { + std::lock_guard lock (mutex_); + + /* + "Preflight" the manifest -- before we spend time checking the + signature, make sure we trust the master key and the sequence + number is newer than any we have. + */ + auto const preflight = preflightManifest_locked(pk, seq, journal); + + if (preflight != ManifestDisposition::accepted) + { + return preflight; + } + } + + if (! verify(st, HashPrefix::manifest, pk)) + { + /* + A manifest's signature is invalid. + This shouldn't happen normally. + */ + if (journal.warning) journal.warning + << "Failed to verify manifest #" << seq; + return ManifestDisposition::invalid; + } + + auto& unl = getApp().getUNL(); + + std::lock_guard lock (mutex_); + + /* + We released the lock above, so we have to preflight again, in case + another thread accepted a newer manifest. + */ + auto const preflight = preflightManifest_locked(pk, seq, journal); + + if (preflight != ManifestDisposition::accepted) + { + return preflight; + } + + auto const iter = map_.find(pk); + + auto& old = iter->second.m; + + /* + The maximum possible sequence number means that the master key + has been revoked. + */ + auto const revoked = std::uint32_t (-1); + + if (! old) + { + /* + This is the first received manifest for a trusted master key + (possibly our own). This only happens once per validator per + run (and possibly not at all, if there's an obsolete entry in + [validator_keys] for a validator that no longer exists). + */ + if (journal.info) journal.info + << "Adding new manifest #" << seq; + } + else + { + if (seq == revoked) + { + /* + The MASTER key for this validator was revoked. This is + expected, but should happen at most *very* rarely. + */ + if (journal.warning) journal.warning + << "Dropping old manifest #" << old->seq + << " because the master key was revoked"; + } + else + { + /* + An ephemeral key was revoked and superseded by a new key. + This is expected, but should happen infrequently. + */ + if (journal.warning) journal.warning + << "Dropping old manifest #" << old->seq + << " in favor of #" << seq; + } + + unl.deleteEphemeralKey (old->signingKey); + } + + if (seq == revoked) + { + // The master key is revoked -- don't insert the signing key + if (auto const& j = journal.warning) + j << "Revoking master key: " << pk; + + /* + A validator master key has been compromised, so its manifests + are now untrustworthy. In order to prevent us from accepting + a forged manifest signed by the compromised master key, store + this manifest, which has the highest possible sequence number + and therefore can't be superseded by a forged one. + */ + } + else + { + unl.insertEphemeralKey (spk, iter->second.comment); + } + + old = Manifest(std::move (s), std::move (pk), std::move (spk), seq); + + return ManifestDisposition::accepted; +} + +} diff --git a/src/ripple/overlay/impl/Manifest.h b/src/ripple/overlay/impl/Manifest.h new file mode 100644 index 000000000..cfd7e5d61 --- /dev/null +++ b/src/ripple/overlay/impl/Manifest.h @@ -0,0 +1,187 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_OVERLAY_MANIFEST_H_INCLUDED +#define RIPPLE_OVERLAY_MANIFEST_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +/* + Validator key manifests + ----------------------- + + First, a rationale: Suppose a system adminstrator leaves the company. + You err on the side of caution (if not paranoia) and assume that the + secret keys installed on Ripple validators are compromised. Not only + do you have to generate and install new key pairs on each validator, + EVERY rippled needs to have its config updated with the new public keys, + and is vulnerable to forged validation signatures until this is done. + The solution is a new layer of indirection: A master secret key under + restrictive access control is used to sign a "manifest": essentially, a + certificate including the master public key, an ephemeral public key for + verifying validations (which will be signed by its secret counterpart), + a sequence number, and a digital signature. + + The manifest has two serialized forms: one which includes the digital + signature and one which doesn't. There is an obvious causal dependency + relationship between the (latter) form with no signature, the signature + of that form, and the (former) form which includes that signature. In + other words, a message can't contain a signature of itself. The code + below stores a serialized manifest which includes the signature, and + dynamically generates the signatureless form when it needs to verify + the signature. + + There are two stores of information within rippled related to manifests. + An instance of ManifestCache stores, for each trusted validator, (a) its + master public key, and (b) the most senior of all valid manifests it has + seen for that validator, if any. On startup, the [validator_keys] config + entries are used to prime the manifest cache with the trusted master keys. + At this point, the manifest cache has all the entries it will ever have, + but none of them have manifests. The [validation_manifest] config entry + (which is the manifest for this validator) is then decoded and added to + the manifest cache. Other manifests are added as "gossip" is received + from rippled peers. + + The other data store (which does not involve manifests per se) contains + the set of active ephemeral validator keys. Keys are added to the set + when a manifest is accepted, and removed when that manifest is obsoleted. + + When an ephemeral key is compromised, a new signing key pair is created, + along with a new manifest vouching for it (with a higher sequence number), + signed by the master key. When a rippled peer receives the new manifest, + it verifies it with the master key and (assuming it's valid) discards the + old ephemeral key and stores the new one. If the master key itself gets + compromised, a manifest with sequence number 0xFFFFFFFF will supersede a + prior manifest and discard any existing ephemeral key without storing a + new one. Since no further manifests for this master key will be accepted + (since no higher sequence number is possible), and no signing key is on + record, no validations will be accepted from the compromised validator. +*/ + +//------------------------------------------------------------------------------ + +struct Manifest +{ + std::string serialized; + AnyPublicKey masterKey; + AnyPublicKey signingKey; + std::uint32_t seq; + + Manifest(std::string s, AnyPublicKey pk, AnyPublicKey spk, std::uint32_t seq) + : serialized(std::move(s)) + , masterKey(std::move(pk)) + , signingKey(std::move(spk)) + , seq(seq) + { + } + +#ifdef _MSC_VER + Manifest(Manifest&& other) + : serialized(std::move(other.serialized)) + , masterKey(std::move(other.masterKey)) + , signingKey(std::move(other.signingKey)) + , seq(other.seq) + { + } + + Manifest& operator=(Manifest&& other) + { + serialized = std::move(other.serialized); + masterKey = std::move(other.masterKey); + signingKey = std::move(other.signingKey); + seq = other.seq; + return *this; + } +#else + Manifest(Manifest&& other) = default; + Manifest& operator=(Manifest&& other) = default; +#endif +}; + +enum class ManifestDisposition +{ + accepted = 0, // everything checked out + + malformed, // deserialization fails + incomplete, // fields are missing + untrusted, // manifest declares a master key we don't trust + stale, // trusted master key, but seq is too old + invalid, // trusted and timely, but invalid signature +}; + +/** Remembers manifests with the highest sequence number. */ +class ManifestCache +{ +private: + struct MappedType + { + MappedType() = default; + + std::string comment; + boost::optional m; + }; + + using MapType = hash_map; + + mutable std::mutex mutex_; + MapType map_; + + ManifestDisposition + preflightManifest_locked (AnyPublicKey const& pk, std::uint32_t seq, + beast::Journal const& journal) const; + +public: + ManifestCache() = default; + ManifestCache (ManifestCache const&) = delete; + ManifestCache& operator= (ManifestCache const&) = delete; + ~ManifestCache() = default; + + void configValidatorKey(std::string const& line, beast::Journal const& journal); + void configManifest(std::string s, beast::Journal const& journal); + + void addTrustedKey (AnyPublicKey const& pk, std::string const& comment); + + ManifestDisposition + applyManifest (std::string s, beast::Journal const& journal); + + // A "for_each" for populated manifests only + template + void + for_each_manifest(Function&& f) const + { + std::lock_guard lock (mutex_); + for (auto const& e : map_) + { + if (auto const& m = e.second.m) + f(*m); + } + } +}; + +} // ripple + +#endif diff --git a/src/ripple/overlay/impl/OverlayImpl.cpp b/src/ripple/overlay/impl/OverlayImpl.cpp index eb3d25d55..45abe4220 100644 --- a/src/ripple/overlay/impl/OverlayImpl.cpp +++ b/src/ripple/overlay/impl/OverlayImpl.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -425,9 +426,47 @@ OverlayImpl::checkStopped () stopped(); } +static +void +prepareValidatorKeyManifests (ManifestCache& mc, beast::Journal const& journal) +{ + auto const validator_keys = getConfig().section("validator_keys"); + auto const validation_manifest = getConfig().section("validation_manifest"); + + if (! validator_keys.lines().empty()) + { + for (auto const& line : validator_keys.lines()) + { + mc.configValidatorKey (line, journal); + } + } + else + { + if (journal.warning) + journal.warning << "[validator_keys] is empty"; + } + + if (! validation_manifest.lines().empty()) + { + std::string s; + for (auto const& line : validation_manifest.lines()) + s += beast::rfc2616::trim(line); + s = beast::base64_decode(s); + mc.configManifest(std::move(s), journal); + } + else + { + if (journal.warning) + journal.warning << "No [validation_manifest] section in config"; + } + +} + void OverlayImpl::onPrepare() { + prepareValidatorKeyManifests (manifestCache_, journal_); + PeerFinder::Config config; if (getConfig ().PEERS_MAX != 0) @@ -576,6 +615,59 @@ OverlayImpl::onPeerDeactivate (Peer::id_t id, m_publicKeyMap.erase(publicKey); } +void +OverlayImpl::onManifests (Job&, + std::shared_ptr const& inbox, + std::shared_ptr const& from) +{ + auto& hashRouter = getApp().getHashRouter(); + auto const n = inbox->list_size(); + auto const& journal = from->pjournal(); + + if (journal.debug) journal.debug + << "TMManifest, " << n << (n == 1 ? " item" : " items"); + + protocol::TMManifests outbox; + + for (std::size_t i = 0; i < n; ++i) + { + auto& s = inbox->list().Get(i).stobject(); + + uint256 const hash = getSHA512Half (s); + + auto const result = manifestCache_.applyManifest (s, journal); + + if (result == ManifestDisposition::accepted) + { + protocol::TMManifests outbox; + + outbox.add_list()->set_stobject(s); + + auto const msg = std::make_shared(outbox, protocol::mtMANIFESTS); + + for_each( [&](std::shared_ptr const& peer) + { + if (hashRouter.addSuppressionPeer (hash, peer->id()) && peer != from) + { + if (auto& j = peer->pjournal().warning) + j << "Forwarding manifest with hash " << hash; + peer->send(msg); + } + else if (peer != from) + { + if (auto& j = peer->pjournal().warning) + j << "Suppressed manifest with hash " << hash; + } + }); + } + else + { + if (journal.info) journal.info + << "Bad manifest #" << i + 1; + } + } +} + std::size_t OverlayImpl::selectPeers (PeerSet& set, std::size_t limit, std::function const&)> score) diff --git a/src/ripple/overlay/impl/OverlayImpl.h b/src/ripple/overlay/impl/OverlayImpl.h index 4b4f1d7e6..5ee94c18d 100644 --- a/src/ripple/overlay/impl/OverlayImpl.h +++ b/src/ripple/overlay/impl/OverlayImpl.h @@ -20,7 +20,9 @@ #ifndef RIPPLE_OVERLAY_OVERLAYIMPL_H_INCLUDED #define RIPPLE_OVERLAY_OVERLAYIMPL_H_INCLUDED +#include #include +#include #include #include #include @@ -113,7 +115,7 @@ private: hash_map> m_shortIdMap; Resolver& m_resolver; std::atomic next_id_; - + ManifestCache manifestCache_; int timer_count_; //-------------------------------------------------------------------------- @@ -147,6 +149,12 @@ public: return serverHandler_; } + ManifestCache const& + manifestCache() const + { + return manifestCache_; + } + Setup const& setup() const { @@ -234,6 +242,12 @@ public: selectPeers (PeerSet& set, std::size_t limit, std::function< bool(std::shared_ptr const&)> score) override; + // Called when TMManifests is received from a peer + void + onManifests (Job&, + std::shared_ptr const& m, + std::shared_ptr const& from); + static bool isPeerUpgrade (beast::http::message const& request); diff --git a/src/ripple/overlay/impl/PeerImp.cpp b/src/ripple/overlay/impl/PeerImp.cpp index d5e4a1efc..87de80bf5 100644 --- a/src/ripple/overlay/impl/PeerImp.cpp +++ b/src/ripple/overlay/impl/PeerImp.cpp @@ -653,6 +653,22 @@ void PeerImp::doProtocolStart() { onReadMessage(error_code(), 0); + + protocol::TMManifests tm; + + overlay_.manifestCache().for_each_manifest( + [&](Manifest const& manifest) + { + auto const& s = manifest.serialized; + auto& tm_e = *tm.add_list(); + tm_e.set_stobject(s.data(), s.size()); + }); + + if (tm.list_size() > 0) + { + auto m = std::make_shared(tm, protocol::mtMANIFESTS); + send (m); + } } // Called repeatedly with protocol message data @@ -778,6 +794,15 @@ PeerImp::onMessage (std::shared_ptr const& m) fail("Deprecated TMHello"); } +void +PeerImp::onMessage (std::shared_ptr const& m) +{ + // VFALCO What's the right job type? + getApp().getJobQueue().addJob (jtVALIDATION_ut, + "receiveManifests", std::bind(&OverlayImpl::onManifests, + &overlay_, std::placeholders::_1, m, shared_from_this())); +} + void PeerImp::onMessage (std::shared_ptr const& m) { diff --git a/src/ripple/overlay/impl/PeerImp.h b/src/ripple/overlay/impl/PeerImp.h index bcd60f246..18bccf9dd 100644 --- a/src/ripple/overlay/impl/PeerImp.h +++ b/src/ripple/overlay/impl/PeerImp.h @@ -188,6 +188,12 @@ public: virtual ~PeerImp(); + beast::Journal const& + pjournal() const + { + return p_journal_; + } + PeerFinder::Slot::ptr const& slot() { @@ -395,6 +401,7 @@ public: std::shared_ptr <::google::protobuf::Message> const& m); void onMessage (std::shared_ptr const& m); + void onMessage (std::shared_ptr const& m); void onMessage (std::shared_ptr const& m); void onMessage (std::shared_ptr const& m); void onMessage (std::shared_ptr const& m); diff --git a/src/ripple/overlay/impl/ProtocolMessage.h b/src/ripple/overlay/impl/ProtocolMessage.h index 12a69af3d..0f3586022 100644 --- a/src/ripple/overlay/impl/ProtocolMessage.h +++ b/src/ripple/overlay/impl/ProtocolMessage.h @@ -42,6 +42,7 @@ protocolMessageName (int type) switch (type) { case protocol::mtHELLO: return "hello"; + case protocol::mtMANIFESTS: return "manifests"; case protocol::mtPING: return "ping"; case protocol::mtPROOFOFWORK: return "proof_of_work"; case protocol::mtCLUSTER: return "cluster"; @@ -112,6 +113,7 @@ invokeProtocolMessage (Buffers const& buffers, Handler& handler) switch (type) { case protocol::mtHELLO: ec = detail::invoke (type, buffers, handler); break; + case protocol::mtMANIFESTS: ec = detail::invoke (type, buffers, handler); break; case protocol::mtPING: ec = detail::invoke (type, buffers, handler); break; case protocol::mtCLUSTER: ec = detail::invoke (type, buffers, handler); break; case protocol::mtGET_PEERS: ec = detail::invoke (type, buffers, handler); break; diff --git a/src/ripple/overlay/tests/manifest_test.cpp b/src/ripple/overlay/tests/manifest_test.cpp new file mode 100644 index 000000000..c49cafb86 --- /dev/null +++ b/src/ripple/overlay/tests/manifest_test.cpp @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright 2014 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include + +namespace ripple { +namespace tests { + +class manifest_test : public ripple::TestSuite +{ +public: + // Return a manifest in both serialized and STObject form + std::string + make_manifest(AnySecretKey const& sk, AnyPublicKey const& spk, int seq, bool broken = false) + { + auto const pk = sk.publicKey(); + + STObject st(sfGeneric); + set(st, sfSequence, seq); + set(st, sfPublicKey, pk); + set(st, sfSigningPubKey, spk); + + sign(st, HashPrefix::manifest, sk); + expect(verify(st, HashPrefix::manifest, pk)); + + if (broken) + { + set(st, sfSequence, seq + 1); + } + + Serializer s; + st.add(s); + + std::string const m (static_cast (s.data()), s.size()); + return m; + } + + void + run() override + { + auto const accepted = ManifestDisposition::accepted; + auto const malformed = ManifestDisposition::malformed; + auto const untrusted = ManifestDisposition::untrusted; + auto const stale = ManifestDisposition::stale; + auto const invalid = ManifestDisposition::invalid; + + beast::Journal journal; + + auto const sk_a = AnySecretKey::make_ed25519(); + auto const sk_b = AnySecretKey::make_ed25519(); + auto const pk_a = sk_a.publicKey(); + auto const pk_b = sk_b.publicKey(); + auto const kp_a = AnySecretKey::make_secp256k1_pair(); + auto const kp_b = AnySecretKey::make_secp256k1_pair(); + + auto const s_a0 = make_manifest(sk_a, kp_a.second, 0); + auto const s_a1 = make_manifest(sk_a, kp_a.second, 1); + auto const s_b0 = make_manifest(sk_b, kp_b.second, 0); + auto const s_b1 = make_manifest(sk_b, kp_b.second, 1); + auto const s_b2 = make_manifest(sk_b, kp_b.second, 2, true); // broken + auto const fake = s_b1 + '\0'; + + ManifestCache cache; + + expect(cache.applyManifest(s_a0, journal) == untrusted, "have to install a trusted key first"); + + cache.addTrustedKey(pk_a, "a"); + cache.addTrustedKey(pk_b, "b"); + + expect(cache.applyManifest(s_a0, journal) == accepted); + expect(cache.applyManifest(s_a0, journal) == stale); + + expect(cache.applyManifest(s_a1, journal) == accepted); + expect(cache.applyManifest(s_a1, journal) == stale); + expect(cache.applyManifest(s_a0, journal) == stale); + + expect(cache.applyManifest(s_b0, journal) == accepted); + expect(cache.applyManifest(s_b0, journal) == stale); + + expect(cache.applyManifest(fake, journal) == malformed); + expect(cache.applyManifest(s_b2, journal) == invalid); + } +}; + +BEAST_DEFINE_TESTSUITE(manifest,overlay,ripple); + +} // tests +} // ripple diff --git a/src/ripple/proto/ripple.proto b/src/ripple/proto/ripple.proto index dad8e1037..b2b215223 100644 --- a/src/ripple/proto/ripple.proto +++ b/src/ripple/proto/ripple.proto @@ -3,6 +3,7 @@ package protocol; enum MessageType { mtHELLO = 1; + mtMANIFESTS = 2; mtPING = 3; mtPROOFOFWORK = 4; mtCLUSTER = 5; @@ -18,7 +19,6 @@ enum MessageType mtVALIDATION = 41; mtGET_OBJECTS = 42; - // = 2; // = 10; // = 11; // = 14; @@ -34,6 +34,20 @@ enum MessageType //------------------------------------------------------------------------------ +/* Provides the current ephemeral key for a validator. */ +message TMManifest +{ + // A Manifest object in the Ripple serialization format. + required bytes stobject = 1; +} + +message TMManifests +{ + repeated TMManifest list = 1; +} + +//------------------------------------------------------------------------------ + /* Requests or responds to a proof of work. Unimplemented and unused currently. */ diff --git a/src/ripple/protocol/HashPrefix.h b/src/ripple/protocol/HashPrefix.h index dc0cd9495..5b34118c4 100644 --- a/src/ripple/protocol/HashPrefix.h +++ b/src/ripple/protocol/HashPrefix.h @@ -92,6 +92,9 @@ public: /** proposal for signing */ static HashPrefix const proposal; + + /** Manifest */ + static HashPrefix const manifest; }; } // ripple diff --git a/src/ripple/protocol/impl/HashPrefix.cpp b/src/ripple/protocol/impl/HashPrefix.cpp index 5443918fa..f6d7aa3df 100644 --- a/src/ripple/protocol/impl/HashPrefix.cpp +++ b/src/ripple/protocol/impl/HashPrefix.cpp @@ -34,5 +34,6 @@ HashPrefix const HashPrefix::txSign ('S', 'T', 'X'); HashPrefix const HashPrefix::txMultiSign ('S', 'M', 'T'); HashPrefix const HashPrefix::validation ('V', 'A', 'L'); HashPrefix const HashPrefix::proposal ('P', 'R', 'P'); +HashPrefix const HashPrefix::manifest ('M', 'A', 'N'); } // ripple diff --git a/src/ripple/unity/overlay.cpp b/src/ripple/unity/overlay.cpp index 0e94406c7..24bb9c45e 100644 --- a/src/ripple/unity/overlay.cpp +++ b/src/ripple/unity/overlay.cpp @@ -20,12 +20,14 @@ #include #include +#include #include #include #include #include #include +#include #include #include