Skip to content

Commit ea192d7

Browse files
committed
Add failure tests for offer message authentication
1 parent 4108d03 commit ea192d7

File tree

2 files changed

+242
-1
lines changed

2 files changed

+242
-1
lines changed

lightning/src/ln/channelmanager.rs

+3
Original file line numberDiff line numberDiff line change
@@ -2100,7 +2100,10 @@ where
21002100
event_persist_notifier: Notifier,
21012101
needs_persist_flag: AtomicBool,
21022102

2103+
#[cfg(not(any(test, feature = "_test_utils")))]
21032104
pending_offers_messages: Mutex<Vec<PendingOnionMessage<OffersMessage>>>,
2105+
#[cfg(any(test, feature = "_test_utils"))]
2106+
pub(crate) pending_offers_messages: Mutex<Vec<PendingOnionMessage<OffersMessage>>>,
21042107

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

lightning/src/ln/offers_tests.rs

+239-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,244 @@ 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+
david.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().destination =
1131+
Destination::Node(alice_id);
1132+
1133+
let onion_message = david.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1134+
alice.onion_messenger.handle_onion_message(&david_id, &onion_message);
1135+
1136+
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
1137+
assert_eq!(invoice_request.amount_msats(), None);
1138+
assert_ne!(invoice_request.payer_id(), david_id);
1139+
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(charlie_id));
1140+
1141+
assert_eq!(alice.onion_messenger.next_onion_message_for_peer(charlie_id), None);
1142+
}
1143+
1144+
/// Check that authentication fails when an invoice is handled using the wrong context (i.e., was
1145+
/// sent over an unexpected blinded path).
1146+
#[test]
1147+
fn fails_authentication_when_handling_invoice_for_offer() {
1148+
let mut accept_forward_cfg = test_default_channel_config();
1149+
accept_forward_cfg.accept_forwards_to_priv_channels = true;
1150+
1151+
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
1152+
features.set_onion_messages_optional();
1153+
features.set_route_blinding_optional();
1154+
1155+
let chanmon_cfgs = create_chanmon_cfgs(6);
1156+
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
1157+
1158+
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
1159+
1160+
let node_chanmgrs = create_node_chanmgrs(
1161+
6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
1162+
);
1163+
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
1164+
1165+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
1166+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
1167+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
1168+
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
1169+
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
1170+
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
1171+
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
1172+
1173+
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
1174+
let alice_id = alice.node.get_our_node_id();
1175+
let bob_id = bob.node.get_our_node_id();
1176+
let charlie_id = charlie.node.get_our_node_id();
1177+
let david_id = david.node.get_our_node_id();
1178+
1179+
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
1180+
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
1181+
1182+
let offer = alice.node
1183+
.create_offer_builder(None)
1184+
.unwrap()
1185+
.amount_msats(10_000_000)
1186+
.build().unwrap();
1187+
assert_ne!(offer.signing_pubkey(), Some(alice_id));
1188+
assert!(!offer.paths().is_empty());
1189+
for path in offer.paths() {
1190+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
1191+
}
1192+
1193+
// Initiate an invoice request, but abandon tracking it.
1194+
let payment_id_255 = PaymentId([255; 32]);
1195+
david.node.pay_for_offer(&offer, None, None, None, payment_id_255, Retry::Attempts(0), None)
1196+
.unwrap();
1197+
david.node.abandon_payment(payment_id_255);
1198+
get_event!(david, Event::InvoiceRequestFailed);
1199+
1200+
// Don't send the invoice request, but grab its reply path to use with a different request.
1201+
let invalid_reply_path = {
1202+
let mut penidng_offers_messages = david.node.pending_offers_messages.lock().unwrap();
1203+
let pending_invoice_request = penidng_offers_messages.pop().unwrap();
1204+
penidng_offers_messages.clear();
1205+
pending_invoice_request.reply_path
1206+
};
1207+
1208+
let payment_id = PaymentId([1; 32]);
1209+
david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None)
1210+
.unwrap();
1211+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1212+
1213+
// Swap out the reply path to force authentication to fail when handling the invoice since it
1214+
// will be sent over the wrong blinded path.
1215+
{
1216+
let mut penidng_offers_messages = david.node.pending_offers_messages.lock().unwrap();
1217+
let mut pending_invoice_request = penidng_offers_messages.first_mut().unwrap();
1218+
pending_invoice_request.reply_path = invalid_reply_path;
1219+
}
1220+
1221+
connect_peers(david, bob);
1222+
1223+
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
1224+
bob.onion_messenger.handle_onion_message(&david_id, &onion_message);
1225+
1226+
connect_peers(alice, charlie);
1227+
1228+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1229+
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
1230+
1231+
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
1232+
assert_eq!(invoice_request.amount_msats(), None);
1233+
assert_ne!(invoice_request.payer_id(), david_id);
1234+
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(charlie_id));
1235+
1236+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
1237+
charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message);
1238+
1239+
let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap();
1240+
david.onion_messenger.handle_onion_message(&charlie_id, &onion_message);
1241+
1242+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1243+
}
1244+
1245+
/// Check that authentication fails when an invoice is handled using the wrong context (i.e., was
1246+
/// sent directly or over an unexpected blinded path).
1247+
#[test]
1248+
fn fails_authentication_when_handling_invoice_for_refund() {
1249+
let mut accept_forward_cfg = test_default_channel_config();
1250+
accept_forward_cfg.accept_forwards_to_priv_channels = true;
1251+
1252+
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
1253+
features.set_onion_messages_optional();
1254+
features.set_route_blinding_optional();
1255+
1256+
let chanmon_cfgs = create_chanmon_cfgs(6);
1257+
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
1258+
1259+
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
1260+
1261+
let node_chanmgrs = create_node_chanmgrs(
1262+
6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
1263+
);
1264+
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
1265+
1266+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
1267+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
1268+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
1269+
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
1270+
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
1271+
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
1272+
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
1273+
1274+
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
1275+
let alice_id = alice.node.get_our_node_id();
1276+
let charlie_id = charlie.node.get_our_node_id();
1277+
let david_id = david.node.get_our_node_id();
1278+
1279+
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
1280+
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
1281+
1282+
let absolute_expiry = Duration::from_secs(u64::MAX);
1283+
let payment_id = PaymentId([1; 32]);
1284+
let refund = david.node
1285+
.create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None)
1286+
.unwrap()
1287+
.build().unwrap();
1288+
assert_ne!(refund.payer_id(), david_id);
1289+
assert!(!refund.paths().is_empty());
1290+
for path in refund.paths() {
1291+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(charlie_id));
1292+
}
1293+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1294+
1295+
let expected_invoice = alice.node.request_refund_payment(&refund).unwrap();
1296+
1297+
// Send the invoice directly to David instead of using a blinded path.
1298+
connect_peers(david, alice);
1299+
alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().destination =
1300+
Destination::Node(david_id);
1301+
1302+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(david_id).unwrap();
1303+
david.onion_messenger.handle_onion_message(&alice_id, &onion_message);
1304+
1305+
let invoice = extract_invoice(david, &onion_message);
1306+
assert_eq!(invoice, expected_invoice);
1307+
1308+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
1309+
}
1310+
10731311
/// Fails creating or paying an offer when a blinded path cannot be created because no peers are
10741312
/// connected.
10751313
#[test]

0 commit comments

Comments
 (0)