mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Allow manifests to include an optional 'domain' field:
The new 'Domain' field allows validator operators to associate a domain name with their manifest in a transparent and independently verifiable fashion. It is important to point out that while this system can cryptographically prove that a particular validator claims to be associated with a domain it does *NOT* prove that the validator is, actually, associated with that domain. Domain owners will have to cryptographically attest to operating particular validators that claim to be associated with that domain. One option for doing so would be by making available a file over HTTPS under the domain being claimed, which is verified separately (e.g. by ensuring that the certificate used to serve the file matches the domain being claimed) and which contains the long-term master public keys of validator(s) associated with that domain. Credit for an early prototype of this idea goes to GitHub user @cryptobrad who introduced a PR that would allow a validator list publisher to attest that a particular validator was associated with a domain. The idea may be worth revisiting as a way of verifying the domain name claimed by the validator's operator.
This commit is contained in:
@@ -127,9 +127,58 @@ public:
|
||||
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
|
||||
make_Manifest
|
||||
(SecretKey const& sk, KeyType type, SecretKey const& ssk, KeyType stype,
|
||||
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);
|
||||
|
||||
std::string const 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::move(m)); // 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);
|
||||
@@ -152,38 +201,22 @@ public:
|
||||
st.add(s);
|
||||
|
||||
std::string const m (static_cast<char const*> (s.data()), s.size());
|
||||
if (auto r = Manifest::make_Manifest (std::move (m)))
|
||||
if (auto r = deserializeManifest(std::move(m)))
|
||||
return std::move (*r);
|
||||
Throw<std::runtime_error> ("Could not create a manifest");
|
||||
return *Manifest::make_Manifest(std::move(m)); // Silence compiler warning.
|
||||
}
|
||||
|
||||
std::string
|
||||
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);
|
||||
|
||||
return base64_encode (std::string(
|
||||
static_cast<char const*> (s.data()), s.size()));
|
||||
return *deserializeManifest(std::move(m)); // Silence compiler warning.
|
||||
}
|
||||
|
||||
Manifest
|
||||
clone (Manifest const& m)
|
||||
{
|
||||
return Manifest (m.serialized, m.masterKey, m.signingKey, m.sequence);
|
||||
Manifest m2;
|
||||
m2.serialized = m.serialized;
|
||||
m2.masterKey = m.masterKey;
|
||||
m2.signingKey = m.signingKey;
|
||||
m2.sequence = m.sequence;
|
||||
m2.domain = m.domain;
|
||||
return m2;
|
||||
}
|
||||
|
||||
void testLoadStore (ManifestCache& m)
|
||||
@@ -197,24 +230,22 @@ public:
|
||||
DatabaseCon dbCon(setup, dbName, WalletDBInit, WalletDBCount);
|
||||
|
||||
auto getPopulatedManifests =
|
||||
[](ManifestCache const& cache) -> std::vector<Manifest const*>
|
||||
{
|
||||
std::vector<Manifest const*> result;
|
||||
result.reserve (32);
|
||||
cache.for_each_manifest (
|
||||
[&result](Manifest const& m)
|
||||
{result.push_back (&m);});
|
||||
return result;
|
||||
};
|
||||
[](ManifestCache const& cache) -> std::vector<Manifest const*>
|
||||
{
|
||||
std::vector<Manifest const*> result;
|
||||
result.reserve (32);
|
||||
cache.for_each_manifest (
|
||||
[&result](Manifest const& m) {result.push_back (&m);});
|
||||
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*> 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)));
|
||||
|
||||
@@ -318,13 +349,13 @@ public:
|
||||
BEAST_EXPECT(! loaded.revoked(pk));
|
||||
|
||||
std::vector<std::string> const badSigRevocation =
|
||||
{ makeRevocation (sk, keyType, true /* invalidSig */) };
|
||||
{ makeRevocationString (sk, keyType, true) };
|
||||
BEAST_EXPECT(! loaded.load (
|
||||
dbCon, "ValidatorManifests", emptyManifest, badSigRevocation));
|
||||
BEAST_EXPECT(! loaded.revoked(pk));
|
||||
|
||||
std::vector<std::string> const cfgRevocation =
|
||||
{ makeRevocation (sk, keyType) };
|
||||
{ makeRevocationString (sk, keyType) };
|
||||
BEAST_EXPECT(loaded.load (
|
||||
dbCon, "ValidatorManifests", emptyManifest, cfgRevocation));
|
||||
|
||||
@@ -341,7 +372,7 @@ public:
|
||||
auto const sk = randomSecretKey();
|
||||
auto const pk = derivePublicKey(KeyType::ed25519, sk);
|
||||
auto const kp = randomKeyPair(KeyType::secp256k1);
|
||||
auto const m = make_Manifest (
|
||||
auto const m = makeManifest (
|
||||
sk, KeyType::ed25519, kp.second, KeyType::secp256k1, 0);
|
||||
|
||||
STObject st(sfGeneric);
|
||||
@@ -374,10 +405,9 @@ public:
|
||||
// getMasterKey should return the listed validator master key
|
||||
// for that ephemeral public key
|
||||
auto const kp0 = randomKeyPair(KeyType::secp256k1);
|
||||
auto const m0 = make_Manifest (
|
||||
sk, KeyType::ed25519, kp0.second, KeyType::secp256k1, 0);
|
||||
BEAST_EXPECT(cache.applyManifest(clone (m0)) ==
|
||||
ManifestDisposition::accepted);
|
||||
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);
|
||||
|
||||
@@ -386,10 +416,9 @@ public:
|
||||
// getMasterKey should only return a master key for the latest
|
||||
// ephemeral public key
|
||||
auto const kp1 = randomKeyPair(KeyType::secp256k1);
|
||||
auto const m1 = make_Manifest (
|
||||
sk, KeyType::ed25519, kp1.second, KeyType::secp256k1, 1);
|
||||
BEAST_EXPECT(cache.applyManifest(clone (m1)) ==
|
||||
ManifestDisposition::accepted);
|
||||
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);
|
||||
@@ -397,27 +426,22 @@ public:
|
||||
// getSigningKey and getMasterKey should return the same keys if
|
||||
// a new manifest is applied with the same signing key but a higher
|
||||
// sequence
|
||||
auto const m2 = make_Manifest (
|
||||
sk, KeyType::ed25519, kp1.second, KeyType::secp256k1, 2);
|
||||
BEAST_EXPECT(cache.applyManifest(clone (m2)) ==
|
||||
ManifestDisposition::accepted);
|
||||
BEAST_EXPECT(ManifestDisposition::accepted ==
|
||||
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 boost::none for a
|
||||
// revoked master public key
|
||||
// getSigningKey should return boost::none for a revoked master public key
|
||||
// getMasterKey should return boost::none for an ephemeral public key
|
||||
// from a revoked master public key
|
||||
auto const kpMax = randomKeyPair(KeyType::secp256k1);
|
||||
auto const mMax = make_Manifest (
|
||||
sk, KeyType::ed25519, kpMax.second, KeyType::secp256k1,
|
||||
std::numeric_limits<std::uint32_t>::max ());
|
||||
BEAST_EXPECT(cache.applyManifest(clone (mMax)) ==
|
||||
ManifestDisposition::accepted);
|
||||
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(kpMax.first) == kpMax.first);
|
||||
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
|
||||
BEAST_EXPECT(cache.getMasterKey(kp1.first) == kp1.first);
|
||||
}
|
||||
|
||||
@@ -460,10 +484,45 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void testMakeManifest()
|
||||
void testManifestVersioning()
|
||||
{
|
||||
testcase ("make_Manifest");
|
||||
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 }};
|
||||
@@ -497,7 +556,8 @@ public:
|
||||
auto const spk = derivePublicKey(sKeyType, ssk);
|
||||
|
||||
auto buildManifestObject = [&](
|
||||
std::uint32_t const& seq,
|
||||
std::uint32_t seq,
|
||||
boost::optional<std::string> domain,
|
||||
bool noSigningPublic = false,
|
||||
bool noSignature = false)
|
||||
{
|
||||
@@ -505,11 +565,13 @@ public:
|
||||
st[sfSequence] = seq;
|
||||
st[sfPublicKey] = pk;
|
||||
|
||||
if (domain)
|
||||
st[sfDomain] = makeSlice(*domain);
|
||||
|
||||
if (! noSigningPublic)
|
||||
st[sfSigningPubKey] = spk;
|
||||
|
||||
sign(st, HashPrefix::manifest, keyType, sk,
|
||||
sfMasterSignature);
|
||||
sign(st, HashPrefix::manifest, keyType, sk, sfMasterSignature);
|
||||
|
||||
if (! noSignature)
|
||||
sign(st, HashPrefix::manifest, sKeyType, ssk);
|
||||
@@ -517,201 +579,329 @@ public:
|
||||
return st;
|
||||
};
|
||||
|
||||
auto const st = buildManifestObject(++sequence);
|
||||
{
|
||||
testcase << "deserializeManifest: normal manifest (" <<
|
||||
to_string(keyType) << " + " <<
|
||||
to_string(sKeyType) << ")";
|
||||
|
||||
{ // valid manifest without domain
|
||||
auto const st = buildManifestObject(
|
||||
++sequence, boost::none);
|
||||
|
||||
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[sfPublicKey] = badSigSt[sfSigningPubKey];
|
||||
|
||||
auto const m = toString(badSigSt);
|
||||
auto const manifest = deserializeManifest(m);
|
||||
|
||||
BEAST_EXPECT(manifest);
|
||||
BEAST_EXPECT(manifest->masterKey == spk);
|
||||
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());
|
||||
}
|
||||
{
|
||||
// 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] = badKey;
|
||||
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
|
||||
}
|
||||
{
|
||||
// reject short public key
|
||||
auto badSt = st;
|
||||
badSt[sfPublicKey] = 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] = badKey;
|
||||
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
|
||||
}
|
||||
{
|
||||
// reject short signing public key
|
||||
auto badSt = st;
|
||||
badSt[sfSigningPubKey] = 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)));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// valid manifest
|
||||
auto const m = toString(st);
|
||||
testcase << "deserializeManifest: revocation manifest (" <<
|
||||
to_string(keyType) << " + " <<
|
||||
to_string(sKeyType) << ")";
|
||||
|
||||
auto const manifest = Manifest::make_Manifest (m);
|
||||
// valid revocation
|
||||
{
|
||||
auto const st = buildManifestObject(
|
||||
std::numeric_limits<std::uint32_t>::max(),
|
||||
boost::none, true, true);
|
||||
|
||||
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->verify());
|
||||
}
|
||||
{
|
||||
// valid manifest with invalid signature
|
||||
auto badSigSt = st;
|
||||
badSigSt[sfPublicKey] = badSigSt[sfSigningPubKey];
|
||||
auto const m = toString(st);
|
||||
auto const manifest = deserializeManifest(m);
|
||||
|
||||
auto const m = toString(badSigSt);
|
||||
auto const manifest = Manifest::make_Manifest (m);
|
||||
BEAST_EXPECT(manifest);
|
||||
BEAST_EXPECT(manifest->masterKey == pk);
|
||||
BEAST_EXPECT(manifest->signingKey == PublicKey());
|
||||
BEAST_EXPECT(manifest->revoked());
|
||||
BEAST_EXPECT(manifest->domain.empty());
|
||||
BEAST_EXPECT(manifest->serialized == m);
|
||||
BEAST_EXPECT(manifest->verify());
|
||||
}
|
||||
|
||||
BEAST_EXPECT(manifest);
|
||||
BEAST_EXPECT(manifest->masterKey == spk);
|
||||
BEAST_EXPECT(manifest->signingKey == spk);
|
||||
BEAST_EXPECT(manifest->sequence == sequence);
|
||||
BEAST_EXPECT(manifest->serialized == m);
|
||||
BEAST_EXPECT(! manifest->verify());
|
||||
}
|
||||
{
|
||||
// reject missing sequence
|
||||
auto badSt = st;
|
||||
BEAST_EXPECT(badSt.delField(sfSequence));
|
||||
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
|
||||
}
|
||||
{
|
||||
// reject missing public key
|
||||
auto badSt = st;
|
||||
BEAST_EXPECT(badSt.delField(sfPublicKey));
|
||||
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
|
||||
}
|
||||
{
|
||||
// reject invalid public key type
|
||||
auto badSt = st;
|
||||
badSt[sfPublicKey] = badKey;
|
||||
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
|
||||
}
|
||||
{
|
||||
// reject short public key
|
||||
auto badSt = st;
|
||||
badSt[sfPublicKey] = shortKey;
|
||||
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
|
||||
}
|
||||
{
|
||||
// reject missing signing public key
|
||||
auto badSt = st;
|
||||
BEAST_EXPECT(badSt.delField(sfSigningPubKey));
|
||||
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
|
||||
}
|
||||
{
|
||||
// reject invalid signing public key type
|
||||
auto badSt = st;
|
||||
badSt[sfSigningPubKey] = badKey;
|
||||
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
|
||||
}
|
||||
{
|
||||
// reject short signing public key
|
||||
auto badSt = st;
|
||||
badSt[sfSigningPubKey] = shortKey;
|
||||
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
|
||||
}
|
||||
{
|
||||
// reject missing signature
|
||||
auto badSt = st;
|
||||
BEAST_EXPECT(badSt.delField(sfMasterSignature));
|
||||
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
|
||||
}
|
||||
{
|
||||
// reject missing signing key signature
|
||||
auto badSt = st;
|
||||
BEAST_EXPECT(badSt.delField(sfSignature));
|
||||
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
|
||||
}
|
||||
{ // can't specify an ephemeral signing key
|
||||
auto const st = buildManifestObject(
|
||||
std::numeric_limits<std::uint32_t>::max(),
|
||||
boost::none, true, false);
|
||||
|
||||
// test revocations (max sequence revoking the master key)
|
||||
auto testRevocation = [&](STObject const& st)
|
||||
{
|
||||
auto const m = toString(st);
|
||||
auto const manifest = Manifest::make_Manifest (m);
|
||||
BEAST_EXPECT(!deserializeManifest(toString(st)));
|
||||
}
|
||||
{ // can't specify an ephemeral signature
|
||||
auto const st = buildManifestObject(
|
||||
std::numeric_limits<std::uint32_t>::max(),
|
||||
boost::none, false, true);
|
||||
|
||||
BEAST_EXPECT(manifest);
|
||||
BEAST_EXPECT(manifest->masterKey == pk);
|
||||
BEAST_EXPECT(manifest->signingKey == PublicKey());
|
||||
BEAST_EXPECT(manifest->sequence ==
|
||||
std::numeric_limits<std::uint32_t>::max ());
|
||||
BEAST_EXPECT(manifest->serialized == m);
|
||||
BEAST_EXPECT(manifest->verify());
|
||||
};
|
||||
BEAST_EXPECT(!deserializeManifest(toString(st)));
|
||||
}
|
||||
{ // can't specify an ephemeral key & signature
|
||||
auto const st = buildManifestObject(
|
||||
std::numeric_limits<std::uint32_t>::max (),
|
||||
boost::none, false, false);
|
||||
|
||||
// valid revocation
|
||||
{
|
||||
auto const revSt = buildManifestObject(
|
||||
std::numeric_limits<std::uint32_t>::max ());
|
||||
testRevocation(revSt);
|
||||
}
|
||||
|
||||
// signing key and signature are optional in revocation
|
||||
{
|
||||
auto const revSt = buildManifestObject(
|
||||
std::numeric_limits<std::uint32_t>::max (),
|
||||
true /* no signing key */);
|
||||
testRevocation(revSt);
|
||||
}
|
||||
{
|
||||
auto const revSt = buildManifestObject(
|
||||
std::numeric_limits<std::uint32_t>::max (),
|
||||
false, true /* no signature */);
|
||||
testRevocation(revSt);
|
||||
|
||||
}
|
||||
{
|
||||
auto const revSt = buildManifestObject(
|
||||
std::numeric_limits<std::uint32_t>::max (),
|
||||
true /* no signing key */,
|
||||
true /* no signature */);
|
||||
testRevocation(revSt);
|
||||
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 accepted = ManifestDisposition::accepted;
|
||||
auto const stale = ManifestDisposition::stale;
|
||||
auto const invalid = ManifestDisposition::invalid;
|
||||
|
||||
auto const sk_a = randomSecretKey();
|
||||
auto const pk_a = derivePublicKey(KeyType::ed25519, sk_a);
|
||||
auto const kp_a = randomKeyPair(KeyType::secp256k1);
|
||||
auto const s_a0 = make_Manifest (
|
||||
auto const s_a0 = makeManifest (
|
||||
sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1, 0);
|
||||
auto const s_a1 = make_Manifest (
|
||||
auto const s_a1 = makeManifest (
|
||||
sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1, 1);
|
||||
auto const s_aMax = make_Manifest (
|
||||
sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1,
|
||||
std::numeric_limits<std::uint32_t>::max ());
|
||||
auto const s_aMax = makeRevocation (sk_a, KeyType::ed25519);
|
||||
|
||||
auto const sk_b = randomSecretKey();
|
||||
auto const kp_b = randomKeyPair(KeyType::secp256k1);
|
||||
auto const s_b0 = make_Manifest (
|
||||
auto const s_b0 = makeManifest (
|
||||
sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 0);
|
||||
auto const s_b1 = make_Manifest (
|
||||
auto const s_b1 = makeManifest (
|
||||
sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 1);
|
||||
auto const s_b2 = make_Manifest (
|
||||
auto const s_b2 = makeManifest (
|
||||
sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 2,
|
||||
true); // invalidSig
|
||||
auto const fake = s_b1.serialized + '\0';
|
||||
|
||||
// applyManifest should accept new manifests with
|
||||
// higher sequence numbers
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == accepted);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == stale);
|
||||
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)) == accepted);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == stale);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == 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);
|
||||
|
||||
// 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)) == accepted);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_aMax)) == stale);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == stale);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == stale);
|
||||
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)) == accepted);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_b0)) == stale);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_b0)) == ManifestDisposition::accepted);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_b0)) == ManifestDisposition::stale);
|
||||
|
||||
BEAST_EXPECT(!Manifest::make_Manifest(fake));
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_b2)) == invalid);
|
||||
BEAST_EXPECT(!deserializeManifest(fake));
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_b2)) == ManifestDisposition::invalid);
|
||||
}
|
||||
testLoadStore (cache);
|
||||
testGetSignature ();
|
||||
testGetKeys ();
|
||||
testValidatorToken ();
|
||||
testMakeManifest ();
|
||||
testManifestDeserialization ();
|
||||
testManifestDomainNames ();
|
||||
testManifestVersioning ();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -91,8 +91,7 @@ public:
|
||||
auto const tokenPublicKey =
|
||||
derivePublicKey(KeyType::secp256k1, tokenSecretKey);
|
||||
|
||||
auto const m = Manifest::make_Manifest(
|
||||
base64_decode(tokenManifest));
|
||||
auto const m = deserializeManifest(base64_decode(tokenManifest));
|
||||
BEAST_EXPECT(m);
|
||||
NodeID const tokenNodeID = calcNodeID(m->masterKey);
|
||||
|
||||
|
||||
@@ -68,11 +68,32 @@ private:
|
||||
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);
|
||||
if (seq != std::numeric_limits<std::uint32_t>::max())
|
||||
{
|
||||
st[sfSigningPubKey] = spk;
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
|
||||
}
|
||||
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk, sfMasterSignature);
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
|
||||
return std::string(static_cast<char const*> (s.data()), s.size());
|
||||
}
|
||||
|
||||
static
|
||||
std::string
|
||||
makeRevocationString (
|
||||
PublicKey const& pk,
|
||||
SecretKey const& sk)
|
||||
{
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = std::numeric_limits<std::uint32_t>::max();
|
||||
st[sfPublicKey] = pk;
|
||||
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk, sfMasterSignature);
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
@@ -162,8 +183,8 @@ private:
|
||||
|
||||
jtx::Env env (*this);
|
||||
PublicKey emptyLocalKey;
|
||||
std::vector<std::string> emptyCfgKeys;
|
||||
std::vector<std::string> emptyCfgPublishers;
|
||||
std::vector<std::string> const emptyCfgKeys;
|
||||
std::vector<std::string> const emptyCfgPublishers;
|
||||
|
||||
auto const localSigningKeys = randomKeyPair(KeyType::secp256k1);
|
||||
auto const localSigningPublic = localSigningKeys.first;
|
||||
@@ -220,7 +241,7 @@ private:
|
||||
localSigningPublic, emptyCfgKeys, emptyCfgPublishers));
|
||||
BEAST_EXPECT(trustedKeys->listed (localSigningPublic));
|
||||
|
||||
manifests.applyManifest (*Manifest::make_Manifest(cfgManifest));
|
||||
manifests.applyManifest (*deserializeManifest(cfgManifest));
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
localSigningPublic, emptyCfgKeys, emptyCfgPublishers));
|
||||
|
||||
@@ -253,23 +274,16 @@ private:
|
||||
BEAST_EXPECT(trustedKeys->listed (masterNode2));
|
||||
|
||||
// load should reject invalid config keys
|
||||
std::vector<std::string> badKeys({"NotAPublicKey"});
|
||||
BEAST_EXPECT(!trustedKeys->load (
|
||||
emptyLocalKey, badKeys, emptyCfgPublishers));
|
||||
|
||||
badKeys[0] = format (randomNode(), "!");
|
||||
BEAST_EXPECT(!trustedKeys->load (
|
||||
emptyLocalKey, badKeys, emptyCfgPublishers));
|
||||
|
||||
badKeys[0] = format (randomNode(), "! Comment");
|
||||
BEAST_EXPECT(!trustedKeys->load (
|
||||
emptyLocalKey, badKeys, emptyCfgPublishers));
|
||||
BEAST_EXPECT(!trustedKeys->load (emptyLocalKey,
|
||||
{ "NotAPublicKey" }, emptyCfgPublishers));
|
||||
BEAST_EXPECT(!trustedKeys->load (emptyLocalKey,
|
||||
{ format (randomNode(), "!") }, emptyCfgPublishers));
|
||||
|
||||
// load terminates when encountering an invalid entry
|
||||
auto const goodKey = randomNode();
|
||||
badKeys.push_back (format (goodKey));
|
||||
BEAST_EXPECT(!trustedKeys->load (
|
||||
emptyLocalKey, badKeys, emptyCfgPublishers));
|
||||
BEAST_EXPECT(!trustedKeys->load (emptyLocalKey,
|
||||
{ format (randomNode(), "!"), format (goodKey) },
|
||||
emptyCfgPublishers));
|
||||
BEAST_EXPECT(!trustedKeys->listed (goodKey));
|
||||
}
|
||||
{
|
||||
@@ -310,7 +324,7 @@ private:
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), env.journal);
|
||||
|
||||
manifests.applyManifest (*Manifest::make_Manifest(cfgManifest));
|
||||
manifests.applyManifest (*deserializeManifest(cfgManifest));
|
||||
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
localSigningPublic, cfgKeys, emptyCfgPublishers));
|
||||
@@ -369,7 +383,7 @@ private:
|
||||
auto const pubRevokedSigning = randomKeyPair(KeyType::secp256k1);
|
||||
// make this manifest revoked (seq num = max)
|
||||
// -- thus should not be loaded
|
||||
pubManifests.applyManifest (*Manifest::make_Manifest (
|
||||
pubManifests.applyManifest (*deserializeManifest (
|
||||
makeManifestString (
|
||||
pubRevokedPublic,
|
||||
pubRevokedSecret,
|
||||
@@ -530,10 +544,8 @@ private:
|
||||
// do not apply list with revoked publisher key
|
||||
// applied list is removed due to revoked publisher key
|
||||
auto const signingKeysMax = randomKeyPair(KeyType::secp256k1);
|
||||
auto maxManifest = base64_encode(makeManifestString (
|
||||
publisherPublic, publisherSecret,
|
||||
pubSigningKeys2.first, pubSigningKeys2.second,
|
||||
std::numeric_limits<std::uint32_t>::max ()));
|
||||
auto maxManifest = base64_encode(makeRevocationString (
|
||||
publisherPublic, publisherSecret));
|
||||
|
||||
auto const sequence5 = 5;
|
||||
auto const blob5 = makeList (
|
||||
@@ -647,7 +659,7 @@ private:
|
||||
BEAST_EXPECT(!trustedKeys->trusted (signingPublic1));
|
||||
|
||||
// Should trust the ephemeral signing key from the applied manifest
|
||||
auto m1 = Manifest::make_Manifest (makeManifestString (
|
||||
auto m1 = deserializeManifest(makeManifestString(
|
||||
masterPublic, masterPrivate,
|
||||
signingPublic1, signingKeys1.second, 1));
|
||||
|
||||
@@ -663,7 +675,7 @@ private:
|
||||
// from the newest applied manifest
|
||||
auto const signingKeys2 = randomKeyPair(KeyType::secp256k1);
|
||||
auto const signingPublic2 = signingKeys2.first;
|
||||
auto m2 = Manifest::make_Manifest (makeManifestString (
|
||||
auto m2 = deserializeManifest(makeManifestString(
|
||||
masterPublic, masterPrivate,
|
||||
signingPublic2, signingKeys2.second, 2));
|
||||
BEAST_EXPECT(
|
||||
@@ -680,10 +692,8 @@ private:
|
||||
auto const signingKeysMax = randomKeyPair(KeyType::secp256k1);
|
||||
auto const signingPublicMax = signingKeysMax.first;
|
||||
activeValidators.emplace (calcNodeID(signingPublicMax));
|
||||
auto mMax = Manifest::make_Manifest (makeManifestString (
|
||||
masterPublic, masterPrivate,
|
||||
signingPublicMax, signingKeysMax.second,
|
||||
std::numeric_limits<std::uint32_t>::max ()));
|
||||
auto mMax = deserializeManifest(makeRevocationString(
|
||||
masterPublic, masterPrivate));
|
||||
|
||||
BEAST_EXPECT(mMax->revoked ());
|
||||
BEAST_EXPECT(
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/misc/Manifest.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
@@ -744,21 +746,6 @@ class LedgerRPC_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void testLedgerEntryGenerator()
|
||||
{
|
||||
testcase ("ledger_entry Request Generator");
|
||||
using namespace test::jtx;
|
||||
Env env {*this};
|
||||
|
||||
// All generator requests are deprecated.
|
||||
Json::Value jvParams;
|
||||
jvParams[jss::generator] = 5;
|
||||
jvParams[jss::ledger_hash] = to_string (env.closed()->info().hash);
|
||||
Json::Value const jrr = env.rpc (
|
||||
"json", "ledger_entry", to_string (jvParams))[jss::result];
|
||||
checkErrorValue (jrr, "deprecatedFeature", "");
|
||||
}
|
||||
|
||||
void testLedgerEntryOffer()
|
||||
{
|
||||
testcase ("ledger_entry Request Offer");
|
||||
@@ -1516,7 +1503,6 @@ public:
|
||||
testLedgerEntryDepositPreauth();
|
||||
testLedgerEntryDirectory();
|
||||
testLedgerEntryEscrow();
|
||||
testLedgerEntryGenerator();
|
||||
testLedgerEntryOffer();
|
||||
testLedgerEntryPayChan();
|
||||
testLedgerEntryRippleState();
|
||||
|
||||
Reference in New Issue
Block a user