X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Fsigner.rs;h=c5a96cbf18491e3f22eb830c879eb4d274e9abc3;hb=478911d42a8b47020511bdcb793756723f3801be;hp=e1a1a4dfd6cf0ac07ac4523bc4296c2fbe327cbf;hpb=1cad430e14108710c826adebbfab2a5ea64a6a5a;p=rust-lightning diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index e1a1a4df..c5a96cbf 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -10,19 +10,31 @@ //! Utilities for signing offer messages and verifying metadata. use bitcoin::hashes::{Hash, HashEngine}; +use bitcoin::hashes::cmp::fixed_time_eq; use bitcoin::hashes::hmac::{Hmac, HmacEngine}; use bitcoin::hashes::sha256::Hash as Sha256; -use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey, self}; -use core::convert::TryInto; +use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self}; 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)] @@ -54,9 +66,27 @@ impl Metadata { } } - pub fn derives_keys(&self) -> bool { + pub fn derives_payer_keys(&self) -> bool { match self { - Metadata::Bytes(_) => false, + // 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 + // 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 Nonce::LENGTH had + // been set explicitly. + Metadata::Bytes(bytes) => bytes.len() == Nonce::LENGTH, Metadata::Derived(_) => false, Metadata::DerivedSigningPubkey(_) => true, } @@ -89,6 +119,12 @@ impl Metadata { } } +impl Default for Metadata { + fn default() -> Self { + Metadata::Bytes(vec![]) + } +} + impl fmt::Debug for Metadata { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -119,21 +155,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 } @@ -141,10 +190,139 @@ 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) + }, + } + } +} + +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_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. +/// +/// 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 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_byte_array()).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.to_byte_array()) { + 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 Err(()); + } + + let nonce = match Nonce::try_from(&metadata[..Nonce::LENGTH]) { + Ok(nonce) => nonce, + Err(_) => return Err(()), + }; + let mut hmac = expanded_key.hmac_for_offer(nonce, iv_bytes); + + for record in tlv_stream { + hmac.input(record.record_bytes); + } + + if metadata.len() == Nonce::LENGTH { + hmac.input(DERIVED_METADATA_AND_KEYS_HMAC_INPUT); + } else { + hmac.input(DERIVED_METADATA_HMAC_INPUT); + } + + Ok(hmac) }