Skip to content

Commit fd1d5fd

Browse files
committed
Randomize initial onion packet data.
This avoids at least the trivial hop count discovery attack, though other obvious ones remain and are slightly harder to avoid. See lightning/bolts#697
1 parent 1b9bbe5 commit fd1d5fd

File tree

9 files changed

+57
-22
lines changed

9 files changed

+57
-22
lines changed

fuzz/fuzz_targets/chanmon_fail_consistency.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,10 @@ impl KeysInterface for KeyProvider {
166166
}
167167
}
168168

169-
fn get_session_key(&self) -> SecretKey {
169+
fn get_onion_rand(&self) -> (SecretKey, [u8; 32]) {
170170
let id = self.session_id.fetch_add(1, atomic::Ordering::Relaxed);
171-
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, id, 10, self.node_id]).unwrap()
171+
(SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, id, 10, self.node_id]).unwrap(),
172+
[0; 32])
172173
}
173174

174175
fn get_channel_id(&self) -> [u8; 32] {

fuzz/fuzz_targets/full_stack_target.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,9 +277,10 @@ impl KeysInterface for KeyProvider {
277277
}
278278
}
279279

280-
fn get_session_key(&self) -> SecretKey {
280+
fn get_onion_rand(&self) -> (SecretKey, [u8; 32]) {
281281
let ctr = self.counter.fetch_add(1, Ordering::Relaxed) as u8;
282-
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, ctr]).unwrap()
282+
(SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, ctr]).unwrap(),
283+
[0; 32])
283284
}
284285

285286
fn get_channel_id(&self) -> [u8; 32] {

lightning/src/chain/keysinterface.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ pub trait KeysInterface: Send + Sync {
7777
/// Get a new set of ChannelKeys for per-channel secrets. These MUST be unique even if you
7878
/// restarted with some stale data!
7979
fn get_channel_keys(&self, inbound: bool) -> ChannelKeys;
80-
/// Get a secret for construting an onion packet
81-
fn get_session_key(&self) -> SecretKey;
80+
/// Get a secret and PRNG seed for construting an onion packet
81+
fn get_onion_rand(&self) -> (SecretKey, [u8; 32]);
8282
/// Get a unique temporary channel id. Channels will be referred to by this until the funding
8383
/// transaction is created, at which point they will use the outpoint in the funding
8484
/// transaction.
@@ -258,13 +258,20 @@ impl KeysInterface for KeysManager {
258258
}
259259
}
260260

261-
fn get_session_key(&self) -> SecretKey {
261+
fn get_onion_rand(&self) -> (SecretKey, [u8; 32]) {
262262
let mut sha = self.unique_start.clone();
263263

264264
let child_ix = self.session_child_index.fetch_add(1, Ordering::AcqRel);
265265
let child_privkey = self.session_master_key.ckd_priv(&self.secp_ctx, ChildNumber::from_hardened_idx(child_ix as u32).expect("key space exhausted")).expect("Your RNG is busted");
266266
sha.input(&child_privkey.private_key.key[..]);
267-
SecretKey::from_slice(&Sha256::from_engine(sha).into_inner()).expect("Your RNG is busted")
267+
268+
let mut rng_seed = sha.clone();
269+
// Not exactly the most ideal construction, but the second value will get fed into
270+
// ChaCha so it is another step harder to break.
271+
rng_seed.input(b"RNG Seed Salt");
272+
sha.input(b"Session Key Salt");
273+
(SecretKey::from_slice(&Sha256::from_engine(sha).into_inner()).expect("Your RNG is busted"),
274+
Sha256::from_engine(rng_seed).into_inner())
268275
}
269276

270277
fn get_channel_id(&self) -> [u8; 32] {

lightning/src/ln/channel.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4199,7 +4199,7 @@ mod tests {
41994199
}
42004200

42014201
fn get_channel_keys(&self, _inbound: bool) -> ChannelKeys { self.chan_keys.clone() }
4202-
fn get_session_key(&self) -> SecretKey { panic!(); }
4202+
fn get_onion_rand(&self) -> (SecretKey, [u8; 32]) { panic!(); }
42034203
fn get_channel_id(&self) -> [u8; 32] { [0; 32] }
42044204
}
42054205

lightning/src/ln/channelmanager.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,21 @@ impl<'a> ChannelManager<'a> {
896896
};
897897

898898
let pending_forward_info = if next_hop_data.hmac == [0; 32] {
899+
#[cfg(test)]
900+
{
901+
// In tests, make sure that the initial onion pcket data is, at least, non-0.
902+
// We could do some fancy randomness test here, but, ehh, whatever.
903+
// This checks for the issue where you can calculate the path length given the
904+
// onion data as all the path entries that the originator sent will be here
905+
// as-is (and were originally 0s).
906+
// Of course reverse path calculation is still pretty easy given naive routing
907+
// algorithms, but this fixes the most-obvious case.
908+
let mut new_packet_data = [0; 19*65];
909+
chacha.process(&msg.onion_routing_packet.hop_data[65..], &mut new_packet_data[0..19*65]);
910+
assert_ne!(new_packet_data[0..65], [0; 65][..]);
911+
assert_ne!(new_packet_data[..], [0; 19*65][..]);
912+
}
913+
899914
// OUR PAYMENT!
900915
// final_expiry_too_soon
901916
if (msg.cltv_expiry as u64) < self.latest_block_height.load(Ordering::Acquire) as u64 + (CLTV_CLAIM_BUFFER + LATENCY_GRACE_PERIOD_BLOCKS) as u64 {
@@ -1089,14 +1104,14 @@ impl<'a> ChannelManager<'a> {
10891104
}
10901105
}
10911106

1092-
let session_priv = self.keys_manager.get_session_key();
1107+
let (session_priv, prng_seed) = self.keys_manager.get_onion_rand();
10931108

10941109
let cur_height = self.latest_block_height.load(Ordering::Acquire) as u32 + 1;
10951110

10961111
let onion_keys = secp_call!(onion_utils::construct_onion_keys(&self.secp_ctx, &route, &session_priv),
10971112
APIError::RouteError{err: "Pubkey along hop was maliciously selected"});
10981113
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height)?;
1099-
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, &payment_hash);
1114+
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, prng_seed, &payment_hash);
11001115

11011116
let _ = self.total_consistency_lock.read().unwrap();
11021117

lightning/src/ln/functional_tests.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,7 +1435,7 @@ fn do_channel_reserve_test(test_recv: bool) {
14351435
let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
14361436
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route, &session_priv).unwrap();
14371437
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height).unwrap();
1438-
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, &our_payment_hash);
1438+
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &our_payment_hash);
14391439
let msg = msgs::UpdateAddHTLC {
14401440
channel_id: chan_1.2,
14411441
htlc_id,
@@ -4815,7 +4815,7 @@ fn test_onion_failure() {
48154815
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
48164816
let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height).unwrap();
48174817
onion_payloads[0].realm = 3;
4818-
msg.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, &payment_hash);
4818+
msg.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash);
48194819
}, ||{}, true, Some(PERM|1), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));//XXX incremented channels idx here
48204820

48214821
// final node failure
@@ -4825,7 +4825,7 @@ fn test_onion_failure() {
48254825
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
48264826
let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height).unwrap();
48274827
onion_payloads[1].realm = 3;
4828-
msg.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, &payment_hash);
4828+
msg.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash);
48294829
}, ||{}, false, Some(PERM|1), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));
48304830

48314831
// the following three with run_onion_failure_test_with_fail_intercept() test only the origin node
@@ -5003,7 +5003,7 @@ fn test_onion_failure() {
50035003
route.hops[1].cltv_expiry_delta += CLTV_FAR_FAR_AWAY + route.hops[0].cltv_expiry_delta + 1;
50045004
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
50055005
let (onion_payloads, _, htlc_cltv) = onion_utils::build_onion_payloads(&route, height).unwrap();
5006-
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, &payment_hash);
5006+
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash);
50075007
msg.cltv_expiry = htlc_cltv;
50085008
msg.onion_routing_packet = onion_packet;
50095009
}, ||{}, true, Some(21), None);
@@ -5253,7 +5253,7 @@ fn test_update_add_htlc_bolt2_receiver_check_max_htlc_limit() {
52535253
let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
52545254
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::signing_only(), &route, &session_priv).unwrap();
52555255
let (onion_payloads, _htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height).unwrap();
5256-
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, &our_payment_hash);
5256+
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &our_payment_hash);
52575257

52585258
let mut msg = msgs::UpdateAddHTLC {
52595259
channel_id: chan.2,

lightning/src/ln/onion_utils.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,18 @@ fn xor_bufs(dst: &mut[u8], src: &[u8]) {
161161
}
162162
}
163163

164+
164165
const ZERO:[u8; 21*65] = [0; 21*65];
165-
pub(super) fn construct_onion_packet(mut payloads: Vec<msgs::OnionHopData>, onion_keys: Vec<OnionKeys>, associated_data: &PaymentHash) -> msgs::OnionPacket {
166+
pub(super) fn construct_onion_packet(payloads: Vec<msgs::OnionHopData>, onion_keys: Vec<OnionKeys>, prng_seed: [u8; 32], associated_data: &PaymentHash) -> msgs::OnionPacket {
167+
let mut packet_data = [0; 20*65];
168+
169+
let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
170+
chacha.process(&[0; 20*65], &mut packet_data);
171+
172+
construct_onion_packet_with_init_noise(payloads, onion_keys, packet_data, associated_data)
173+
}
174+
175+
fn construct_onion_packet_with_init_noise(mut payloads: Vec<msgs::OnionHopData>, onion_keys: Vec<OnionKeys>, mut packet_data: [u8; 20*65], associated_data: &PaymentHash) -> msgs::OnionPacket {
166176
let mut buf = Vec::with_capacity(21*65);
167177
buf.resize(21*65, 0);
168178

@@ -181,7 +191,6 @@ pub(super) fn construct_onion_packet(mut payloads: Vec<msgs::OnionHopData>, onio
181191
res
182192
};
183193

184-
let mut packet_data = [0; 20*65];
185194
let mut hmac_res = [0; 32];
186195

187196
for (i, (payload, keys)) in payloads.iter_mut().zip(onion_keys.iter()).rev().enumerate() {
@@ -547,7 +556,8 @@ mod tests {
547556
},
548557
);
549558

550-
let packet = super::construct_onion_packet(payloads, onion_keys, &PaymentHash([0x42; 32]));
559+
let packet = [0; 20*65];
560+
let packet = super::construct_onion_packet_with_init_noise(payloads, onion_keys, packet, &PaymentHash([0x42; 32]));
551561
// Just check the final packet encoding, as it includes all the per-hop vectors in it
552562
// anyway...
553563
assert_eq!(packet.encode(), hex::decode("0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a716a996c7845c93d90e4ecbb9bde4ece2f69425c99e4bc820e44485455f135edc0d10f7d61ab590531cf08000179a333a347f8b4072f216400406bdf3bf038659793d4a1fd7b246979e3150a0a4cb052c9ec69acf0f48c3d39cd55675fe717cb7d80ce721caad69320c3a469a202f1e468c67eaf7a7cd8226d0fd32f7b48084dca885d56047694762b67021713ca673929c163ec36e04e40ca8e1c6d17569419d3039d9a1ec866abe044a9ad635778b961fc0776dc832b3a451bd5d35072d2269cf9b040f6b7a7dad84fb114ed413b1426cb96ceaf83825665ed5a1d002c1687f92465b49ed4c7f0218ff8c6c7dd7221d589c65b3b9aaa71a41484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f9172307c7268724c3618e6817abd793adc214a0dc0bc616816632f27ea336fb56dfd").unwrap());

lightning/src/util/chacha20.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ mod real_chacha {
224224
self.offset = 0;
225225
}
226226

227+
#[inline] // Useful cause input may be 0s on stack that should be optimized out
227228
pub fn process(&mut self, input: &[u8], output: &mut [u8]) {
228229
assert!(input.len() == output.len());
229230
let len = input.len();

lightning/src/util/test_utils.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,10 @@ impl keysinterface::KeysInterface for TestKeysInterface {
226226
fn get_shutdown_pubkey(&self) -> PublicKey { self.backing.get_shutdown_pubkey() }
227227
fn get_channel_keys(&self, inbound: bool) -> keysinterface::ChannelKeys { self.backing.get_channel_keys(inbound) }
228228

229-
fn get_session_key(&self) -> SecretKey {
229+
fn get_onion_rand(&self) -> (SecretKey, [u8; 32]) {
230230
match *self.override_session_priv.lock().unwrap() {
231-
Some(key) => key.clone(),
232-
None => self.backing.get_session_key()
231+
Some(key) => (key.clone(), [0; 32]),
232+
None => self.backing.get_onion_rand()
233233
}
234234
}
235235

0 commit comments

Comments
 (0)