Skip to content

Commit 56b3d22

Browse files
committed
Add support for authenticating forwarding blinded path contexts
In the previous commit we added support for authenticating received blinded paths by using an additional secret as the AAD in the MAC. Here, we extend this to support authenticating blinded path contexts received for forwarding, allowing us to authenticate dummy hops added as padding. This will allow us to prevent a DoS attack where someone could create a blinded path which has many forwarding hops all for us as fictitious dummy hops, requiring us to decrypt many times only to find no useful onion message.
1 parent 1725a3b commit 56b3d22

File tree

2 files changed

+58
-17
lines changed

2 files changed

+58
-17
lines changed

lightning/src/onion_message/messenger.rs

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,17 +1120,31 @@ where
11201120
},
11211121
},
11221122
Ok((
1123-
Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
1124-
next_hop,
1125-
next_blinding_override,
1126-
})),
1123+
Payload::Forward {
1124+
control_tlvs: ForwardControlTlvs::Unblinded(ForwardTlvs {
1125+
next_hop,
1126+
next_blinding_override,
1127+
}),
1128+
control_tlvs_authenticated,
1129+
},
11271130
Some((next_hop_hmac, new_packet_bytes)),
11281131
)) => {
11291132
// TODO: we need to check whether `next_hop` is our node, in which case this is a dummy
11301133
// blinded hop and this onion message is destined for us. In this situation, we should keep
11311134
// unwrapping the onion layers to get to the final payload. Since we don't have the option
11321135
// of creating blinded paths with dummy hops currently, we should be ok to not handle this
11331136
// for now.
1137+
if control_tlvs_authenticated {
1138+
// TODO: When we start adding dummy hops, we should require the use of
1139+
// authenticated control TLVs, as it prevents a DoS attack where someone builds a
1140+
// blinded path to us which requires we decode hundreds of dummy hops only to find
1141+
// that we don't actually have a message inside to read.
1142+
// However, we should never accept a `control_tlvs_authenticated` packet which is
1143+
// *not* a dummy blinded hop we added, though it shouldn't be possible to reach in
1144+
// any case.
1145+
log_trace!(logger, "Received an authenticated to-forward onion message");
1146+
return Err(())
1147+
}
11341148
let packet_pubkey = msg.onion_routing_packet.public_key;
11351149
let new_pubkey_opt =
11361150
onion_utils::next_hop_pubkey(&secp_ctx, packet_pubkey, &onion_decode_ss);
@@ -2251,10 +2265,13 @@ fn packet_payloads_and_keys<
22512265
if num_unblinded_hops != 0 && unblinded_path_idx < num_unblinded_hops {
22522266
if let Some(ss) = prev_control_tlvs_ss.take() {
22532267
payloads.push((
2254-
Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
2255-
next_hop: NextMessageHop::NodeId(unblinded_pk_opt.unwrap()),
2256-
next_blinding_override: None,
2257-
})),
2268+
Payload::Forward {
2269+
control_tlvs: ForwardControlTlvs::Unblinded(ForwardTlvs {
2270+
next_hop: NextMessageHop::NodeId(unblinded_pk_opt.unwrap()),
2271+
next_blinding_override: None,
2272+
}),
2273+
control_tlvs_authenticated: false,
2274+
},
22582275
ss,
22592276
));
22602277
}
@@ -2263,17 +2280,23 @@ fn packet_payloads_and_keys<
22632280
} else if let Some((intro_node_id, blinding_pt)) = intro_node_id_blinding_pt.take() {
22642281
if let Some(control_tlvs_ss) = prev_control_tlvs_ss.take() {
22652282
payloads.push((
2266-
Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
2267-
next_hop: NextMessageHop::NodeId(intro_node_id),
2268-
next_blinding_override: Some(blinding_pt),
2269-
})),
2283+
Payload::Forward {
2284+
control_tlvs: ForwardControlTlvs::Unblinded(ForwardTlvs {
2285+
next_hop: NextMessageHop::NodeId(intro_node_id),
2286+
next_blinding_override: Some(blinding_pt),
2287+
}),
2288+
control_tlvs_authenticated: false,
2289+
},
22702290
control_tlvs_ss,
22712291
));
22722292
}
22732293
}
22742294
if blinded_path_idx < num_blinded_hops.saturating_sub(1) && enc_payload_opt.is_some() {
22752295
payloads.push((
2276-
Payload::Forward(ForwardControlTlvs::Blinded(enc_payload_opt.unwrap())),
2296+
Payload::Forward {
2297+
control_tlvs: ForwardControlTlvs::Blinded(enc_payload_opt.unwrap()),
2298+
control_tlvs_authenticated: false,
2299+
},
22772300
control_tlvs_ss,
22782301
));
22792302
blinded_path_idx += 1;

lightning/src/onion_message/packet.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,16 @@ impl LengthReadable for Packet {
110110
/// message content itself, such as an invoice request.
111111
pub(super) enum Payload<T: OnionMessageContents> {
112112
/// This payload is for an intermediate hop.
113-
Forward(ForwardControlTlvs),
113+
Forward {
114+
/// The [`ReceiveControlTlvs`] were authenticated with the additional key which was
115+
/// provided to [`ReadableArgs::read`].
116+
///
117+
/// This should not happen for blinded paths built by any node but us (i.e. messages
118+
/// forwarded through us to other nodes), but can be used to authenticate extra hops added
119+
/// to a blinded path as padding.
120+
control_tlvs_authenticated: bool,
121+
control_tlvs: ForwardControlTlvs,
122+
},
114123
/// This payload is for the final hop.
115124
Receive {
116125
/// The [`ReceiveControlTlvs`] were authenticated with the additional key which was
@@ -220,7 +229,10 @@ pub(super) enum ReceiveControlTlvs {
220229
impl<T: OnionMessageContents> Writeable for (Payload<T>, [u8; 32]) {
221230
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
222231
match &self.0 {
223-
Payload::Forward(ForwardControlTlvs::Blinded(encrypted_bytes)) => {
232+
Payload::Forward {
233+
control_tlvs: ForwardControlTlvs::Blinded(encrypted_bytes),
234+
control_tlvs_authenticated: _,
235+
} => {
224236
_encode_varint_length_prefixed_tlv!(w, { (4, encrypted_bytes, required_vec) })
225237
},
226238
Payload::Receive {
@@ -235,7 +247,10 @@ impl<T: OnionMessageContents> Writeable for (Payload<T>, [u8; 32]) {
235247
(message.tlv_type(), message, required)
236248
})
237249
},
238-
Payload::Forward(ForwardControlTlvs::Unblinded(control_tlvs)) => {
250+
Payload::Forward {
251+
control_tlvs: ForwardControlTlvs::Unblinded(control_tlvs),
252+
control_tlvs_authenticated: _,
253+
} => {
239254
let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
240255
_encode_varint_length_prefixed_tlv!(w, { (4, write_adapter, required) })
241256
},
@@ -314,7 +329,10 @@ impl<H: CustomOnionMessageHandler + ?Sized, L: Logger + ?Sized> ReadableArgs<(Sh
314329
if used_aad || message_type.is_some() {
315330
return Err(DecodeError::InvalidValue);
316331
}
317-
Ok(Payload::Forward(ForwardControlTlvs::Unblinded(tlvs)))
332+
Ok(Payload::Forward {
333+
control_tlvs: ForwardControlTlvs::Unblinded(tlvs),
334+
control_tlvs_authenticated: used_aad,
335+
})
318336
},
319337
Some(ChaChaDualPolyReadAdapter { readable: ControlTlvs::Receive(tlvs), used_aad }) => {
320338
Ok(Payload::Receive {

0 commit comments

Comments
 (0)