diff --git a/denylist/src/denylist.rs b/denylist/src/denylist.rs index 35c898151..3ab2c9c43 100644 --- a/denylist/src/denylist.rs +++ b/denylist/src/denylist.rs @@ -1,16 +1,12 @@ -use crate::{client::DenyListClient, models::metadata::Asset, Error, Result}; +use crate::{client::DenyListClient, models::metadata::Asset, Error, Result, Settings}; use bytes::Buf; use helium_crypto::{PublicKey, PublicKeyBinary, Verify}; use serde::Serialize; -use std::{fs, hash::Hasher, path, str::FromStr}; +use std::{fs, hash::Hasher, path}; use twox_hash::XxHash64; use xorf::{Filter as XorFilter, Xor32}; pub const SERIAL_SIZE: usize = 32; - -/// the pubkey used to verify the signature of denylist updates -// TODO: is there a better home for this key ? -const PUB_KEY_B58: &str = "1SbEYKju337P6aYsRd9DT2k4qgK5ZK62kXbSvnJgqeaxK3hqQrYURZjL"; /// a copy of the last saved filter bin downloaded from github /// if present will be used to initialise the denylist upon verifier startup // TODO: look at using the tempfile crate to handle this @@ -23,6 +19,7 @@ pub struct DenyList { pub client: DenyListClient, #[serde(skip_serializing)] pub filter: Xor32, + pub sign_keys: Vec, } impl TryFrom> for DenyList { @@ -35,12 +32,13 @@ impl TryFrom> for DenyList { tag_name: 0, client, filter, + sign_keys: vec![], }) } } impl DenyList { - pub fn new() -> Result { + pub fn new(settings: &Settings) -> Result { tracing::debug!("initializing new denylist"); // if exists default to the local saved filter bin, // otherwise default to empty filter @@ -54,7 +52,8 @@ impl DenyList { ); Vec::new() }); - let filter = filter_from_bin(&bin).unwrap_or_else(|_| Xor32::from(Vec::new())); + let sign_keys = settings.sign_keys()?; + let filter = filter_from_bin(&bin, &sign_keys).unwrap_or_else(|_| Xor32::from(Vec::new())); let client = DenyListClient::new()?; Ok(Self { // default tag to 0, proper tag name will be set on first @@ -62,6 +61,7 @@ impl DenyList { tag_name: 0, client, filter, + sign_keys, }) } @@ -93,7 +93,7 @@ impl DenyList { tracing::debug!("found asset for tag"); let asset_url = &asset.browser_download_url; let bin = self.client.get_bin(asset_url).await?; - if let Ok(filter) = filter_from_bin(&bin) { + if let Ok(filter) = filter_from_bin(&bin, &self.sign_keys) { self.filter = filter; self.tag_name = new_tag_name; save_local_filter_bin(&bin, FILTER_BIN_PATH)?; @@ -113,7 +113,7 @@ impl DenyList { } /// deconstruct bytes into the filter component parts -pub fn filter_from_bin(bin: &Vec) -> Result { +pub fn filter_from_bin(bin: &Vec, sign_keys: &[PublicKey]) -> Result { if bin.is_empty() { return Err(Error::InvalidBinary("invalid filter bin".to_string())); } @@ -122,21 +122,26 @@ pub fn filter_from_bin(bin: &Vec) -> Result { let _version = buf.get_u8(); let signature_len = buf.get_u16_le() as usize; let signature = buf.copy_to_bytes(signature_len).to_vec(); - let pubkey = PublicKey::from_str(PUB_KEY_B58)?; - match pubkey.verify(buf, &signature) { - Ok(_) => { - tracing::info!("updating filter to latest"); - let _serial = buf.get_u32_le(); - let xor = bincode::deserialize::(buf)?; - Ok(xor) - } - Err(_) => { + sign_keys + .iter() + .any(|pubkey| { + pubkey + .verify(buf, &signature) + .map(|res| { + tracing::info!(%pubkey, "valid denylist signer"); + res + }) + .is_ok() + }) + .then(|| { + buf.advance(4); + bincode::deserialize::(buf) + }) + .transpose()? + .ok_or_else(|| { tracing::warn!("filter signature verification failed"); - Err(Error::InvalidBinary( - "filter signature verification failed".to_string(), - )) - } - } + Error::InvalidBinary("filter signature verification failed".to_string()) + }) } fn public_key_hash>(public_key: R) -> u64 { diff --git a/denylist/src/settings.rs b/denylist/src/settings.rs index 844a7b4b7..670797fff 100644 --- a/denylist/src/settings.rs +++ b/denylist/src/settings.rs @@ -1,7 +1,8 @@ use crate::{Error, Result}; use config::{Config, Environment, File}; +use helium_crypto::PublicKey; use serde::Deserialize; -use std::{path::Path, time::Duration}; +use std::{path::Path, str::FromStr, time::Duration}; #[derive(Debug, Deserialize, Clone)] pub struct Settings { @@ -15,6 +16,10 @@ pub struct Settings { /// Cadence at which we poll for an updated denylist (secs) #[serde(default = "default_trigger_interval")] pub trigger: u64, + // vec of b58 helium encoded pubkeys + // used to verify signature of denylist filters + #[serde(default)] + pub sign_keys: Vec, } pub fn default_log() -> String { @@ -56,4 +61,11 @@ impl Settings { pub fn trigger_interval(&self) -> Duration { Duration::from_secs(self.trigger) } + + pub fn sign_keys(&self) -> std::result::Result, helium_crypto::Error> { + self.sign_keys + .iter() + .map(|pubkey| PublicKey::from_str(pubkey)) + .collect() + } } diff --git a/iot_verifier/src/runner.rs b/iot_verifier/src/runner.rs index 5526bb260..375eaf674 100644 --- a/iot_verifier/src/runner.rs +++ b/iot_verifier/src/runner.rs @@ -73,7 +73,7 @@ impl Runner { let beacon_max_retries = settings.beacon_max_retries; let witness_max_retries = settings.witness_max_retries; let deny_list_latest_url = settings.denylist.denylist_url.clone(); - let mut deny_list = DenyList::new()?; + let mut deny_list = DenyList::new(&settings.denylist)?; // force update to latest in order to update the tag name // when first run, the denylist will load the local filter // but we dont save the tag name so it defaults to 0