From 615eefb543822bf361bb11de07bf391a99095352 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 5 Sep 2024 17:32:08 -0400 Subject: [PATCH] Verify inbound ReleaseHeldHtlc messages via hmac. See AsyncPaymentsContext::hmac, but this prevents the recipient from deanonymizing us. Without this, if they are able to guess the correct payment id, then they could create a blinded path to us and confirm our identity. We also move the PAYMENT_HASH_HMAC_INPUT const to use &[7; 16], which is safe because this const was added since the last release. This ordering reads more smoothly. --- lightning/src/blinded_path/message.rs | 15 +++++++++- lightning/src/ln/channelmanager.rs | 27 ++++++++++++++++-- lightning/src/offers/signer.rs | 41 +++++++++++++++++++++------ 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index e6aa4e340..805d35a01 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -383,7 +383,18 @@ pub enum AsyncPaymentsContext { /// which of our pending outbound payments should be released to its often-offline payee. /// /// [`Offer`]: crate::offers::offer::Offer - payment_id: PaymentId + payment_id: PaymentId, + /// A nonce used for authenticating that a [`ReleaseHeldHtlc`] message is valid for a preceding + /// [`HeldHtlcAvailable`] message. + /// + /// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc + /// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable + nonce: Nonce, + /// Authentication code for the [`PaymentId`]. + /// + /// Prevents the recipient from being able to deanonymize us by creating a blinded path to us + /// containing the expected [`PaymentId`]. + hmac: Hmac, }, } @@ -412,6 +423,8 @@ impl_writeable_tlv_based_enum!(OffersContext, impl_writeable_tlv_based_enum!(AsyncPaymentsContext, (0, OutboundPayment) => { (0, payment_id, required), + (2, nonce, required), + (4, hmac, required), }, ); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 7e891f93d..b2f182a63 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -453,6 +453,24 @@ pub struct PaymentId(pub [u8; Self::LENGTH]); impl PaymentId { /// Number of bytes in the id. pub const LENGTH: usize = 32; + + /// Constructs an HMAC to include in [`AsyncPaymentsContext::OutboundPayment`] for the payment id + /// along with the given [`Nonce`]. + #[cfg(async_payments)] + pub fn hmac_for_async_payment( + &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, + ) -> Hmac { + signer::hmac_for_async_payment_id(*self, nonce, expanded_key) + } + + /// Authenticates the payment id using an HMAC and a [`Nonce`] taken from an + /// [`AsyncPaymentsContext::OutboundPayment`]. + #[cfg(async_payments)] + pub fn verify_for_async_payment( + &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, + ) -> Result<(), ()> { + signer::verify_async_payment_id(*self, hmac, nonce, expanded_key) + } } impl Verification for PaymentId { @@ -4353,8 +4371,12 @@ where } }; + let nonce = Nonce::from_entropy_source(&*self.entropy_source); + let hmac = payment_id.hmac_for_async_payment(nonce, &self.inbound_payment_key); let reply_paths = match self.create_blinded_paths( - MessageContext::AsyncPayments(AsyncPaymentsContext::OutboundPayment { payment_id }) + MessageContext::AsyncPayments( + AsyncPaymentsContext::OutboundPayment { payment_id, nonce, hmac } + ) ) { Ok(paths) => paths, Err(()) => { @@ -11209,7 +11231,8 @@ where fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) { #[cfg(async_payments)] { - let AsyncPaymentsContext::OutboundPayment { payment_id } = _context; + let AsyncPaymentsContext::OutboundPayment { payment_id, hmac, nonce } = _context; + if payment_id.verify_for_async_payment(hmac, nonce, &self.inbound_payment_key).is_err() { return } if let Err(e) = self.send_payment_for_static_invoice(payment_id, _message.payment_release_secret) { log_trace!( self.logger, "Failed to release held HTLC with payment id {} and release secret {:02x?}: {:?}", diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index 152c7af57..d8caa2175 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -39,9 +39,12 @@ const WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[4; 16]; // HMAC input for a `PaymentId`. The HMAC is used in `OffersContext::OutboundPayment`. const OFFER_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[5; 16]; +// HMAC input for a `PaymentId`. The HMAC is used in `AsyncPaymentsContext::OutboundPayment`. +#[cfg(async_payments)] +const ASYNC_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[6; 16]; // HMAC input for a `PaymentHash`. The HMAC is used in `OffersContext::InboundPayment`. -const PAYMENT_HASH_HMAC_INPUT: &[u8; 16] = &[6; 16]; +const PAYMENT_HASH_HMAC_INPUT: &[u8; 16] = &[7; 16]; /// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be /// verified. @@ -402,14 +405,7 @@ fn hmac_for_message<'a>( pub(crate) fn hmac_for_offer_payment_id( payment_id: PaymentId, nonce: Nonce, expanded_key: &ExpandedKey, ) -> Hmac { - const IV_BYTES: &[u8; IV_LEN] = b"LDK Payment ID ~"; - let mut hmac = expanded_key.hmac_for_offer(); - hmac.input(IV_BYTES); - hmac.input(&nonce.0); - hmac.input(OFFER_PAYMENT_ID_HMAC_INPUT); - hmac.input(&payment_id.0); - - Hmac::from_engine(hmac) + hmac_for_payment_id(payment_id, nonce, OFFER_PAYMENT_ID_HMAC_INPUT, expanded_key) } pub(crate) fn verify_offer_payment_id( @@ -436,3 +432,30 @@ pub(crate) fn verify_payment_hash( ) -> Result<(), ()> { if hmac_for_payment_hash(payment_hash, nonce, expanded_key) == hmac { Ok(()) } else { Err(()) } } + +#[cfg(async_payments)] +pub(crate) fn hmac_for_async_payment_id( + payment_id: PaymentId, nonce: Nonce, expanded_key: &ExpandedKey, +) -> Hmac { + hmac_for_payment_id(payment_id, nonce, ASYNC_PAYMENT_ID_HMAC_INPUT, expanded_key) +} + +#[cfg(async_payments)] +pub(crate) fn verify_async_payment_id( + payment_id: PaymentId, hmac: Hmac, nonce: Nonce, expanded_key: &ExpandedKey, +) -> Result<(), ()> { + if hmac_for_async_payment_id(payment_id, nonce, expanded_key) == hmac { Ok(()) } else { Err(()) } +} + +fn hmac_for_payment_id( + payment_id: PaymentId, nonce: Nonce, hmac_input: &[u8; 16], expanded_key: &ExpandedKey, +) -> Hmac { + const IV_BYTES: &[u8; IV_LEN] = b"LDK Payment ID ~"; + let mut hmac = expanded_key.hmac_for_offer(); + hmac.input(IV_BYTES); + hmac.input(&nonce.0); + hmac.input(hmac_input); + hmac.input(&payment_id.0); + + Hmac::from_engine(hmac) +} -- 2.39.5