fix: Bound untrusted manifest cache

This commit is contained in:
Pratik Mankawde
2026-07-17 19:07:57 +01:00
committed by Ayaz Salikhov
parent 033dca2f0e
commit 68a765d929
7 changed files with 313 additions and 97 deletions

View File

@@ -399,7 +399,8 @@ public:
BEAST_EXPECT(
ManifestDisposition::Accepted ==
cache.applyManifest(
makeManifest(sk, KeyType::Ed25519, kp0.second, KeyType::Secp256k1, 0)));
makeManifest(sk, KeyType::Ed25519, kp0.second, KeyType::Secp256k1, 0),
ManifestRateLimitCap::Capped));
BEAST_EXPECT(cache.getSigningKey(pk) == kp0.first);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == pk);
@@ -411,7 +412,8 @@ public:
BEAST_EXPECT(
ManifestDisposition::Accepted ==
cache.applyManifest(
makeManifest(sk, KeyType::Ed25519, kp1.second, KeyType::Secp256k1, 1)));
makeManifest(sk, KeyType::Ed25519, kp1.second, KeyType::Secp256k1, 1),
ManifestRateLimitCap::Capped));
BEAST_EXPECT(cache.getSigningKey(pk) == kp1.first);
BEAST_EXPECT(cache.getMasterKey(kp1.first) == pk);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
@@ -421,7 +423,8 @@ public:
BEAST_EXPECT(
ManifestDisposition::BadEphemeralKey ==
cache.applyManifest(
makeManifest(sk, KeyType::Ed25519, kp1.second, KeyType::Secp256k1, 2)));
makeManifest(sk, KeyType::Ed25519, kp1.second, KeyType::Secp256k1, 2),
ManifestRateLimitCap::Capped));
BEAST_EXPECT(cache.getSigningKey(pk) == kp1.first);
BEAST_EXPECT(cache.getMasterKey(kp1.first) == pk);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
@@ -431,7 +434,8 @@ public:
// key from a revoked master public key
BEAST_EXPECT(
ManifestDisposition::Accepted ==
cache.applyManifest(makeRevocation(sk, KeyType::Ed25519)));
cache.applyManifest(
makeRevocation(sk, KeyType::Ed25519), ManifestRateLimitCap::Capped));
BEAST_EXPECT(cache.revoked(pk));
BEAST_EXPECT(cache.getSigningKey(pk) == pk);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
@@ -902,39 +906,69 @@ public:
// applyManifest should accept new manifests with
// higher sequence numbers
auto const seq0 = cache.sequence();
BEAST_EXPECT(cache.applyManifest(clone(sA0)) == ManifestDisposition::Accepted);
BEAST_EXPECT(
cache.applyManifest(clone(sA0), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Accepted);
BEAST_EXPECT(cache.sequence() > seq0);
auto const seq1 = cache.sequence();
BEAST_EXPECT(cache.applyManifest(clone(sA0)) == ManifestDisposition::Stale);
BEAST_EXPECT(
cache.applyManifest(clone(sA0), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Stale);
BEAST_EXPECT(cache.sequence() == seq1);
BEAST_EXPECT(cache.applyManifest(clone(sA1)) == ManifestDisposition::Accepted);
BEAST_EXPECT(cache.applyManifest(clone(sA1)) == ManifestDisposition::Stale);
BEAST_EXPECT(cache.applyManifest(clone(sA0)) == ManifestDisposition::Stale);
BEAST_EXPECT(
cache.applyManifest(clone(sA1), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Accepted);
BEAST_EXPECT(
cache.applyManifest(clone(sA1), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Stale);
BEAST_EXPECT(
cache.applyManifest(clone(sA0), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Stale);
BEAST_EXPECT(cache.applyManifest(clone(sA2)) == ManifestDisposition::BadEphemeralKey);
BEAST_EXPECT(
cache.applyManifest(clone(sA2), ManifestRateLimitCap::Capped) ==
ManifestDisposition::BadEphemeralKey);
// applyManifest should accept manifests with max sequence numbers
// that revoke the master public key
BEAST_EXPECT(!cache.revoked(pkA));
BEAST_EXPECT(sAMax.revoked());
BEAST_EXPECT(cache.applyManifest(clone(sAMax)) == ManifestDisposition::Accepted);
BEAST_EXPECT(cache.applyManifest(clone(sAMax)) == ManifestDisposition::Stale);
BEAST_EXPECT(cache.applyManifest(clone(sA1)) == ManifestDisposition::Stale);
BEAST_EXPECT(cache.applyManifest(clone(sA0)) == ManifestDisposition::Stale);
BEAST_EXPECT(
cache.applyManifest(clone(sAMax), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Accepted);
BEAST_EXPECT(
cache.applyManifest(clone(sAMax), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Stale);
BEAST_EXPECT(
cache.applyManifest(clone(sA1), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Stale);
BEAST_EXPECT(
cache.applyManifest(clone(sA0), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Stale);
BEAST_EXPECT(cache.revoked(pkA));
// applyManifest should reject manifests with invalid signatures
BEAST_EXPECT(cache.applyManifest(clone(sB0)) == ManifestDisposition::Accepted);
BEAST_EXPECT(cache.applyManifest(clone(sB0)) == ManifestDisposition::Stale);
BEAST_EXPECT(
cache.applyManifest(clone(sB0), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Accepted);
BEAST_EXPECT(
cache.applyManifest(clone(sB0), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Stale);
BEAST_EXPECT(!deserializeManifest(fake));
BEAST_EXPECT(cache.applyManifest(clone(sB1)) == ManifestDisposition::Invalid);
BEAST_EXPECT(cache.applyManifest(clone(sB2)) == ManifestDisposition::Accepted);
BEAST_EXPECT(
cache.applyManifest(clone(sB1), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Invalid);
BEAST_EXPECT(
cache.applyManifest(clone(sB2), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Accepted);
auto const sC0 = makeManifest(
kpB2.second, KeyType::Ed25519, randomSecretKey(), KeyType::Ed25519, 47);
BEAST_EXPECT(cache.applyManifest(clone(sC0)) == ManifestDisposition::BadMasterKey);
BEAST_EXPECT(
cache.applyManifest(clone(sC0), ManifestRateLimitCap::Capped) ==
ManifestDisposition::BadMasterKey);
}
testLoadStore(cache);

View File

@@ -278,8 +278,10 @@ private:
trustedKeys->load(localSigningPublicOuter, emptyCfgKeys, emptyCfgPublishers));
BEAST_EXPECT(trustedKeys->listed(localSigningPublicOuter));
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
manifests.applyManifest(*deserializeManifest(cfgManifest));
// NOLINTBEGIN(bugprone-unchecked-optional-access)
manifests.applyManifest(
*deserializeManifest(cfgManifest), ManifestRateLimitCap::Capped);
// NOLINTEND(bugprone-unchecked-optional-access)
BEAST_EXPECT(
trustedKeys->load(localSigningPublicOuter, emptyCfgKeys, emptyCfgPublishers));
@@ -369,8 +371,10 @@ private:
app.config().legacy(Sections::kDatabasePath),
env.journal);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
manifests.applyManifest(*deserializeManifest(cfgManifest));
// NOLINTBEGIN(bugprone-unchecked-optional-access)
manifests.applyManifest(
*deserializeManifest(cfgManifest), ManifestRateLimitCap::Capped);
// NOLINTEND(bugprone-unchecked-optional-access)
BEAST_EXPECT(trustedKeys->load(localSigningPublicOuter, cfgKeys, emptyCfgPublishers));
@@ -455,13 +459,16 @@ private:
auto const pubRevokedSigning = randomKeyPair(KeyType::Secp256k1);
// make this manifest revoked (seq num = max)
// -- thus should not be loaded
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
pubManifests.applyManifest(*deserializeManifest(makeManifestString(
pubRevokedPublic,
pubRevokedSecret,
pubRevokedSigning.first,
pubRevokedSigning.second,
std::numeric_limits<std::uint32_t>::max())));
// NOLINTBEGIN(bugprone-unchecked-optional-access)
pubManifests.applyManifest(
*deserializeManifest(makeManifestString(
pubRevokedPublic,
pubRevokedSecret,
pubRevokedSigning.first,
pubRevokedSigning.second,
std::numeric_limits<std::uint32_t>::max())),
ManifestRateLimitCap::Capped);
// NOLINTEND(bugprone-unchecked-optional-access)
// these two are not revoked (and not in the manifest cache at all.)
auto legitKey1 = randomMasterKey();
@@ -494,13 +501,16 @@ private:
auto const pubRevokedSigning = randomKeyPair(KeyType::Secp256k1);
// make this manifest revoked (seq num = max)
// -- thus should not be loaded
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
pubManifests.applyManifest(*deserializeManifest(makeManifestString(
pubRevokedPublic,
pubRevokedSecret,
pubRevokedSigning.first,
pubRevokedSigning.second,
std::numeric_limits<std::uint32_t>::max())));
// NOLINTBEGIN(bugprone-unchecked-optional-access)
pubManifests.applyManifest(
*deserializeManifest(makeManifestString(
pubRevokedPublic,
pubRevokedSecret,
pubRevokedSigning.first,
pubRevokedSigning.second,
std::numeric_limits<std::uint32_t>::max())),
ManifestRateLimitCap::Capped);
// NOLINTEND(bugprone-unchecked-optional-access)
// this one is not revoked (and not in the manifest cache at all.)
auto legitKey = randomMasterKey();
@@ -1218,7 +1228,8 @@ private:
BEAST_EXPECT(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
manifestsOuter.applyManifest(std::move(*m1)) == ManifestDisposition::Accepted);
manifestsOuter.applyManifest(std::move(*m1), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Accepted);
BEAST_EXPECT(trustedKeysOuter->listed(masterPublic));
BEAST_EXPECT(trustedKeysOuter->trusted(masterPublic));
BEAST_EXPECT(trustedKeysOuter->listed(signingPublic1));
@@ -1232,7 +1243,8 @@ private:
masterPublic, masterPrivate, signingPublic2, signingKeys2.second, 2));
BEAST_EXPECT(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
manifestsOuter.applyManifest(std::move(*m2)) == ManifestDisposition::Accepted);
manifestsOuter.applyManifest(std::move(*m2), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Accepted);
BEAST_EXPECT(trustedKeysOuter->listed(masterPublic));
BEAST_EXPECT(trustedKeysOuter->trusted(masterPublic));
BEAST_EXPECT(trustedKeysOuter->listed(signingPublic2));
@@ -1249,7 +1261,8 @@ private:
// NOLINTBEGIN(bugprone-unchecked-optional-access)
BEAST_EXPECT(max->revoked());
BEAST_EXPECT(
manifestsOuter.applyManifest(std::move(*max)) == ManifestDisposition::Accepted);
manifestsOuter.applyManifest(std::move(*max), ManifestRateLimitCap::Capped) ==
ManifestDisposition::Accepted);
// NOLINTEND(bugprone-unchecked-optional-access)
BEAST_EXPECT(manifestsOuter.getSigningKey(masterPublic) == masterPublic);
@@ -2668,7 +2681,9 @@ private:
auto threshold = listThreshold > 0 ? std::optional(listThreshold) : std::nullopt;
if (self)
{
valManifests.applyManifest(*deserializeManifest(base64Decode(self->manifest)));
valManifests.applyManifest(
*deserializeManifest(base64Decode(self->manifest)),
ManifestRateLimitCap::Capped);
BEAST_EXPECT(
result->load(self->signingPublic, emptyCfgKeys, cfgPublishers, threshold));
}