Skip to content

Commit bdf3330

Browse files
committed
Add failure tests for offer message authentication
1 parent 559daeb commit bdf3330

File tree

2 files changed

+344
-1
lines changed

2 files changed

+344
-1
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2255,7 +2255,10 @@ where
22552255
event_persist_notifier: Notifier,
22562256
needs_persist_flag: AtomicBool,
22572257

2258+
#[cfg(not(any(test, feature = "_test_utils")))]
22582259
pending_offers_messages: Mutex<Vec<PendingOnionMessage<OffersMessage>>>,
2260+
#[cfg(any(test, feature = "_test_utils"))]
2261+
pub(crate) pending_offers_messages: Mutex<Vec<PendingOnionMessage<OffersMessage>>>,
22592262

22602263
/// Tracks the message events that are to be broadcasted when we are connected to some peer.
22612264
pending_broadcast_messages: Mutex<Vec<MessageSendEvent>>,

lightning/src/ln/offers_tests.rs

Lines changed: 341 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ use crate::offers::invoice::Bolt12Invoice;
5454
use crate::offers::invoice_error::InvoiceError;
5555
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields};
5656
use crate::offers::parse::Bolt12SemanticError;
57-
use crate::onion_message::messenger::PeeledOnion;
57+
use crate::onion_message::messenger::{Destination, PeeledOnion};
5858
use crate::onion_message::offers::OffersMessage;
5959
use crate::onion_message::packet::ParsedOnionMessageContents;
6060
use crate::routing::gossip::{NodeAlias, NodeId};
@@ -1234,6 +1234,346 @@ fn creates_refund_with_blinded_path_using_unannounced_introduction_node() {
12341234
}
12351235
}
12361236

1237+
/// Check that authentication fails when an invoice request is handled using the wrong context
1238+
/// (i.e., was sent directly or over an unexpected blinded path).
1239+
#[test]
1240+
fn fails_authentication_when_handling_invoice_request() {
1241+
let mut accept_forward_cfg = test_default_channel_config();
1242+
accept_forward_cfg.accept_forwards_to_priv_channels = true;
1243+
1244+
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
1245+
features.set_onion_messages_optional();
1246+
features.set_route_blinding_optional();
1247+
1248+
let chanmon_cfgs = create_chanmon_cfgs(6);
1249+
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
1250+
1251+
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
1252+
1253+
let node_chanmgrs = create_node_chanmgrs(
1254+
6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
1255+
);
1256+
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
1257+
1258+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
1259+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
1260+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
1261+
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
1262+
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
1263+
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
1264+
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
1265+
1266+
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
1267+
let alice_id = alice.node.get_our_node_id();
1268+
let bob_id = bob.node.get_our_node_id();
1269+
let charlie_id = charlie.node.get_our_node_id();
1270+
let david_id = david.node.get_our_node_id();
1271+
1272+
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
1273+
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
1274+
1275+
let offer = alice.node
1276+
.create_offer_builder(None)
1277+
.unwrap()
1278+
.amount_msats(10_000_000)
1279+
.build().unwrap();
1280+
assert_eq!(offer.metadata(), None);
1281+
assert_ne!(offer.signing_pubkey(), Some(alice_id));
1282+
assert!(!offer.paths().is_empty());
1283+
for path in offer.paths() {
1284+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
1285+
}
1286+
1287+
let invalid_path = alice.node
1288+
.create_offer_builder(None)
1289+
.unwrap()
1290+
.build().unwrap()
1291+
.paths().first().unwrap()
1292+
.clone();
1293+
assert_eq!(invalid_path.introduction_node, IntroductionNode::NodeId(bob_id));
1294+
1295+
// Send the invoice request directly to Alice instead of using a blinded path.
1296+
let payment_id = PaymentId([1; 32]);
1297+
david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None)
1298+
.unwrap();
1299+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1300+
1301+
connect_peers(david, alice);
1302+
#[cfg(not(c_bindings))] {
1303+
david.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().destination =
1304+
Destination::Node(alice_id);
1305+
}
1306+
#[cfg(c_bindings)] {
1307+
david.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 =
1308+
Destination::Node(alice_id);
1309+
}
1310+
1311+
let onion_message = david.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1312+
alice.onion_messenger.handle_onion_message(&david_id, &onion_message);
1313+
1314+
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
1315+
assert_eq!(invoice_request.amount_msats(), None);
1316+
assert_ne!(invoice_request.payer_id(), david_id);
1317+
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(charlie_id));
1318+
1319+
assert_eq!(alice.onion_messenger.next_onion_message_for_peer(charlie_id), None);
1320+
1321+
david.node.abandon_payment(payment_id);
1322+
get_event!(david, Event::InvoiceRequestFailed);
1323+
1324+
// Send the invoice request to Alice using an invalid blinded path.
1325+
let payment_id = PaymentId([2; 32]);
1326+
david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None)
1327+
.unwrap();
1328+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1329+
1330+
#[cfg(not(c_bindings))] {
1331+
david.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().destination =
1332+
Destination::BlindedPath(invalid_path);
1333+
}
1334+
#[cfg(c_bindings)] {
1335+
david.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 =
1336+
Destination::BlindedPath(invalid_path);
1337+
}
1338+
1339+
connect_peers(david, bob);
1340+
1341+
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
1342+
bob.onion_messenger.handle_onion_message(&david_id, &onion_message);
1343+
1344+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1345+
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
1346+
1347+
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
1348+
assert_eq!(invoice_request.amount_msats(), None);
1349+
assert_ne!(invoice_request.payer_id(), david_id);
1350+
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(charlie_id));
1351+
1352+
assert_eq!(alice.onion_messenger.next_onion_message_for_peer(charlie_id), None);
1353+
}
1354+
1355+
/// Check that authentication fails when an invoice is handled using the wrong context (i.e., was
1356+
/// sent over an unexpected blinded path).
1357+
#[test]
1358+
fn fails_authentication_when_handling_invoice_for_offer() {
1359+
let mut accept_forward_cfg = test_default_channel_config();
1360+
accept_forward_cfg.accept_forwards_to_priv_channels = true;
1361+
1362+
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
1363+
features.set_onion_messages_optional();
1364+
features.set_route_blinding_optional();
1365+
1366+
let chanmon_cfgs = create_chanmon_cfgs(6);
1367+
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
1368+
1369+
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
1370+
1371+
let node_chanmgrs = create_node_chanmgrs(
1372+
6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
1373+
);
1374+
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
1375+
1376+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
1377+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
1378+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
1379+
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
1380+
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
1381+
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
1382+
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
1383+
1384+
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
1385+
let alice_id = alice.node.get_our_node_id();
1386+
let bob_id = bob.node.get_our_node_id();
1387+
let charlie_id = charlie.node.get_our_node_id();
1388+
let david_id = david.node.get_our_node_id();
1389+
1390+
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
1391+
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
1392+
1393+
let offer = alice.node
1394+
.create_offer_builder(None)
1395+
.unwrap()
1396+
.amount_msats(10_000_000)
1397+
.build().unwrap();
1398+
assert_ne!(offer.signing_pubkey(), Some(alice_id));
1399+
assert!(!offer.paths().is_empty());
1400+
for path in offer.paths() {
1401+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
1402+
}
1403+
1404+
// Initiate an invoice request, but abandon tracking it.
1405+
let payment_id = PaymentId([1; 32]);
1406+
david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None)
1407+
.unwrap();
1408+
david.node.abandon_payment(payment_id);
1409+
get_event!(david, Event::InvoiceRequestFailed);
1410+
1411+
// Don't send the invoice request, but grab its reply path to use with a different request.
1412+
let invalid_reply_path = {
1413+
let mut pending_offers_messages = david.node.pending_offers_messages.lock().unwrap();
1414+
let pending_invoice_request = pending_offers_messages.pop().unwrap();
1415+
pending_offers_messages.clear();
1416+
#[cfg(not(c_bindings))] {
1417+
pending_invoice_request.reply_path
1418+
}
1419+
#[cfg(c_bindings)] {
1420+
pending_invoice_request.2
1421+
}
1422+
};
1423+
1424+
let payment_id = PaymentId([2; 32]);
1425+
david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None)
1426+
.unwrap();
1427+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1428+
1429+
// Swap out the reply path to force authentication to fail when handling the invoice since it
1430+
// will be sent over the wrong blinded path.
1431+
{
1432+
let mut pending_offers_messages = david.node.pending_offers_messages.lock().unwrap();
1433+
let mut pending_invoice_request = pending_offers_messages.first_mut().unwrap();
1434+
#[cfg(not(c_bindings))] {
1435+
pending_invoice_request.reply_path = invalid_reply_path;
1436+
}
1437+
#[cfg(c_bindings)] {
1438+
pending_invoice_request.2 = invalid_reply_path;
1439+
}
1440+
}
1441+
1442+
connect_peers(david, bob);
1443+
1444+
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
1445+
bob.onion_messenger.handle_onion_message(&david_id, &onion_message);
1446+
1447+
connect_peers(alice, charlie);
1448+
1449+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1450+
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
1451+
1452+
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
1453+
assert_eq!(invoice_request.amount_msats(), None);
1454+
assert_ne!(invoice_request.payer_id(), david_id);
1455+
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(charlie_id));
1456+
1457+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
1458+
charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message);
1459+
1460+
let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap();
1461+
david.onion_messenger.handle_onion_message(&charlie_id, &onion_message);
1462+
1463+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1464+
}
1465+
1466+
/// Check that authentication fails when an invoice is handled using the wrong context (i.e., was
1467+
/// sent directly or over an unexpected blinded path).
1468+
#[test]
1469+
fn fails_authentication_when_handling_invoice_for_refund() {
1470+
let mut accept_forward_cfg = test_default_channel_config();
1471+
accept_forward_cfg.accept_forwards_to_priv_channels = true;
1472+
1473+
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
1474+
features.set_onion_messages_optional();
1475+
features.set_route_blinding_optional();
1476+
1477+
let chanmon_cfgs = create_chanmon_cfgs(6);
1478+
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
1479+
1480+
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
1481+
1482+
let node_chanmgrs = create_node_chanmgrs(
1483+
6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
1484+
);
1485+
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
1486+
1487+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
1488+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
1489+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
1490+
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
1491+
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
1492+
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
1493+
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
1494+
1495+
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
1496+
let alice_id = alice.node.get_our_node_id();
1497+
let charlie_id = charlie.node.get_our_node_id();
1498+
let david_id = david.node.get_our_node_id();
1499+
1500+
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
1501+
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
1502+
1503+
let absolute_expiry = Duration::from_secs(u64::MAX);
1504+
let payment_id = PaymentId([1; 32]);
1505+
let refund = david.node
1506+
.create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None)
1507+
.unwrap()
1508+
.build().unwrap();
1509+
assert_ne!(refund.payer_id(), david_id);
1510+
assert!(!refund.paths().is_empty());
1511+
for path in refund.paths() {
1512+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(charlie_id));
1513+
}
1514+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1515+
1516+
// Send the invoice directly to David instead of using a blinded path.
1517+
let expected_invoice = alice.node.request_refund_payment(&refund).unwrap();
1518+
1519+
connect_peers(david, alice);
1520+
#[cfg(not(c_bindings))] {
1521+
alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().destination =
1522+
Destination::Node(david_id);
1523+
}
1524+
#[cfg(c_bindings)] {
1525+
alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 =
1526+
Destination::Node(david_id);
1527+
}
1528+
1529+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(david_id).unwrap();
1530+
david.onion_messenger.handle_onion_message(&alice_id, &onion_message);
1531+
1532+
let (invoice, _) = extract_invoice(david, &onion_message);
1533+
assert_eq!(invoice, expected_invoice);
1534+
1535+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1536+
david.node.abandon_payment(payment_id);
1537+
get_event!(david, Event::InvoiceRequestFailed);
1538+
1539+
// Send the invoice to David using an invalid blinded path.
1540+
let invalid_path = refund.paths().first().unwrap().clone();
1541+
let payment_id = PaymentId([2; 32]);
1542+
let refund = david.node
1543+
.create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None)
1544+
.unwrap()
1545+
.build().unwrap();
1546+
assert_ne!(refund.payer_id(), david_id);
1547+
assert!(!refund.paths().is_empty());
1548+
for path in refund.paths() {
1549+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(charlie_id));
1550+
}
1551+
1552+
let expected_invoice = alice.node.request_refund_payment(&refund).unwrap();
1553+
1554+
#[cfg(not(c_bindings))] {
1555+
alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().destination =
1556+
Destination::BlindedPath(invalid_path);
1557+
}
1558+
#[cfg(c_bindings)] {
1559+
alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 =
1560+
Destination::BlindedPath(invalid_path);
1561+
}
1562+
1563+
connect_peers(alice, charlie);
1564+
1565+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
1566+
charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message);
1567+
1568+
let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap();
1569+
david.onion_messenger.handle_onion_message(&charlie_id, &onion_message);
1570+
1571+
let (invoice, _) = extract_invoice(david, &onion_message);
1572+
assert_eq!(invoice, expected_invoice);
1573+
1574+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1575+
}
1576+
12371577
/// Fails creating or paying an offer when a blinded path cannot be created because no peers are
12381578
/// connected.
12391579
#[test]

0 commit comments

Comments
 (0)