Files
rippled/src/test/app/Manifest_test.cpp
Bart 1eb0fdac65 refactor: Rename ripple namespace to xrpl (#5982)
This change renames all occurrences of `namespace ripple` and `ripple::` to `namespace xrpl` and `xrpl::`, respectively, as well as the names of test suites. It also provides a script to allow developers to replicate the changes in their local branch or fork to avoid conflicts.
2025-12-11 16:51:49 +00:00

1073 lines
38 KiB
C++

#include <test/jtx.h>
#include <xrpld/app/main/DBInit.h>
#include <xrpld/app/misc/Manifest.h>
#include <xrpld/app/misc/ValidatorList.h>
#include <xrpld/app/rdb/Wallet.h>
#include <xrpl/basics/base64.h>
#include <xrpl/basics/contract.h>
#include <xrpl/protocol/STExchange.h>
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/Sign.h>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/utility/in_place_factory.hpp>
namespace xrpl {
namespace test {
class Manifest_test : public beast::unit_test::suite
{
private:
static PublicKey
randomNode()
{
return derivePublicKey(KeyType::secp256k1, randomSecretKey());
}
static PublicKey
randomMasterKey()
{
return derivePublicKey(KeyType::ed25519, randomSecretKey());
}
static void
cleanupDatabaseDir(boost::filesystem::path const& dbPath)
{
using namespace boost::filesystem;
if (!exists(dbPath) || !is_directory(dbPath) || !is_empty(dbPath))
return;
remove(dbPath);
}
static void
setupDatabaseDir(boost::filesystem::path const& dbPath)
{
using namespace boost::filesystem;
if (!exists(dbPath))
{
create_directory(dbPath);
return;
}
if (!is_directory(dbPath))
{
// someone created a file where we want to put our directory
Throw<std::runtime_error>(
"Cannot create directory: " + dbPath.string());
}
}
static boost::filesystem::path
getDatabasePath()
{
return boost::filesystem::current_path() / "manifest_test_databases";
}
public:
Manifest_test()
{
try
{
setupDatabaseDir(getDatabasePath());
}
catch (std::exception const&)
{
}
}
~Manifest_test()
{
try
{
cleanupDatabaseDir(getDatabasePath());
}
catch (std::exception const&)
{
}
}
std::string
makeManifestString(
PublicKey const& pk,
SecretKey const& sk,
PublicKey const& spk,
SecretKey const& ssk,
int seq)
{
STObject st(sfGeneric);
st[sfSequence] = seq;
st[sfPublicKey] = pk;
st[sfSigningPubKey] = spk;
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
sign(
st,
HashPrefix::manifest,
*publicKeyType(pk),
sk,
sfMasterSignature);
Serializer s;
st.add(s);
return base64_encode(
std::string(static_cast<char const*>(s.data()), s.size()));
}
std::string
makeRevocationString(
SecretKey const& sk,
KeyType type,
bool invalidSig = false)
{
auto const pk = derivePublicKey(type, sk);
STObject st(sfGeneric);
st[sfSequence] = std::numeric_limits<std::uint32_t>::max();
st[sfPublicKey] = pk;
sign(
st,
HashPrefix::manifest,
type,
invalidSig ? randomSecretKey() : sk,
sfMasterSignature);
BEAST_EXPECT(
invalidSig ^
verify(st, HashPrefix::manifest, pk, sfMasterSignature));
Serializer s;
st.add(s);
return base64_encode(
std::string(static_cast<char const*>(s.data()), s.size()));
}
Manifest
makeRevocation(SecretKey const& sk, KeyType type, bool invalidSig = false)
{
auto const pk = derivePublicKey(type, sk);
STObject st(sfGeneric);
st[sfSequence] = std::numeric_limits<std::uint32_t>::max();
st[sfPublicKey] = pk;
sign(
st,
HashPrefix::manifest,
type,
invalidSig ? randomSecretKey() : sk,
sfMasterSignature);
BEAST_EXPECT(
invalidSig ^
verify(st, HashPrefix::manifest, pk, sfMasterSignature));
Serializer s;
st.add(s);
// m is non-const so it can be moved from
std::string m(static_cast<char const*>(s.data()), s.size());
if (auto r = deserializeManifest(std::move(m)))
return std::move(*r);
Throw<std::runtime_error>("Could not create a revocation manifest");
return *deserializeManifest(
std::string{}); // Silence compiler warning.
}
Manifest
makeManifest(
SecretKey const& sk,
KeyType type,
SecretKey const& ssk,
KeyType stype,
int seq,
bool invalidSig = false)
{
auto const pk = derivePublicKey(type, sk);
auto const spk = derivePublicKey(stype, ssk);
STObject st(sfGeneric);
st[sfSequence] = seq;
st[sfPublicKey] = pk;
st[sfSigningPubKey] = spk;
sign(st, HashPrefix::manifest, stype, ssk);
BEAST_EXPECT(verify(st, HashPrefix::manifest, spk));
sign(
st,
HashPrefix::manifest,
type,
invalidSig ? randomSecretKey() : sk,
sfMasterSignature);
BEAST_EXPECT(
invalidSig ^
verify(st, HashPrefix::manifest, pk, sfMasterSignature));
Serializer s;
st.add(s);
std::string m(
static_cast<char const*>(s.data()),
s.size()); // non-const so can be moved
if (auto r = deserializeManifest(std::move(m)))
return std::move(*r);
Throw<std::runtime_error>("Could not create a manifest");
return *deserializeManifest(
std::string{}); // Silence compiler warning.
}
Manifest
clone(Manifest const& m)
{
Manifest m2(
m.serialized, m.masterKey, m.signingKey, m.sequence, m.domain);
return m2;
}
void
testLoadStore(ManifestCache& m)
{
testcase("load/store");
std::string const dbName("ManifestCacheTestDB");
{
jtx::Env env(*this);
DatabaseCon::Setup setup;
setup.dataDir = getDatabasePath();
assert(!setup.useGlobalPragma);
auto dbCon = makeTestWalletDB(setup, dbName, env.journal);
auto getPopulatedManifests =
[](ManifestCache const& cache) -> std::vector<Manifest const*> {
std::vector<Manifest const*> result;
result.reserve(32);
cache.for_each_manifest(
[&result](Manifest const& man) { result.push_back(&man); });
return result;
};
auto sort = [](std::vector<Manifest const*> mv)
-> std::vector<Manifest const*> {
std::sort(
mv.begin(),
mv.end(),
[](Manifest const* lhs, Manifest const* rhs) {
return lhs->serialized < rhs->serialized;
});
return mv;
};
std::vector<Manifest const*> const inManifests(
sort(getPopulatedManifests(m)));
auto& app = env.app();
auto unl = std::make_unique<ValidatorList>(
m,
m,
env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
{
// save should not store untrusted master keys to db
// except for revocations
m.save(
*dbCon,
"ValidatorManifests",
[&unl](PublicKey const& pubKey) {
return unl->listed(pubKey);
});
ManifestCache loaded;
loaded.load(*dbCon, "ValidatorManifests");
// check that all loaded manifests are revocations
std::vector<Manifest const*> const loadedManifests(
sort(getPopulatedManifests(loaded)));
for (auto const& man : loadedManifests)
BEAST_EXPECT(man->revoked());
}
{
// save should store all trusted master keys to db
std::vector<std::string> s1;
std::vector<std::string> keys;
std::string cfgManifest;
for (auto const& man : inManifests)
s1.push_back(
toBase58(TokenType::NodePublic, man->masterKey));
unl->load({}, s1, keys);
m.save(
*dbCon,
"ValidatorManifests",
[&unl](PublicKey const& pubKey) {
return unl->listed(pubKey);
});
ManifestCache loaded;
loaded.load(*dbCon, "ValidatorManifests");
// check that the manifest caches are the same
std::vector<Manifest const*> const loadedManifests(
sort(getPopulatedManifests(loaded)));
if (inManifests.size() == loadedManifests.size())
{
BEAST_EXPECT(std::equal(
inManifests.begin(),
inManifests.end(),
loadedManifests.begin(),
[](Manifest const* lhs, Manifest const* rhs) {
return *lhs == *rhs;
}));
}
else
{
fail();
}
}
{
// load config manifest
ManifestCache loaded;
std::vector<std::string> const emptyRevocation;
std::string const badManifest = "bad manifest";
BEAST_EXPECT(!loaded.load(
*dbCon,
"ValidatorManifests",
badManifest,
emptyRevocation));
auto const sk = randomSecretKey();
auto const pk = derivePublicKey(KeyType::ed25519, sk);
auto const kp = randomKeyPair(KeyType::secp256k1);
std::string const cfgManifest =
makeManifestString(pk, sk, kp.first, kp.second, 0);
BEAST_EXPECT(loaded.load(
*dbCon,
"ValidatorManifests",
cfgManifest,
emptyRevocation));
}
{
// load config revocation
ManifestCache loaded;
std::string const emptyManifest;
std::vector<std::string> const badRevocation = {
"bad revocation"};
BEAST_EXPECT(!loaded.load(
*dbCon,
"ValidatorManifests",
emptyManifest,
badRevocation));
auto const sk = randomSecretKey();
auto const keyType = KeyType::ed25519;
auto const pk = derivePublicKey(keyType, sk);
auto const kp = randomKeyPair(KeyType::secp256k1);
std::vector<std::string> const nonRevocation = {
makeManifestString(pk, sk, kp.first, kp.second, 0)};
BEAST_EXPECT(!loaded.load(
*dbCon,
"ValidatorManifests",
emptyManifest,
nonRevocation));
BEAST_EXPECT(!loaded.revoked(pk));
std::vector<std::string> const badSigRevocation = {
makeRevocationString(sk, keyType, true)};
BEAST_EXPECT(!loaded.load(
*dbCon,
"ValidatorManifests",
emptyManifest,
badSigRevocation));
BEAST_EXPECT(!loaded.revoked(pk));
std::vector<std::string> const cfgRevocation = {
makeRevocationString(sk, keyType)};
BEAST_EXPECT(loaded.load(
*dbCon,
"ValidatorManifests",
emptyManifest,
cfgRevocation));
BEAST_EXPECT(loaded.revoked(pk));
}
}
boost::filesystem::remove(
getDatabasePath() / boost::filesystem::path(dbName));
}
void
testGetSignature()
{
testcase("getSignature");
auto const sk = randomSecretKey();
auto const pk = derivePublicKey(KeyType::ed25519, sk);
auto const kp = randomKeyPair(KeyType::secp256k1);
auto const m = makeManifest(
sk, KeyType::ed25519, kp.second, KeyType::secp256k1, 0);
STObject st(sfGeneric);
st[sfSequence] = 0;
st[sfPublicKey] = pk;
st[sfSigningPubKey] = kp.first;
Serializer ss;
ss.add32(HashPrefix::manifest);
st.addWithoutSigningFields(ss);
auto const sig = sign(KeyType::secp256k1, kp.second, ss.slice());
BEAST_EXPECT(strHex(sig) == strHex(*m.getSignature()));
auto const masterSig = sign(KeyType::ed25519, sk, ss.slice());
BEAST_EXPECT(strHex(masterSig) == strHex(m.getMasterSignature()));
}
void
testGetKeys()
{
testcase("getKeys");
ManifestCache cache;
auto const sk = randomSecretKey();
auto const pk = derivePublicKey(KeyType::ed25519, sk);
// getSigningKey should return same key if there is no manifest
BEAST_EXPECT(cache.getSigningKey(pk) == pk);
// getSigningKey should return the ephemeral public key
// for the listed validator master public key
// getMasterKey should return the listed validator master key
// for that ephemeral public key
auto const kp0 = randomKeyPair(KeyType::secp256k1);
BEAST_EXPECT(
ManifestDisposition::accepted ==
cache.applyManifest(makeManifest(
sk, KeyType::ed25519, kp0.second, KeyType::secp256k1, 0)));
BEAST_EXPECT(cache.getSigningKey(pk) == kp0.first);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == pk);
// getSigningKey should return the latest ephemeral public key
// for the listed validator master public key
// getMasterKey should only return a master key for the latest
// ephemeral public key
auto const kp1 = randomKeyPair(KeyType::secp256k1);
BEAST_EXPECT(
ManifestDisposition::accepted ==
cache.applyManifest(makeManifest(
sk, KeyType::ed25519, kp1.second, KeyType::secp256k1, 1)));
BEAST_EXPECT(cache.getSigningKey(pk) == kp1.first);
BEAST_EXPECT(cache.getMasterKey(kp1.first) == pk);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
// getSigningKey and getMasterKey should fail if a new manifest is
// applied with the same signing key but a higher sequence
BEAST_EXPECT(
ManifestDisposition::badEphemeralKey ==
cache.applyManifest(makeManifest(
sk, KeyType::ed25519, kp1.second, KeyType::secp256k1, 2)));
BEAST_EXPECT(cache.getSigningKey(pk) == kp1.first);
BEAST_EXPECT(cache.getMasterKey(kp1.first) == pk);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
// getSigningKey should return std::nullopt for a revoked master public
// key getMasterKey should return std::nullopt for an ephemeral public
// key from a revoked master public key
BEAST_EXPECT(
ManifestDisposition::accepted ==
cache.applyManifest(makeRevocation(sk, KeyType::ed25519)));
BEAST_EXPECT(cache.revoked(pk));
BEAST_EXPECT(cache.getSigningKey(pk) == pk);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
BEAST_EXPECT(cache.getMasterKey(kp1.first) == kp1.first);
}
void
testValidatorToken()
{
testcase("validator token");
{
auto const valSecret = parseBase58<SecretKey>(
TokenType::NodePrivate,
"paQmjZ37pKKPMrgadBLsuf9ab7Y7EUNzh27LQrZqoexpAs31nJi");
// Format token string to test trim()
std::vector<std::string> const tokenBlob = {
" "
"eyJ2YWxpZGF0aW9uX3NlY3JldF9rZXkiOiI5ZWQ0NWY4NjYyNDFjYzE4YTI3ND"
"diNT\n",
" \tQzODdjMDYyNTkwNzk3MmY0ZTcxOTAyMzFmYWE5Mzc0NTdmYTlkYWY2Iiwib"
"WFuaWZl \n",
"\tc3QiOiJKQUFBQUFGeEllMUZ0d21pbXZHdEgyaUNjTUpxQzlnVkZLaWxHZncx"
"L3ZDeE\n",
"\t "
"hYWExwbGMyR25NaEFrRTFhZ3FYeEJ3RHdEYklENk9NU1l1TTBGREFscEFnTms4"
"U0tG\t \t\n",
"bjdNTzJmZGtjd1JRSWhBT25ndTlzQUtxWFlvdUorbDJWMFcrc0FPa1ZCK1pSUz"
"ZQU2\n",
"hsSkFmVXNYZkFpQnNWSkdlc2FhZE9KYy9hQVpva1MxdnltR21WcmxIUEtXWDNZ"
"eXd1\n",
"NmluOEhBU1FLUHVnQkQ2N2tNYVJGR3ZtcEFUSGxHS0pkdkRGbFdQWXk1QXFEZW"
"RGdj\n",
"VUSmEydzBpMjFlcTNNWXl3TFZKWm5GT3I3QzBrdzJBaVR6U0NqSXpkaXRROD0i"
"fQ==\n"};
auto const manifest =
"JAAAAAFxIe1FtwmimvGtH2iCcMJqC9gVFKilGfw1/"
"vCxHXXLplc2GnMhAkE1agqXxBwD"
"wDbID6OMSYuM0FDAlpAgNk8SKFn7MO2fdkcwRQIhAOngu9sAKqXYouJ+l2V0W+"
"sAOkVB"
"+ZRS6PShlJAfUsXfAiBsVJGesaadOJc/"
"aAZokS1vymGmVrlHPKWX3Yywu6in8HASQKPu"
"gBD67kMaRFGvmpATHlGKJdvDFlWPYy5AqDedFv5TJa2w0i21eq3MYywLVJZnFO"
"r7C0kw"
"2AiTzSCjIzditQ8=";
auto const token = loadValidatorToken(tokenBlob);
BEAST_EXPECT(token);
BEAST_EXPECT(token->validationSecret == *valSecret);
BEAST_EXPECT(token->manifest == manifest);
}
{
std::vector<std::string> const badToken = {"bad token"};
BEAST_EXPECT(!loadValidatorToken(badToken));
}
}
void
testManifestVersioning()
{
testcase("Versioning");
auto const sk = generateSecretKey(KeyType::ed25519, randomSeed());
auto const pk = derivePublicKey(KeyType::ed25519, sk);
auto const ssk = generateSecretKey(KeyType::secp256k1, randomSeed());
auto const spk = derivePublicKey(KeyType::secp256k1, ssk);
auto buildManifestObject = [&](std::uint16_t version) {
STObject st(sfGeneric);
st[sfSequence] = 3;
st[sfPublicKey] = pk;
st[sfSigningPubKey] = spk;
if (version != 0)
st[sfVersion] = version;
sign(
st,
HashPrefix::manifest,
KeyType::ed25519,
sk,
sfMasterSignature);
sign(st, HashPrefix::manifest, KeyType::secp256k1, ssk);
Serializer s;
st.add(s);
return std::string(static_cast<char const*>(s.data()), s.size());
};
// We understand version 0 manifests:
BEAST_EXPECT(deserializeManifest(buildManifestObject(0)));
// We don't understand any other versions:
BEAST_EXPECT(!deserializeManifest(buildManifestObject(1)));
BEAST_EXPECT(!deserializeManifest(buildManifestObject(2001)));
}
void
testManifestDeserialization()
{
std::array<KeyType, 2> const keyTypes{
{KeyType::ed25519, KeyType::secp256k1}};
std::uint32_t sequence = 0;
// public key with invalid type
std::array<std::uint8_t, 33> const badKey{
0x99, 0x30, 0xE7, 0xFC, 0x9D, 0x56, 0xBB, 0x25, 0xD6, 0x89, 0x3B,
0xA3, 0xF3, 0x17, 0xAE, 0x5B, 0xCF, 0x33, 0xB3, 0x29, 0x1B, 0xD6,
0x3D, 0xB3, 0x26, 0x54, 0xA3, 0x13, 0x22, 0x2F, 0x7F, 0xD0, 0x20};
// Short public key:
std::array<std::uint8_t, 16> const shortKey{
0x03,
0x30,
0xE7,
0xFC,
0x9D,
0x56,
0xBB,
0x25,
0xD6,
0x89,
0x3B,
0xA3,
0xF3,
0x17,
0xAE,
0x5B};
auto toString = [](STObject const& st) {
Serializer s;
st.add(s);
return std::string(static_cast<char const*>(s.data()), s.size());
};
for (auto const keyType : keyTypes)
{
auto const sk = generateSecretKey(keyType, randomSeed());
auto const pk = derivePublicKey(keyType, sk);
for (auto const sKeyType : keyTypes)
{
auto const ssk = generateSecretKey(sKeyType, randomSeed());
auto const spk = derivePublicKey(sKeyType, ssk);
auto buildManifestObject =
[&](std::uint32_t seq,
std::optional<std::string> domain,
bool noSigningPublic = false,
bool noSignature = false) {
STObject st(sfGeneric);
st[sfSequence] = seq;
st[sfPublicKey] = pk;
if (domain)
st[sfDomain] = makeSlice(*domain);
if (!noSigningPublic)
st[sfSigningPubKey] = spk;
sign(
st,
HashPrefix::manifest,
keyType,
sk,
sfMasterSignature);
if (!noSignature)
sign(st, HashPrefix::manifest, sKeyType, ssk);
return st;
};
{
testcase << "deserializeManifest: normal manifest ("
<< to_string(keyType) << " + "
<< to_string(sKeyType) << ")";
{ // valid manifest without domain
auto const st =
buildManifestObject(++sequence, std::nullopt);
auto const m = toString(st);
auto const manifest = deserializeManifest(m);
BEAST_EXPECT(manifest);
BEAST_EXPECT(manifest->masterKey == pk);
BEAST_EXPECT(manifest->signingKey == spk);
BEAST_EXPECT(manifest->sequence == sequence);
BEAST_EXPECT(manifest->serialized == m);
BEAST_EXPECT(manifest->domain.empty());
BEAST_EXPECT(manifest->verify());
}
{ // invalid manifest (empty domain)
auto const st =
buildManifestObject(++sequence, std::string{});
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
{ // invalid manifest (domain too short)
auto const st =
buildManifestObject(++sequence, std::string{"a.b"});
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
{ // invalid manifest (domain too long)
std::string s(254, 'a');
auto const st =
buildManifestObject(++sequence, s + ".example.com");
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
{ // invalid manifest (domain component too long)
std::string s(72, 'a');
auto const st =
buildManifestObject(++sequence, s + ".example.com");
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
auto const st = buildManifestObject(
++sequence, std::string{"example.com"});
{
// valid manifest with domain
auto const m = toString(st);
auto const manifest = deserializeManifest(m);
BEAST_EXPECT(manifest);
BEAST_EXPECT(manifest->masterKey == pk);
BEAST_EXPECT(manifest->signingKey == spk);
BEAST_EXPECT(manifest->sequence == sequence);
BEAST_EXPECT(manifest->serialized == m);
BEAST_EXPECT(manifest->domain == "example.com");
BEAST_EXPECT(manifest->verify());
}
{
// valid manifest with invalid signature
auto badSigSt = st;
badSigSt[sfSequence] = sequence + 1;
auto const m = toString(badSigSt);
auto const manifest = deserializeManifest(m);
BEAST_EXPECT(manifest);
BEAST_EXPECT(manifest->masterKey == pk);
BEAST_EXPECT(manifest->signingKey == spk);
BEAST_EXPECT(manifest->sequence == sequence + 1);
BEAST_EXPECT(manifest->serialized == m);
BEAST_EXPECT(manifest->domain == "example.com");
BEAST_EXPECT(!manifest->verify());
}
{
// reject missing sequence
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfSequence));
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject missing public key
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfPublicKey));
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject invalid public key type
auto badSt = st;
badSt[sfPublicKey] = makeSlice(badKey);
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject short public key
auto badSt = st;
badSt[sfPublicKey] = makeSlice(shortKey);
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject missing signing public key
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfSigningPubKey));
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject invalid signing public key type
auto badSt = st;
badSt[sfSigningPubKey] = makeSlice(badKey);
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject short signing public key
auto badSt = st;
badSt[sfSigningPubKey] = makeSlice(shortKey);
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject missing signature
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfMasterSignature));
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject missing signing key signature
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfSignature));
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject matching master & ephemeral keys
STObject st(sfGeneric);
st[sfSequence] = 314159;
st[sfPublicKey] = pk;
st[sfSigningPubKey] = pk;
sign(
st,
HashPrefix::manifest,
keyType,
sk,
sfMasterSignature);
sign(st, HashPrefix::manifest, sKeyType, sk);
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
}
{
testcase << "deserializeManifest: revocation manifest ("
<< to_string(keyType) << " + "
<< to_string(sKeyType) << ")";
// valid revocation
{
auto const st = buildManifestObject(
std::numeric_limits<std::uint32_t>::max(),
std::nullopt,
true,
true);
auto const m = toString(st);
auto const manifest = deserializeManifest(m);
BEAST_EXPECT(manifest);
BEAST_EXPECT(manifest->masterKey == pk);
// Since this manifest is revoked, it should not have
// a signingKey
BEAST_EXPECT(!manifest->signingKey);
BEAST_EXPECT(manifest->revoked());
BEAST_EXPECT(manifest->domain.empty());
BEAST_EXPECT(manifest->serialized == m);
BEAST_EXPECT(manifest->verify());
}
{ // can't specify an ephemeral signing key
auto const st = buildManifestObject(
std::numeric_limits<std::uint32_t>::max(),
std::nullopt,
true,
false);
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
{ // can't specify an ephemeral signature
auto const st = buildManifestObject(
std::numeric_limits<std::uint32_t>::max(),
std::nullopt,
false,
true);
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
{ // can't specify an ephemeral key & signature
auto const st = buildManifestObject(
std::numeric_limits<std::uint32_t>::max(),
std::nullopt,
false,
false);
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
}
}
}
}
void
testManifestDomainNames()
{
testcase("Manifest Domain Names");
auto const sk1 = generateSecretKey(KeyType::secp256k1, randomSeed());
auto const pk1 = derivePublicKey(KeyType::secp256k1, sk1);
auto const sk2 = generateSecretKey(KeyType::secp256k1, randomSeed());
auto const pk2 = derivePublicKey(KeyType::secp256k1, sk2);
auto test = [&](std::string domain) {
STObject st(sfGeneric);
st[sfSequence] = 7;
st[sfPublicKey] = pk1;
st[sfDomain] = makeSlice(domain);
st[sfSigningPubKey] = pk2;
sign(
st,
HashPrefix::manifest,
KeyType::secp256k1,
sk1,
sfMasterSignature);
sign(st, HashPrefix::manifest, KeyType::secp256k1, sk2);
Serializer s;
st.add(s);
return deserializeManifest(
std::string(static_cast<char const*>(s.data()), s.size()));
};
BEAST_EXPECT(test("example.com"));
BEAST_EXPECT(test("test.example.com"));
BEAST_EXPECT(test("example-domain.com"));
BEAST_EXPECT(test("xn--mxavchb.gr"));
BEAST_EXPECT(test("test.xn--mxavchb.gr"));
BEAST_EXPECT(test("123.gr"));
BEAST_EXPECT(test("x.yz"));
BEAST_EXPECT(test(std::string(63, 'a') + ".example.com"));
BEAST_EXPECT(test(std::string(63, 'a') + "." + std::string(63, 'b')));
// No period
BEAST_EXPECT(!test("example"));
// Leading period:
BEAST_EXPECT(!test(".com"));
BEAST_EXPECT(!test(".example.com"));
// A trailing period is technically valid but we don't allow it
BEAST_EXPECT(!test("example.com."));
// A component can't start or end with a dash
BEAST_EXPECT(!test("-example.com"));
BEAST_EXPECT(!test("example-.com"));
// Empty component:
BEAST_EXPECT(!test("double..periods.example.com"));
// TLD too short or too long:
BEAST_EXPECT(!test("example.x"));
BEAST_EXPECT(!test("example." + std::string(64, 'a')));
// Invalid characters:
BEAST_EXPECT(!test("example.com-org"));
BEAST_EXPECT(!test("bang!.com"));
BEAST_EXPECT(!test("bang!.example.com"));
// Too short
BEAST_EXPECT(!test("a.b"));
// Single component too long:
BEAST_EXPECT(!test(std::string(64, 'a') + ".com"));
BEAST_EXPECT(!test(std::string(64, 'a') + ".example.com"));
// Multiple components too long:
BEAST_EXPECT(!test(std::string(64, 'a') + "." + std::string(64, 'b')));
BEAST_EXPECT(!test(std::string(64, 'a') + "." + std::string(64, 'b')));
// Overall too long:
BEAST_EXPECT(!test(
std::string(63, 'a') + "." + std::string(63, 'b') +
".example.com"));
}
void
run() override
{
ManifestCache cache;
{
testcase("apply");
auto const sk_a = randomSecretKey();
auto const pk_a = derivePublicKey(KeyType::ed25519, sk_a);
auto const kp_a0 = randomKeyPair(KeyType::secp256k1);
auto const kp_a1 = randomKeyPair(KeyType::secp256k1);
auto const s_a0 = makeManifest(
sk_a, KeyType::ed25519, kp_a0.second, KeyType::secp256k1, 0);
auto const s_a1 = makeManifest(
sk_a, KeyType::ed25519, kp_a1.second, KeyType::secp256k1, 1);
auto const s_a2 = makeManifest(
sk_a, KeyType::ed25519, kp_a1.second, KeyType::secp256k1, 2);
auto const s_aMax = makeRevocation(sk_a, KeyType::ed25519);
auto const sk_b = randomSecretKey();
auto const kp_b0 = randomKeyPair(KeyType::secp256k1);
auto const kp_b1 = randomKeyPair(KeyType::secp256k1);
auto const kp_b2 = randomKeyPair(KeyType::secp256k1);
auto const s_b0 = makeManifest(
sk_b, KeyType::ed25519, kp_b0.second, KeyType::secp256k1, 0);
auto const s_b1 = makeManifest(
sk_b,
KeyType::ed25519,
kp_b1.second,
KeyType::secp256k1,
1,
true); // invalidSig
auto const s_b2 = makeManifest(
sk_b, KeyType::ed25519, kp_b2.second, KeyType::ed25519, 2);
auto const fake = s_b2.serialized + '\0';
// applyManifest should accept new manifests with
// higher sequence numbers
BEAST_EXPECT(
cache.applyManifest(clone(s_a0)) ==
ManifestDisposition::accepted);
BEAST_EXPECT(
cache.applyManifest(clone(s_a0)) == ManifestDisposition::stale);
BEAST_EXPECT(
cache.applyManifest(clone(s_a1)) ==
ManifestDisposition::accepted);
BEAST_EXPECT(
cache.applyManifest(clone(s_a1)) == ManifestDisposition::stale);
BEAST_EXPECT(
cache.applyManifest(clone(s_a0)) == ManifestDisposition::stale);
BEAST_EXPECT(
cache.applyManifest(clone(s_a2)) ==
ManifestDisposition::badEphemeralKey);
// applyManifest should accept manifests with max sequence numbers
// that revoke the master public key
BEAST_EXPECT(!cache.revoked(pk_a));
BEAST_EXPECT(s_aMax.revoked());
BEAST_EXPECT(
cache.applyManifest(clone(s_aMax)) ==
ManifestDisposition::accepted);
BEAST_EXPECT(
cache.applyManifest(clone(s_aMax)) ==
ManifestDisposition::stale);
BEAST_EXPECT(
cache.applyManifest(clone(s_a1)) == ManifestDisposition::stale);
BEAST_EXPECT(
cache.applyManifest(clone(s_a0)) == ManifestDisposition::stale);
BEAST_EXPECT(cache.revoked(pk_a));
// applyManifest should reject manifests with invalid signatures
BEAST_EXPECT(
cache.applyManifest(clone(s_b0)) ==
ManifestDisposition::accepted);
BEAST_EXPECT(
cache.applyManifest(clone(s_b0)) == ManifestDisposition::stale);
BEAST_EXPECT(!deserializeManifest(fake));
BEAST_EXPECT(
cache.applyManifest(clone(s_b1)) ==
ManifestDisposition::invalid);
BEAST_EXPECT(
cache.applyManifest(clone(s_b2)) ==
ManifestDisposition::accepted);
auto const s_c0 = makeManifest(
kp_b2.second,
KeyType::ed25519,
randomSecretKey(),
KeyType::ed25519,
47);
BEAST_EXPECT(
cache.applyManifest(clone(s_c0)) ==
ManifestDisposition::badMasterKey);
}
testLoadStore(cache);
testGetSignature();
testGetKeys();
testValidatorToken();
testManifestDeserialization();
testManifestDomainNames();
testManifestVersioning();
}
};
BEAST_DEFINE_TESTSUITE(Manifest, app, xrpl);
} // namespace test
} // namespace xrpl