Skip to content

Commit

Permalink
Remove most sharing of keypairs
Browse files Browse the repository at this point in the history
And move keypair and session keypair management to the conduit itself.
  • Loading branch information
madninja committed Oct 10, 2023
1 parent 4b59c14 commit fa42bc4
Show file tree
Hide file tree
Showing 11 changed files with 264 additions and 197 deletions.
4 changes: 2 additions & 2 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub use helium_proto::{
};
pub use server::LocalServer;

use crate::{Error, Result};
use crate::{Error, PublicKey, Result};

impl TryFrom<RouterRes> for crate::packet_router::RouterStatus {
type Error = Error;
Expand All @@ -20,7 +20,7 @@ impl TryFrom<RouterRes> for crate::packet_router::RouterStatus {
Ok(Self {
uri: http::Uri::from_str(&value.uri)?,
connected: value.connected,
session_key: helium_crypto::PublicKey::try_from(value.session_key).ok(),
session_key: PublicKey::try_from(value.session_key).ok(),
})
}
}
91 changes: 22 additions & 69 deletions src/beaconer.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
//! This module provides proof-of-coverage (PoC) beaconing support.

use crate::{
error::DecodeError,
gateway::{self, BeaconResp},
keypair::mk_session_keypair,
message_cache::MessageCache,
region_watcher,
service::{entropy::EntropyService, poc::PocIotService, Reconnect},
settings::Settings,
sign, sync, Base64, Error, Keypair, PacketUp, PublicKey, RegionParams, Result,
sync, Base64, PacketUp, PublicKey, RegionParams, Result,
};
use futures::TryFutureExt;
use helium_proto::{
services::poc_lora::{self, lora_stream_request_v1, lora_stream_response_v1},
Message as ProtoMessage,
};
use helium_proto::services::poc_lora::{self, lora_stream_response_v1};
use http::Uri;
use std::sync::Arc;
use time::{Duration, Instant};
Expand Down Expand Up @@ -42,10 +37,6 @@ impl MessageSender {
pub struct Beaconer {
/// Beacon/Witness handling disabled
disabled: bool,
/// keypair to sign session init with
keypair: Arc<Keypair>,
/// session keypair to use for reports
session_key: Option<Arc<Keypair>>,
/// gateway packet transmit message queue
transmit: gateway::MessageSender,
/// Our receive queue.
Expand Down Expand Up @@ -77,14 +68,11 @@ impl Beaconer {
let interval = Duration::seconds(settings.poc.interval as i64);
let entropy_uri = settings.poc.entropy_uri.clone();
let service = PocIotService::new(settings.poc.ingest_uri.clone(), settings.keypair.clone());
let keypair = settings.keypair.clone();
let reconnect = Reconnect::default();
let region_params = Arc::new(region_watcher::current_value(&region_watch));
let disabled = settings.poc.disable;

Self {
keypair,
session_key: None,
transmit,
messages,
region_watch,
Expand All @@ -106,6 +94,7 @@ impl Beaconer {
info!(
beacon_interval = self.interval.whole_seconds(),
disabled = self.disabled,
uri = %self.service.uri,
"starting"
);

Expand Down Expand Up @@ -140,7 +129,7 @@ impl Beaconer {
if self.region_params.params.is_empty() {
// Calculate a random but deterministic time offset
// for this hotspot's beacons
let offset = mk_beacon_offset(self.keypair.public_key(), self.interval);
let offset = mk_beacon_offset(self.service.gateway_key(), self.interval);
// Get a delay for the first beacon based on the
// deterministic offset and the timestamp in the
// first region params. If there's an error
Expand Down Expand Up @@ -204,17 +193,16 @@ impl Beaconer {
.map_ok(|BeaconResp { powe, tmst }| (powe, tmst))
.await?;

// Check if a session key is available to sign the report
let Some(session_key) = self.session_key.clone() else {
warn!(%beacon_id, "no session key for beacon report");
return Err(Error::no_service());
};

Self::mk_beacon_report(beacon.clone(), powe, tmst, session_key)
.and_then(|report| self.service.submit_beacon(report))
.inspect_err(|err| warn!(beacon_id, %err, "submit poc beacon report"))
.inspect_ok(|_| info!(beacon_id, "poc beacon report submitted"))
.await?;
Self::mk_beacon_report(
beacon.clone(),
powe,
tmst,
self.service.gateway_key().clone(),
)
.and_then(|report| self.service.submit_beacon(report))
.inspect_err(|err| warn!(beacon_id, %err, "submit poc beacon report"))
.inspect_ok(|_| info!(beacon_id, "poc beacon report submitted"))
.await?;

Ok(beacon)
}
Expand All @@ -223,15 +211,7 @@ impl Beaconer {
&mut self,
message: poc_lora::LoraStreamSessionOfferV1,
) -> Result {
let session_key = mk_session_key_init(self.keypair.clone(), &message)
.and_then(|(session_key, session_init)| {
self.service.send(session_init).map_ok(|_| session_key)
})
.inspect_err(|err| warn!(%err, "failed to initialize session"))
.await?;
self.session_key = Some(session_key.clone());
info!(session_key = %session_key.public_key(),"initialized session");
Ok(())
self.service.session_init(&message.nonce).await
}

async fn handle_reconnect(&mut self) -> Result {
Expand Down Expand Up @@ -280,13 +260,7 @@ impl Beaconer {
return;
}

// Check if a session key is available to sign the report
let Some(session_key) = self.session_key.clone() else {
warn!(%beacon_id, "no session key for witness report");
return;
};

let _ = Self::mk_witness_report(packet, beacon_data, session_key)
let _ = Self::mk_witness_report(packet, beacon_data, self.service.gateway_key().clone())
.and_then(|report| self.service.submit_witness(report))
.inspect_err(|err| warn!(beacon_id, %err, "submit poc witness report"))
.inspect_ok(|_| info!(beacon_id, "poc witness report submitted"))
Expand All @@ -295,7 +269,6 @@ impl Beaconer {

fn disconnect(&mut self) {
self.service.disconnect();
self.session_key = None;
}

pub async fn mk_beacon(
Expand All @@ -316,47 +289,27 @@ impl Beaconer {
beacon: beacon::Beacon,
conducted_power: i32,
tmst: u32,
keypair: Arc<Keypair>,
gateway: PublicKey,
) -> Result<poc_lora::LoraBeaconReportReqV1> {
let mut report = poc_lora::LoraBeaconReportReqV1::try_from(beacon)?;
report.pub_key = gateway.to_vec();
report.tx_power = conducted_power;
report.tmst = tmst;
report.pub_key = keypair.public_key().to_vec();
report.signature = sign(keypair.clone(), report.encode_to_vec()).await?;
Ok(report)
}

async fn mk_witness_report(
packet: PacketUp,
payload: Vec<u8>,
keypair: Arc<Keypair>,
gateway: PublicKey,
) -> Result<poc_lora::LoraWitnessReportReqV1> {
let mut report = poc_lora::LoraWitnessReportReqV1::try_from(packet)?;
report.pub_key = gateway.to_vec();
report.data = payload;
report.pub_key = keypair.public_key().to_vec();
report.signature = sign(keypair.clone(), report.encode_to_vec()).await?;
Ok(report)
}
}

pub async fn mk_session_key_init(
keypair: Arc<Keypair>,
offer: &poc_lora::LoraStreamSessionOfferV1,
) -> Result<(Arc<Keypair>, lora_stream_request_v1::Request)> {
let session_keypair = Arc::new(mk_session_keypair());
let session_key = session_keypair.public_key();

let mut session_init = poc_lora::LoraStreamSessionInitV1 {
pub_key: keypair.public_key().into(),
session_key: session_key.into(),
nonce: offer.nonce.clone(),
signature: vec![],
};
session_init.signature = sign(keypair, session_init.encode_to_vec()).await?;
let envelope = lora_stream_request_v1::Request::SessionInit(session_init);
Ok((session_keypair, envelope))
}

/// Construct a random but deterministic offset for beaconing. This is based on
/// the public key as of this hotspot as the seed to a random number generator.
fn mk_beacon_offset(key: &PublicKey, interval: Duration) -> Duration {
Expand Down Expand Up @@ -447,14 +400,14 @@ mod test {

const PUBKEY_1: &str = "13WvV82S7QN3VMzMSieiGxvuaPKknMtf213E5JwPnboDkUfesKw";
const PUBKEY_2: &str = "14HZVR4bdF9QMowYxWrumcFBNfWnhDdD5XXA5za1fWwUhHxxFS1";
let pubkey_1 = helium_crypto::PublicKey::from_str(PUBKEY_1).expect("public key");
let pubkey_1 = crate::PublicKey::from_str(PUBKEY_1).expect("public key");
let offset_1 = mk_beacon_offset(&pubkey_1, time::Duration::hours(6));
// Same key and interval should always end up at the same offset
assert_eq!(
offset_1,
mk_beacon_offset(&pubkey_1, time::Duration::hours(6))
);
let pubkey_2 = helium_crypto::PublicKey::from_str(PUBKEY_2).expect("public key 2");
let pubkey_2 = crate::PublicKey::from_str(PUBKEY_2).expect("public key 2");
let offset_2 = mk_beacon_offset(&pubkey_2, time::Duration::hours(6));
assert_eq!(
offset_2,
Expand Down
8 changes: 4 additions & 4 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ pub enum ServiceError {
Stream,
#[error("channel closed")]
Channel,
#[error("no service")]
NoService,
#[error("no active session")]
NoSession,
#[error("age {age}s > {max_age}s")]
Check { age: u64, max_age: u64 },
#[error("Unable to connect to local server. Check that `helium_gateway` is running.")]
Expand Down Expand Up @@ -170,8 +170,8 @@ impl Error {
Error::Service(ServiceError::Channel)
}

pub fn no_service() -> Error {
Error::Service(ServiceError::NoService)
pub fn no_session() -> Error {
Error::Service(ServiceError::NoSession)
}

pub fn gateway_service_check(age: u64, max_age: u64) -> Error {
Expand Down
4 changes: 2 additions & 2 deletions src/keyed_uri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl TryFrom<helium_proto::services::local::KeyedUri> for KeyedUri {
fn try_from(v: helium_proto::services::local::KeyedUri) -> Result<Self> {
let result = Self {
uri: http::Uri::from_str(&v.uri)?,
pubkey: Arc::new(helium_crypto::PublicKey::from_bytes(v.address)?),
pubkey: Arc::new(PublicKey::from_bytes(v.address)?),
};
Ok(result)
}
Expand All @@ -63,7 +63,7 @@ impl TryFrom<helium_proto::RoutingAddress> for KeyedUri {
fn try_from(v: helium_proto::RoutingAddress) -> Result<Self> {
let result = Self {
uri: http::Uri::from_str(&String::from_utf8_lossy(&v.uri))?,
pubkey: Arc::new(helium_crypto::PublicKey::from_bytes(v.pub_key)?),
pubkey: Arc::new(PublicKey::from_bytes(v.pub_key)?),
};
Ok(result)
}
Expand Down
66 changes: 43 additions & 23 deletions src/keypair.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::*;
use crate::{error, Error, Result};
#[cfg(feature = "ecc608")]
use helium_crypto::ecc608;
#[cfg(feature = "tpm")]
Expand All @@ -10,33 +10,21 @@ use serde::{de, Deserializer};
#[cfg(feature = "ecc608")]
use std::path::Path;
use std::{collections::HashMap, convert::TryFrom, fmt, fs, io, path, str::FromStr};
use tonic::async_trait;

#[derive(Debug)]
pub struct Keypair(helium_crypto::Keypair);
pub type PublicKey = helium_crypto::PublicKey;

pub fn load_from_file(path: &str) -> error::Result<Keypair> {
let data = fs::read(path)?;
Ok(helium_crypto::Keypair::try_from(&data[..])?.into())
}

pub fn save_to_file(keypair: &Keypair, path: &str) -> io::Result<()> {
if let Some(parent) = path::PathBuf::from(path).parent() {
fs::create_dir_all(parent)?;
};
fs::write(path, keypair.0.to_vec())?;
Ok(())
#[async_trait]
pub trait Sign {
async fn sign<K>(&mut self, keypair: K) -> Result
where
K: AsRef<Keypair> + std::marker::Send + 'static;
}

pub fn mk_session_keypair() -> Keypair {
let keypair = helium_crypto::Keypair::generate(
KeyTag {
network: Network::MainNet,
key_type: KeyType::Ed25519,
},
&mut OsRng,
);
keypair.into()
pub trait Verify {
fn verify(&self, pub_key: &crate::PublicKey) -> Result;
}

macro_rules! uri_error {
Expand All @@ -61,7 +49,7 @@ impl FromStr for Keypair {
.parse()
.map_err(|err| uri_error!("invalid keypair url \"{str}\": {err:?}"))?;
match url.scheme_str() {
Some("file") | None => match load_from_file(url.path()) {
Some("file") | None => match Self::load_from_file(url.path()) {
Ok(k) => Ok(k),
Err(Error::IO(io_error)) if io_error.kind() == std::io::ErrorKind::NotFound => {
let args = KeypairArgs::from_uri(&url)?;
Expand All @@ -74,7 +62,7 @@ impl FromStr for Keypair {
&mut OsRng,
)
.into();
save_to_file(&new_key, url.path()).map_err(|err| {
new_key.save_to_file(url.path()).map_err(|err| {
uri_error!("unable to save key file \"{}\": {err:?}", url.path())
})?;
Ok(new_key)
Expand Down Expand Up @@ -137,6 +125,38 @@ impl std::ops::Deref for Keypair {
}
}

impl Keypair {
pub fn new() -> Self {
let keypair = helium_crypto::Keypair::generate(
KeyTag {
network: Network::MainNet,
key_type: KeyType::Ed25519,
},
&mut OsRng,
);
keypair.into()
}

pub fn load_from_file(path: &str) -> Result<Self> {
let data = fs::read(path)?;
Ok(helium_crypto::Keypair::try_from(&data[..])?.into())
}

pub fn save_to_file(&self, path: &str) -> io::Result<()> {
if let Some(parent) = path::PathBuf::from(path).parent() {
fs::create_dir_all(parent)?;
};
fs::write(path, self.0.to_vec())?;
Ok(())
}
}

impl Default for Keypair {
fn default() -> Self {
Self::new()
}
}

#[derive(Debug)]
struct KeypairArgs(HashMap<String, String>);

Expand Down
Loading

0 comments on commit fa42bc4

Please sign in to comment.