Skip to content

Commit 5cfad02

Browse files
committed
Add failure tests for offer message authentication
1 parent b34c6b0 commit 5cfad02

File tree

2 files changed

+264
-1
lines changed

2 files changed

+264
-1
lines changed

lightning/src/ln/channelmanager.rs

+3
Original file line numberDiff line numberDiff line change
@@ -2184,7 +2184,10 @@ where
21842184
event_persist_notifier: Notifier,
21852185
needs_persist_flag: AtomicBool,
21862186

2187+
#[cfg(not(any(test, feature = "_test_utils")))]
21872188
pending_offers_messages: Mutex<Vec<PendingOnionMessage<OffersMessage>>>,
2189+
#[cfg(any(test, feature = "_test_utils"))]
2190+
pub(crate) pending_offers_messages: Mutex<Vec<PendingOnionMessage<OffersMessage>>>,
21882191

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

lightning/src/ln/offers_tests.rs

+261-1
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};
@@ -1070,6 +1070,266 @@ fn creates_refund_with_blinded_path_using_unannounced_introduction_node() {
10701070
}
10711071
}
10721072

1073+
/// Check that authentication fails when an invoice request is handled using the wrong context
1074+
/// (i.e., was sent directly or over an unexpected blinded path).
1075+
#[test]
1076+
fn fails_authentication_when_handling_invoice_request() {
1077+
let mut accept_forward_cfg = test_default_channel_config();
1078+
accept_forward_cfg.accept_forwards_to_priv_channels = true;
1079+
1080+
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
1081+
features.set_onion_messages_optional();
1082+
features.set_route_blinding_optional();
1083+
1084+
let chanmon_cfgs = create_chanmon_cfgs(6);
1085+
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
1086+
1087+
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
1088+
1089+
let node_chanmgrs = create_node_chanmgrs(
1090+
6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
1091+
);
1092+
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
1093+
1094+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
1095+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
1096+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
1097+
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
1098+
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
1099+
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
1100+
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
1101+
1102+
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
1103+
let alice_id = alice.node.get_our_node_id();
1104+
let bob_id = bob.node.get_our_node_id();
1105+
let charlie_id = charlie.node.get_our_node_id();
1106+
let david_id = david.node.get_our_node_id();
1107+
1108+
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
1109+
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
1110+
1111+
let offer = alice.node
1112+
.create_offer_builder(None)
1113+
.unwrap()
1114+
.amount_msats(10_000_000)
1115+
.build().unwrap();
1116+
assert_eq!(offer.metadata(), None);
1117+
assert_ne!(offer.signing_pubkey(), Some(alice_id));
1118+
assert!(!offer.paths().is_empty());
1119+
for path in offer.paths() {
1120+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
1121+
}
1122+
1123+
let payment_id = PaymentId([1; 32]);
1124+
david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None)
1125+
.unwrap();
1126+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1127+
1128+
// Send the invoice request directly to Alice instead of using a blinded path.
1129+
connect_peers(david, alice);
1130+
#[cfg(not(c_bindings))] {
1131+
david.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().destination =
1132+
Destination::Node(alice_id);
1133+
}
1134+
#[cfg(c_bindings)] {
1135+
david.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 =
1136+
Destination::Node(alice_id);
1137+
}
1138+
1139+
let onion_message = david.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1140+
alice.onion_messenger.handle_onion_message(&david_id, &onion_message);
1141+
1142+
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
1143+
assert_eq!(invoice_request.amount_msats(), None);
1144+
assert_ne!(invoice_request.payer_id(), david_id);
1145+
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(charlie_id));
1146+
1147+
assert_eq!(alice.onion_messenger.next_onion_message_for_peer(charlie_id), None);
1148+
}
1149+
1150+
/// Check that authentication fails when an invoice is handled using the wrong context (i.e., was
1151+
/// sent over an unexpected blinded path).
1152+
#[test]
1153+
fn fails_authentication_when_handling_invoice_for_offer() {
1154+
let mut accept_forward_cfg = test_default_channel_config();
1155+
accept_forward_cfg.accept_forwards_to_priv_channels = true;
1156+
1157+
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
1158+
features.set_onion_messages_optional();
1159+
features.set_route_blinding_optional();
1160+
1161+
let chanmon_cfgs = create_chanmon_cfgs(6);
1162+
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
1163+
1164+
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
1165+
1166+
let node_chanmgrs = create_node_chanmgrs(
1167+
6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
1168+
);
1169+
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
1170+
1171+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
1172+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
1173+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
1174+
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
1175+
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
1176+
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
1177+
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
1178+
1179+
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
1180+
let alice_id = alice.node.get_our_node_id();
1181+
let bob_id = bob.node.get_our_node_id();
1182+
let charlie_id = charlie.node.get_our_node_id();
1183+
let david_id = david.node.get_our_node_id();
1184+
1185+
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
1186+
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
1187+
1188+
let offer = alice.node
1189+
.create_offer_builder(None)
1190+
.unwrap()
1191+
.amount_msats(10_000_000)
1192+
.build().unwrap();
1193+
assert_ne!(offer.signing_pubkey(), Some(alice_id));
1194+
assert!(!offer.paths().is_empty());
1195+
for path in offer.paths() {
1196+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
1197+
}
1198+
1199+
// Initiate an invoice request, but abandon tracking it.
1200+
let payment_id_255 = PaymentId([255; 32]);
1201+
david.node.pay_for_offer(&offer, None, None, None, payment_id_255, Retry::Attempts(0), None)
1202+
.unwrap();
1203+
david.node.abandon_payment(payment_id_255);
1204+
get_event!(david, Event::InvoiceRequestFailed);
1205+
1206+
// Don't send the invoice request, but grab its reply path to use with a different request.
1207+
let invalid_reply_path = {
1208+
let mut penidng_offers_messages = david.node.pending_offers_messages.lock().unwrap();
1209+
let pending_invoice_request = penidng_offers_messages.pop().unwrap();
1210+
penidng_offers_messages.clear();
1211+
#[cfg(not(c_bindings))] {
1212+
pending_invoice_request.reply_path
1213+
}
1214+
#[cfg(c_bindings)] {
1215+
pending_invoice_request.2
1216+
}
1217+
};
1218+
1219+
let payment_id = PaymentId([1; 32]);
1220+
david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None)
1221+
.unwrap();
1222+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1223+
1224+
// Swap out the reply path to force authentication to fail when handling the invoice since it
1225+
// will be sent over the wrong blinded path.
1226+
{
1227+
let mut penidng_offers_messages = david.node.pending_offers_messages.lock().unwrap();
1228+
let mut pending_invoice_request = penidng_offers_messages.first_mut().unwrap();
1229+
#[cfg(not(c_bindings))] {
1230+
pending_invoice_request.reply_path = invalid_reply_path;
1231+
}
1232+
#[cfg(c_bindings)] {
1233+
pending_invoice_request.2 = invalid_reply_path;
1234+
}
1235+
}
1236+
1237+
connect_peers(david, bob);
1238+
1239+
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
1240+
bob.onion_messenger.handle_onion_message(&david_id, &onion_message);
1241+
1242+
connect_peers(alice, charlie);
1243+
1244+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1245+
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
1246+
1247+
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
1248+
assert_eq!(invoice_request.amount_msats(), None);
1249+
assert_ne!(invoice_request.payer_id(), david_id);
1250+
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(charlie_id));
1251+
1252+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
1253+
charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message);
1254+
1255+
let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap();
1256+
david.onion_messenger.handle_onion_message(&charlie_id, &onion_message);
1257+
1258+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1259+
}
1260+
1261+
/// Check that authentication fails when an invoice is handled using the wrong context (i.e., was
1262+
/// sent directly or over an unexpected blinded path).
1263+
#[test]
1264+
fn fails_authentication_when_handling_invoice_for_refund() {
1265+
let mut accept_forward_cfg = test_default_channel_config();
1266+
accept_forward_cfg.accept_forwards_to_priv_channels = true;
1267+
1268+
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
1269+
features.set_onion_messages_optional();
1270+
features.set_route_blinding_optional();
1271+
1272+
let chanmon_cfgs = create_chanmon_cfgs(6);
1273+
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
1274+
1275+
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
1276+
1277+
let node_chanmgrs = create_node_chanmgrs(
1278+
6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
1279+
);
1280+
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
1281+
1282+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
1283+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
1284+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
1285+
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
1286+
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
1287+
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
1288+
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
1289+
1290+
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
1291+
let alice_id = alice.node.get_our_node_id();
1292+
let charlie_id = charlie.node.get_our_node_id();
1293+
let david_id = david.node.get_our_node_id();
1294+
1295+
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
1296+
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
1297+
1298+
let absolute_expiry = Duration::from_secs(u64::MAX);
1299+
let payment_id = PaymentId([1; 32]);
1300+
let refund = david.node
1301+
.create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None)
1302+
.unwrap()
1303+
.build().unwrap();
1304+
assert_ne!(refund.payer_id(), david_id);
1305+
assert!(!refund.paths().is_empty());
1306+
for path in refund.paths() {
1307+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(charlie_id));
1308+
}
1309+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1310+
1311+
let expected_invoice = alice.node.request_refund_payment(&refund).unwrap();
1312+
1313+
// Send the invoice directly to David instead of using a blinded path.
1314+
connect_peers(david, alice);
1315+
#[cfg(not(c_bindings))] {
1316+
alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().destination =
1317+
Destination::Node(david_id);
1318+
}
1319+
#[cfg(c_bindings)] {
1320+
alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 =
1321+
Destination::Node(david_id);
1322+
}
1323+
1324+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(david_id).unwrap();
1325+
david.onion_messenger.handle_onion_message(&alice_id, &onion_message);
1326+
1327+
let invoice = extract_invoice(david, &onion_message);
1328+
assert_eq!(invoice, expected_invoice);
1329+
1330+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1331+
}
1332+
10731333
/// Fails creating or paying an offer when a blinded path cannot be created because no peers are
10741334
/// connected.
10751335
#[test]

0 commit comments

Comments
 (0)