diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/btfaddr.cpp | 40 | ||||
| -rw-r--r-- | src/test_btfaddr.cpp | 19 |
2 files changed, 41 insertions, 18 deletions
diff --git a/src/btfaddr.cpp b/src/btfaddr.cpp index ce2ac86..502220e 100644 --- a/src/btfaddr.cpp +++ b/src/btfaddr.cpp | |||
| @@ -319,20 +319,32 @@ bool VerifyDescriptor(void* ctxv, const std::string& jsonStr, | |||
| 319 | std::string onion; | 319 | std::string onion; |
| 320 | if (v.contains("onion")) | 320 | if (v.contains("onion")) |
| 321 | { | 321 | { |
| 322 | if (v["onion"].is_string() && v.contains("sig3") && v["sig3"].is_string()) | 322 | // A descriptor that carries an onion endpoint must prove it. The |
| 323 | { | 323 | // endpoint is covered by sig3 and only sig3, so any failure here -- |
| 324 | std::string onionNorm; | 324 | // missing sig3, bad hex, unnormalised endpoint, bad signature -- |
| 325 | std::string onionRaw = v["onion"].get<std::string>(); | 325 | // means the descriptor was tampered with, and a tampered |
| 326 | if (NormalizeOnionEndpoint(onionRaw, onionNorm) && onionNorm == onionRaw) | 326 | // descriptor is rejected outright. Upstream dropped just the onion |
| 327 | { | 327 | // and accepted the rest, which let any relay convert an onion |
| 328 | unsigned char sig3[64]; | 328 | // descriptor into a rendezvous-only one by deleting a field. |
| 329 | unsigned char digest3[32]; | 329 | // |
| 330 | DescriptorDigestV3(expect_pubkey, enc, meeting_node, onionNorm, created, digest3); | 330 | // Residual, by design: removing BOTH "onion" and "sig3" still |
| 331 | if (HexDecode(v["sig3"].get<std::string>(), sig3, 64) && | 331 | // verifies, because the result is indistinguishable from a peer |
| 332 | secp256k1_schnorrsig_verify(ctx, sig3, digest3, 32, &xpub)) | 332 | // that never had an onion endpoint. Closing that requires binding |
| 333 | onion = onionNorm; | 333 | // the onion into the primary sig (the V2 digest), which every |
| 334 | } | 334 | // upstream 1.2.18/1.2.19 verifier would reject -- an upstream |
| 335 | } | 335 | // protocol change, not something this fork can do unilaterally. |
| 336 | if (!v["onion"].is_string()) return false; | ||
| 337 | if (!v.contains("sig3") || !v["sig3"].is_string()) return false; | ||
| 338 | std::string onionNorm; | ||
| 339 | std::string onionRaw = v["onion"].get<std::string>(); | ||
| 340 | if (!NormalizeOnionEndpoint(onionRaw, onionNorm) || onionNorm != onionRaw) | ||
| 341 | return false; | ||
| 342 | unsigned char sig3[64]; | ||
| 343 | unsigned char digest3[32]; | ||
| 344 | DescriptorDigestV3(expect_pubkey, enc, meeting_node, onionNorm, created, digest3); | ||
| 345 | if (!HexDecode(v["sig3"].get<std::string>(), sig3, 64)) return false; | ||
| 346 | if (!secp256k1_schnorrsig_verify(ctx, sig3, digest3, 32, &xpub)) return false; | ||
| 347 | onion = onionNorm; | ||
| 336 | } | 348 | } |
| 337 | 349 | ||
| 338 | memcpy(out.pubkey, expect_pubkey, 32); | 350 | memcpy(out.pubkey, expect_pubkey, 32); |
diff --git a/src/test_btfaddr.cpp b/src/test_btfaddr.cpp index 8de426e..9d33d63 100644 --- a/src/test_btfaddr.cpp +++ b/src/test_btfaddr.cpp | |||
| @@ -133,12 +133,23 @@ int main() | |||
| 133 | std::string d3 = btf::SignDescriptor(ctx, sk, ENC_HEX, "node-1", 5, ONION); | 133 | std::string d3 = btf::SignDescriptor(ctx, sk, ENC_HEX, "node-1", 5, ONION); |
| 134 | json j3 = json::parse(d3); | 134 | json j3 = json::parse(d3); |
| 135 | j3["onion"] = "bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde.onion:8433"; | 135 | j3["onion"] = "bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde.onion:8433"; |
| 136 | CHECK(btf::VerifyDescriptor(ctx, j3.dump(), pk, o) && o.onion.empty(), | 136 | CHECK(!btf::VerifyDescriptor(ctx, j3.dump(), pk, o), |
| 137 | "tampered onion ignored rather than trusted"); | 137 | "tampered onion rejects the descriptor"); |
| 138 | json j4 = json::parse(d3); | 138 | json j4 = json::parse(d3); |
| 139 | j4["sig3"] = std::string(128, '0'); | 139 | j4["sig3"] = std::string(128, '0'); |
| 140 | CHECK(btf::VerifyDescriptor(ctx, j4.dump(), pk, o) && o.onion.empty(), | 140 | CHECK(!btf::VerifyDescriptor(ctx, j4.dump(), pk, o), |
| 141 | "bad onion signature ignored rather than trusted"); | 141 | "bad onion signature rejects the descriptor"); |
| 142 | json j5 = json::parse(d3); | ||
| 143 | j5.erase("sig3"); | ||
| 144 | CHECK(!btf::VerifyDescriptor(ctx, j5.dump(), pk, o), | ||
| 145 | "unsigned onion rejects the descriptor"); | ||
| 146 | // Removing both fields still verifies: the result is indistinguishable | ||
| 147 | // from a peer that never had an onion endpoint. That residual strip | ||
| 148 | // needs the onion bound into the primary sig, an upstream change. | ||
| 149 | json j6 = json::parse(d3); | ||
| 150 | j6.erase("onion"); j6.erase("sig3"); | ||
| 151 | CHECK(btf::VerifyDescriptor(ctx, j6.dump(), pk, o) && o.onion.empty(), | ||
| 152 | "full strip indistinguishable from no-onion (documented residual)"); | ||
| 142 | } | 153 | } |
| 143 | 154 | ||
| 144 | printf("full_resolve_chain_from_address\n"); | 155 | printf("full_resolve_chain_from_address\n"); |