]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Verify inbound ReleaseHeldHtlc messages via hmac.
authorValentine Wallace <vwallace@protonmail.com>
Thu, 5 Sep 2024 21:32:08 +0000 (17:32 -0400)
committerValentine Wallace <vwallace@protonmail.com>
Fri, 13 Sep 2024 14:40:06 +0000 (10:40 -0400)
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
lightning/src/ln/channelmanager.rs
lightning/src/offers/signer.rs

index e6aa4e340cefd2747de89c51dd0a06b1fd4877ac..805d35a010cdbc9d2f85dc3c4d223c6b8e486de5 100644 (file)
@@ -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<Sha256>,
        },
 }
 
@@ -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),
        },
 );
 
index 7e891f93dfd9d6d082f4925e673f2f9eb8664335..b2f182a6365fb9e28cbb01ff097b91e3c5e0a420 100644 (file)
@@ -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<Sha256> {
+               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<Sha256>, 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?}: {:?}",
index 152c7af5736cca0c1ff0cf2e389ab1abc055ead4..d8caa2175fe268dc4583050d2d4ff5defed4a018 100644 (file)
@@ -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<Sha256> {
-       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<Sha256> {
+       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<Sha256>, 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<Sha256> {
+       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)
+}