Skip to content

Commit ad214bc

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 cfa4877 commit ad214bc

File tree

2 files changed

+56
-17
lines changed

2 files changed

+56
-17
lines changed

lightning/src/onion_message/messenger.rs

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,17 +1120,29 @@ 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:
1125+
ForwardControlTlvs::Unblinded(ForwardTlvs { next_hop, next_blinding_override }),
1126+
control_tlvs_authenticated,
1127+
},
11271128
Some((next_hop_hmac, new_packet_bytes)),
11281129
)) => {
11291130
// TODO: we need to check whether `next_hop` is our node, in which case this is a dummy
11301131
// blinded hop and this onion message is destined for us. In this situation, we should keep
11311132
// unwrapping the onion layers to get to the final payload. Since we don't have the option
11321133
// of creating blinded paths with dummy hops currently, we should be ok to not handle this
11331134
// for now.
1135+
if control_tlvs_authenticated {
1136+
// TODO: When we start adding dummy hops, we should require the use of
1137+
// authenticated control TLVs, as it prevents a DoS attack where someone builds a
1138+
// blinded path to us which requires we decode hundreds of dummy hops only to find
1139+
// that we don't actually have a message inside to read.
1140+
// However, we should never accept a `control_tlvs_authenticated` packet which is
1141+
// *not* a dummy blinded hop we added, though it shouldn't be possible to reach in
1142+
// any case.
1143+
log_trace!(logger, "Received an authenticated to-forward onion message");
1144+
return Err(());
1145+
}
11341146
let packet_pubkey = msg.onion_routing_packet.public_key;
11351147
let new_pubkey_opt =
11361148
onion_utils::next_hop_pubkey(&secp_ctx, packet_pubkey, &onion_decode_ss);
@@ -2251,10 +2263,13 @@ fn packet_payloads_and_keys<
22512263
if num_unblinded_hops != 0 && unblinded_path_idx < num_unblinded_hops {
22522264
if let Some(ss) = prev_control_tlvs_ss.take() {
22532265
payloads.push((
2254-
Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
2255-
next_hop: NextMessageHop::NodeId(unblinded_pk_opt.unwrap()),
2256-
next_blinding_override: None,
2257-
})),
2266+
Payload::Forward {
2267+
control_tlvs: ForwardControlTlvs::Unblinded(ForwardTlvs {
2268+
next_hop: NextMessageHop::NodeId(unblinded_pk_opt.unwrap()),
2269+
next_blinding_override: None,
2270+
}),
2271+
control_tlvs_authenticated: false,
2272+
},
22582273
ss,
22592274
));
22602275
}
@@ -2263,17 +2278,23 @@ fn packet_payloads_and_keys<
22632278
} else if let Some((intro_node_id, blinding_pt)) = intro_node_id_blinding_pt.take() {
22642279
if let Some(control_tlvs_ss) = prev_control_tlvs_ss.take() {
22652280
payloads.push((
2266-
Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
2267-
next_hop: NextMessageHop::NodeId(intro_node_id),
2268-
next_blinding_override: Some(blinding_pt),
2269-
})),
2281+
Payload::Forward {
2282+
control_tlvs: ForwardControlTlvs::Unblinded(ForwardTlvs {
2283+
next_hop: NextMessageHop::NodeId(intro_node_id),
2284+
next_blinding_override: Some(blinding_pt),
2285+
}),
2286+
control_tlvs_authenticated: false,
2287+
},
22702288
control_tlvs_ss,
22712289
));
22722290
}
22732291
}
22742292
if blinded_path_idx < num_blinded_hops.saturating_sub(1) && enc_payload_opt.is_some() {
22752293
payloads.push((
2276-
Payload::Forward(ForwardControlTlvs::Blinded(enc_payload_opt.unwrap())),
2294+
Payload::Forward {
2295+
control_tlvs: ForwardControlTlvs::Blinded(enc_payload_opt.unwrap()),
2296+
control_tlvs_authenticated: false,
2297+
},
22772298
control_tlvs_ss,
22782299
));
22792300
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
@@ -223,7 +232,10 @@ pub(super) enum ReceiveControlTlvs {
223232
impl<T: OnionMessageContents> Writeable for (Payload<T>, [u8; 32]) {
224233
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
225234
match &self.0 {
226-
Payload::Forward(ForwardControlTlvs::Blinded(encrypted_bytes)) => {
235+
Payload::Forward {
236+
control_tlvs: ForwardControlTlvs::Blinded(encrypted_bytes),
237+
control_tlvs_authenticated: _,
238+
} => {
227239
_encode_varint_length_prefixed_tlv!(w, { (4, encrypted_bytes, required_vec) })
228240
},
229241
Payload::Receive {
@@ -238,7 +250,10 @@ impl<T: OnionMessageContents> Writeable for (Payload<T>, [u8; 32]) {
238250
(message.tlv_type(), message, required)
239251
})
240252
},
241-
Payload::Forward(ForwardControlTlvs::Unblinded(control_tlvs)) => {
253+
Payload::Forward {
254+
control_tlvs: ForwardControlTlvs::Unblinded(control_tlvs),
255+
control_tlvs_authenticated: _,
256+
} => {
242257
let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
243258
_encode_varint_length_prefixed_tlv!(w, { (4, write_adapter, required) })
244259
},
@@ -320,7 +335,10 @@ impl<H: CustomOnionMessageHandler + ?Sized, L: Logger + ?Sized>
320335
if used_aad || message_type.is_some() {
321336
return Err(DecodeError::InvalidValue);
322337
}
323-
Ok(Payload::Forward(ForwardControlTlvs::Unblinded(tlvs)))
338+
Ok(Payload::Forward {
339+
control_tlvs: ForwardControlTlvs::Unblinded(tlvs),
340+
control_tlvs_authenticated: used_aad,
341+
})
324342
},
325343
Some(ChaChaDualPolyReadAdapter { readable: ControlTlvs::Receive(tlvs), used_aad }) => {
326344
Ok(Payload::Receive {

0 commit comments

Comments
 (0)