X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Fsigner.rs;h=4d5d4662bd62b806cb78543e41653c266a02146a;hb=e9d9711de4ddc20b78eb110abfe400da6eef863d;hp=f6141e59699addb391ff6bfb523f75767e81aa7f;hpb=022eadc4dbf0f60179674f936d604cade6c5dd9e;p=rust-lightning diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index f6141e59..4d5d4662 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -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, + // 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 + ) -> 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 { 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,32 +191,127 @@ impl MetadataMaterial { mut self, secp_ctx: &Secp256k1 ) -> (Vec, 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) + }, + } } } +pub(super) fn derive_keys(nonce: Nonce, expanded_key: &ExpandedKey) -> KeyPair { + const IV_BYTES: &[u8; IV_LEN] = b"LDK Invoice ~~~~"; + let secp_ctx = Secp256k1::new(); + let hmac = Hmac::from_engine(expanded_key.hmac_for_offer(nonce, IV_BYTES)); + let privkey = SecretKey::from_slice(hmac.as_inner()).unwrap(); + 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>, + secp_ctx: &Secp256k1 +) -> Result { + 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>, secp_ctx: &Secp256k1 -) -> bool { +) -> Result, ()> { + 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( + metadata: &[u8], hmac: Hmac, signing_pubkey: PublicKey, secp_ctx: &Secp256k1 +) -> Result, ()> { + if metadata.len() == Nonce::LENGTH { + let derived_keys = KeyPair::from_secret_key( + secp_ctx, &SecretKey::from_slice(hmac.as_inner()).unwrap() + ); + if fixed_time_eq(&signing_pubkey.serialize(), &derived_keys.public_key().serialize()) { + Ok(Some(derived_keys)) + } else { + Err(()) + } + } else if metadata[Nonce::LENGTH..].len() == Sha256::LEN { + if fixed_time_eq(&metadata[Nonce::LENGTH..], &hmac.into_inner()) { + Ok(None) + } else { + Err(()) + } + } else { + Err(()) + } +} + +fn hmac_for_message<'a>( + metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN], + tlv_stream: impl core::iter::Iterator> +) -> Result, ()> { if metadata.len() < Nonce::LENGTH { - return false; + return Err(()); } let nonce = match Nonce::try_from(&metadata[..Nonce::LENGTH]) { Ok(nonce) => nonce, - Err(_) => return false, + Err(_) => return Err(()), }; let mut hmac = expanded_key.hmac_for_offer(nonce, iv_bytes); @@ -189,13 +321,9 @@ pub(super) fn verify_metadata<'a, T: secp256k1::Signing>( if metadata.len() == Nonce::LENGTH { hmac.input(DERIVED_METADATA_AND_KEYS_HMAC_INPUT); - let hmac = Hmac::from_engine(hmac); - let derived_pubkey = SecretKey::from_slice(hmac.as_inner()).unwrap().public_key(&secp_ctx); - fixed_time_eq(&signing_pubkey.serialize(), &derived_pubkey.serialize()) - } else if metadata[Nonce::LENGTH..].len() == Sha256::LEN { - hmac.input(DERIVED_METADATA_HMAC_INPUT); - fixed_time_eq(&metadata[Nonce::LENGTH..], &Hmac::from_engine(hmac).into_inner()) } else { - false + hmac.input(DERIVED_METADATA_HMAC_INPUT); } + + Ok(hmac) }