X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Fsigner.rs;h=e0abd82767f6c9fd51dc0a7e425f7236631a0f67;hb=ec928d55b480254f2ce3457a5c219ed115fdf9ef;hp=8d5f98e6f6b050993474bbedbcc9a0f25c409980;hpb=b8ed4d2608e32128dd5a1dee92911638a4301138;p=rust-lightning diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index 8d5f98e6..e0abd827 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,21 +156,34 @@ 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(); - bytes.extend_from_slice(&Hmac::from_engine(self.hmac).into_inner()); + 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).as_byte_array()); bytes } @@ -154,11 +191,26 @@ 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 privkey = SecretKey::from_slice(hmac.as_byte_array()).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) + }, + } } } @@ -166,26 +218,72 @@ 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(); + let privkey = SecretKey::from_slice(hmac.as_byte_array()).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 ) -> Result, ()> { - 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( + 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() + secp_ctx, &SecretKey::from_slice(hmac.as_byte_array()).unwrap() ); if fixed_time_eq(&signing_pubkey.serialize(), &derived_keys.public_key().serialize()) { Ok(Some(derived_keys)) @@ -193,7 +291,7 @@ pub(super) fn verify_metadata<'a, T: secp256k1::Signing>( Err(()) } } else if metadata[Nonce::LENGTH..].len() == Sha256::LEN { - if fixed_time_eq(&metadata[Nonce::LENGTH..], &hmac.into_inner()) { + if fixed_time_eq(&metadata[Nonce::LENGTH..], &hmac.to_byte_array()) { Ok(None) } else { Err(()) @@ -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> -) -> Result, ()> { +) -> Result, ()> { 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) }