Include PaymentId in payer metadata
[rust-lightning] / lightning / src / offers / signer.rs
index 8d5f98e6f6b050993474bbedbcc9a0f25c409980..4d5d4662bd62b806cb78543e41653c266a02146a 100644 (file)
@@ -16,15 +16,26 @@ use bitcoin::hashes::sha256::Hash as Sha256;
 use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self};
 use core::convert::TryFrom;
 use core::fmt;
+use crate::ln::channelmanager::PaymentId;
 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
 use crate::offers::merkle::TlvRecord;
 use crate::util::ser::Writeable;
 
 use crate::prelude::*;
 
+// Use a different HMAC input for each derivation. Otherwise, an attacker could:
+// - take an Offer that has metadata consisting of a nonce and HMAC
+// - strip off the HMAC and replace the signing_pubkey where the privkey is the HMAC,
+// - generate and sign an invoice using the new signing_pubkey, and
+// - claim they paid it since they would know the preimage of the invoice's payment_hash
 const DERIVED_METADATA_HMAC_INPUT: &[u8; 16] = &[1; 16];
 const DERIVED_METADATA_AND_KEYS_HMAC_INPUT: &[u8; 16] = &[2; 16];
 
+// Additional HMAC inputs to distinguish use cases, either Offer or Refund/InvoiceRequest, where
+// metadata for the latter contain an encrypted PaymentId.
+const WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[3; 16];
+const WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[4; 16];
+
 /// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be
 /// verified.
 #[derive(Clone)]
@@ -56,7 +67,20 @@ impl Metadata {
                }
        }
 
-       pub fn derives_keys(&self) -> bool {
+       pub fn derives_payer_keys(&self) -> bool {
+               match self {
+                       // Infer whether Metadata::derived_from was called on Metadata::DerivedSigningPubkey to
+                       // produce Metadata::Bytes. This is merely to determine which fields should be included
+                       // when verifying a message. It doesn't necessarily indicate that keys were in fact
+                       // derived, as wouldn't be the case if a Metadata::Bytes with length PaymentId::LENGTH +
+                       // Nonce::LENGTH had been set explicitly.
+                       Metadata::Bytes(bytes) => bytes.len() == PaymentId::LENGTH + Nonce::LENGTH,
+                       Metadata::Derived(_) => false,
+                       Metadata::DerivedSigningPubkey(_) => true,
+               }
+       }
+
+       pub fn derives_recipient_keys(&self) -> bool {
                match self {
                        // Infer whether Metadata::derived_from was called on Metadata::DerivedSigningPubkey to
                        // produce Metadata::Bytes. This is merely to determine which fields should be included
@@ -132,20 +156,33 @@ impl PartialEq for Metadata {
 pub(super) struct MetadataMaterial {
        nonce: Nonce,
        hmac: HmacEngine<Sha256>,
+       // Some for payer metadata and None for offer metadata
+       encrypted_payment_id: Option<[u8; PaymentId::LENGTH]>,
 }
 
 impl MetadataMaterial {
-       pub fn new(nonce: Nonce, expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN]) -> Self {
+       pub fn new(
+               nonce: Nonce, expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
+               payment_id: Option<PaymentId>
+       ) -> Self {
+               // Encrypt payment_id
+               let encrypted_payment_id = payment_id.map(|payment_id| {
+                       expanded_key.crypt_for_offer(payment_id.0, nonce)
+               });
+
                Self {
                        nonce,
                        hmac: expanded_key.hmac_for_offer(nonce, iv_bytes),
+                       encrypted_payment_id,
                }
        }
 
        fn derive_metadata(mut self) -> Vec<u8> {
                self.hmac.input(DERIVED_METADATA_HMAC_INPUT);
+               self.maybe_include_encrypted_payment_id();
 
-               let mut bytes = self.nonce.as_slice().to_vec();
+               let mut bytes = self.encrypted_payment_id.map(|id| id.to_vec()).unwrap_or(vec![]);
+               bytes.extend_from_slice(self.nonce.as_slice());
                bytes.extend_from_slice(&Hmac::from_engine(self.hmac).into_inner());
                bytes
        }
@@ -154,11 +191,26 @@ impl MetadataMaterial {
                mut self, secp_ctx: &Secp256k1<T>
        ) -> (Vec<u8>, KeyPair) {
                self.hmac.input(DERIVED_METADATA_AND_KEYS_HMAC_INPUT);
+               self.maybe_include_encrypted_payment_id();
+
+               let mut bytes = self.encrypted_payment_id.map(|id| id.to_vec()).unwrap_or(vec![]);
+               bytes.extend_from_slice(self.nonce.as_slice());
 
                let hmac = Hmac::from_engine(self.hmac);
                let privkey = SecretKey::from_slice(hmac.as_inner()).unwrap();
                let keys = KeyPair::from_secret_key(secp_ctx, &privkey);
-               (self.nonce.as_slice().to_vec(), keys)
+
+               (bytes, keys)
+       }
+
+       fn maybe_include_encrypted_payment_id(&mut self) {
+               match self.encrypted_payment_id {
+                       None => self.hmac.input(WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT),
+                       Some(encrypted_payment_id) => {
+                               self.hmac.input(WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT);
+                               self.hmac.input(&encrypted_payment_id)
+                       },
+               }
        }
 }
 
@@ -170,19 +222,65 @@ pub(super) fn derive_keys(nonce: Nonce, expanded_key: &ExpandedKey) -> KeyPair {
        KeyPair::from_secret_key(&secp_ctx, &privkey)
 }
 
+/// Verifies data given in a TLV stream was used to produce the given metadata, consisting of:
+/// - a 256-bit [`PaymentId`],
+/// - a 128-bit [`Nonce`], and possibly
+/// - a [`Sha256`] hash of the nonce and the TLV records using the [`ExpandedKey`].
+///
+/// If the latter is not included in the metadata, the TLV stream is used to check if the given
+/// `signing_pubkey` can be derived from it.
+///
+/// Returns the [`PaymentId`] that should be used for sending the payment.
+pub(super) fn verify_payer_metadata<'a, T: secp256k1::Signing>(
+       metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
+       signing_pubkey: PublicKey, tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>,
+       secp_ctx: &Secp256k1<T>
+) -> Result<PaymentId, ()> {
+       if metadata.len() < PaymentId::LENGTH {
+               return Err(());
+       }
+
+       let mut encrypted_payment_id = [0u8; PaymentId::LENGTH];
+       encrypted_payment_id.copy_from_slice(&metadata[..PaymentId::LENGTH]);
+
+       let mut hmac = hmac_for_message(
+               &metadata[PaymentId::LENGTH..], expanded_key, iv_bytes, tlv_stream
+       )?;
+       hmac.input(WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT);
+       hmac.input(&encrypted_payment_id);
+
+       verify_metadata(
+               &metadata[PaymentId::LENGTH..], Hmac::from_engine(hmac), signing_pubkey, secp_ctx
+       )?;
+
+       let nonce = Nonce::try_from(&metadata[PaymentId::LENGTH..][..Nonce::LENGTH]).unwrap();
+       let payment_id = expanded_key.crypt_for_offer(encrypted_payment_id, nonce);
+
+       Ok(PaymentId(payment_id))
+}
+
 /// Verifies data given in a TLV stream was used to produce the given metadata, consisting of:
 /// - a 128-bit [`Nonce`] and possibly
 /// - a [`Sha256`] hash of the nonce and the TLV records using the [`ExpandedKey`].
 ///
 /// If the latter is not included in the metadata, the TLV stream is used to check if the given
 /// `signing_pubkey` can be derived from it.
-pub(super) fn verify_metadata<'a, T: secp256k1::Signing>(
+///
+/// Returns the [`KeyPair`] for signing the invoice, if it can be derived from the metadata.
+pub(super) fn verify_recipient_metadata<'a, T: secp256k1::Signing>(
        metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
        signing_pubkey: PublicKey, tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>,
        secp_ctx: &Secp256k1<T>
 ) -> Result<Option<KeyPair>, ()> {
-       let hmac = hmac_for_message(metadata, expanded_key, iv_bytes, tlv_stream)?;
+       let mut hmac = hmac_for_message(metadata, expanded_key, iv_bytes, tlv_stream)?;
+       hmac.input(WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT);
+
+       verify_metadata(metadata, Hmac::from_engine(hmac), signing_pubkey, secp_ctx)
+}
 
+fn verify_metadata<T: secp256k1::Signing>(
+       metadata: &[u8], hmac: Hmac<Sha256>, signing_pubkey: PublicKey, secp_ctx: &Secp256k1<T>
+) -> Result<Option<KeyPair>, ()> {
        if metadata.len() == Nonce::LENGTH {
                let derived_keys = KeyPair::from_secret_key(
                        secp_ctx, &SecretKey::from_slice(hmac.as_inner()).unwrap()
@@ -206,7 +304,7 @@ pub(super) fn verify_metadata<'a, T: secp256k1::Signing>(
 fn hmac_for_message<'a>(
        metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
        tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>
-) -> Result<Hmac<Sha256>, ()> {
+) -> Result<HmacEngine<Sha256>, ()> {
        if metadata.len() < Nonce::LENGTH {
                return Err(());
        }
@@ -227,5 +325,5 @@ fn hmac_for_message<'a>(
                hmac.input(DERIVED_METADATA_HMAC_INPUT);
        }
 
-       Ok(Hmac::from_engine(hmac))
+       Ok(hmac)
 }