hs time handling: Make TimePeriod contain length as IntegerMinutes

Locally, the only functional effect is that now we refuse to handle
non-whole-number-of-minutes lengths - but since the consensus
parameter can't represent those, there's no overall functional change.
This commit is contained in:
Ian Jackson 2023-02-08 17:35:51 +00:00
parent bb2ab7c2a3
commit 4626ccfcaa
5 changed files with 28 additions and 12 deletions

1
Cargo.lock generated
View File

@ -3950,6 +3950,7 @@ dependencies = [
"thiserror", "thiserror",
"tor-basic-utils", "tor-basic-utils",
"tor-llcrypto", "tor-llcrypto",
"tor-units",
] ]
[[package]] [[package]]

View File

@ -26,6 +26,7 @@ tor-llcrypto = { version = "0.4.1", path = "../tor-llcrypto", features = [
"hsv3-client", "hsv3-client",
"hsv3-service", "hsv3-service",
] } ] }
tor-units = { path = "../tor-units", version = "0.4.1" }
[dev-dependencies] [dev-dependencies]
hex-literal = "0.3" hex-literal = "0.3"

View File

@ -142,7 +142,10 @@ impl HsIdKey {
h.update(ED25519_BASEPOINT); h.update(ED25519_BASEPOINT);
h.update(b"key-blind"); h.update(b"key-blind");
h.update(cur_period.interval_num.to_be_bytes()); h.update(cur_period.interval_num.to_be_bytes());
h.update(u64::from(cur_period.length_in_sec).to_be_bytes()); // TODO hs: shouldn't this be the period length in *minutes*, not seconds ?
// The spec just says `period_length` which elsewhere means minutes.
// https://gitlab.torproject.org/tpo/core/arti/-/issues/768
h.update((u64::from(cur_period.length.as_minutes()) * 60).to_be_bytes());
h.finalize().into() h.finalize().into()
} }

View File

@ -2,6 +2,8 @@
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use tor_units::IntegerMinutes;
/// A period of time, as used in the onion service system. /// A period of time, as used in the onion service system.
/// ///
/// A `TimePeriod` is defined as a duration (in seconds), and the number of such /// A `TimePeriod` is defined as a duration (in seconds), and the number of such
@ -25,8 +27,10 @@ use std::time::{Duration, SystemTime};
pub struct TimePeriod { pub struct TimePeriod {
/// Index of the time periods that have passed since the unix epoch. /// Index of the time periods that have passed since the unix epoch.
pub(crate) interval_num: u64, pub(crate) interval_num: u64,
/// The length of a time period, in seconds. /// The length of a time period, in **minutes**.
pub(crate) length_in_sec: u32, ///
/// The spec admits only periods which are a whole number of minutes.
pub(crate) length: IntegerMinutes<u32>,
/// Our offset from the epoch, in seconds. /// Our offset from the epoch, in seconds.
pub(crate) offset_in_sec: u32, pub(crate) offset_in_sec: u32,
} }
@ -35,7 +39,7 @@ pub struct TimePeriod {
/// same interval length and offset. /// same interval length and offset.
impl PartialOrd for TimePeriod { impl PartialOrd for TimePeriod {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
if self.length_in_sec == other.length_in_sec && self.offset_in_sec == other.offset_in_sec { if self.length == other.length && self.offset_in_sec == other.offset_in_sec {
Some(self.interval_num.cmp(&other.interval_num)) Some(self.interval_num.cmp(&other.interval_num))
} else { } else {
None None
@ -46,7 +50,8 @@ impl PartialOrd for TimePeriod {
impl TimePeriod { impl TimePeriod {
/// Construct a time period of a given `length` that contains `when`. /// Construct a time period of a given `length` that contains `when`.
/// ///
/// The `length` value is rounded down to the nearest second. /// The `length` value is rounded down to the nearest second,
/// and must then be a whole number of minutes.
/// ///
/// The `epoch_offset` value is the amount of time after the Unix epoch when /// The `epoch_offset` value is the amount of time after the Unix epoch when
/// our epoch begins. It is also rounded down to the nearest second. /// our epoch begins. It is also rounded down to the nearest second.
@ -68,6 +73,11 @@ impl TimePeriod {
pub fn new(length: Duration, when: SystemTime, epoch_offset: Duration) -> Option<Self> { pub fn new(length: Duration, when: SystemTime, epoch_offset: Duration) -> Option<Self> {
// The algorithm here is specified in rend-spec-v3 section 2.2.1 // The algorithm here is specified in rend-spec-v3 section 2.2.1
let length_in_sec = u32::try_from(length.as_secs()).ok()?; let length_in_sec = u32::try_from(length.as_secs()).ok()?;
if length_in_sec % 60 != 0 {
return None;
}
let length_in_minutes = length_in_sec / 60;
let length = IntegerMinutes::new(length_in_minutes);
let offset_in_sec = u32::try_from(epoch_offset.as_secs()).ok()?; let offset_in_sec = u32::try_from(epoch_offset.as_secs()).ok()?;
let interval_num = when let interval_num = when
.duration_since(SystemTime::UNIX_EPOCH + epoch_offset) .duration_since(SystemTime::UNIX_EPOCH + epoch_offset)
@ -76,7 +86,7 @@ impl TimePeriod {
/ u64::from(length_in_sec); / u64::from(length_in_sec);
Some(TimePeriod { Some(TimePeriod {
interval_num, interval_num,
length_in_sec, length,
offset_in_sec, offset_in_sec,
}) })
} }
@ -116,8 +126,9 @@ impl TimePeriod {
/// Return None if this time period contains any times that can be /// Return None if this time period contains any times that can be
/// represented as a `SystemTime`. /// represented as a `SystemTime`.
pub fn range(&self) -> Option<std::ops::Range<SystemTime>> { pub fn range(&self) -> Option<std::ops::Range<SystemTime>> {
let start_sec = u64::from(self.length_in_sec).checked_mul(self.interval_num)?; let length_in_sec = u64::from(self.length.as_minutes()) * 60;
let end_sec = start_sec.checked_add(self.length_in_sec.into())?; let start_sec = length_in_sec.checked_mul(self.interval_num)?;
let end_sec = start_sec.checked_add(length_in_sec)?;
let epoch_offset = Duration::new(self.offset_in_sec.into(), 0); let epoch_offset = Duration::new(self.offset_in_sec.into(), 0);
let start = let start =
(SystemTime::UNIX_EPOCH + epoch_offset).checked_add(Duration::from_secs(start_sec))?; (SystemTime::UNIX_EPOCH + epoch_offset).checked_add(Duration::from_secs(start_sec))?;
@ -138,8 +149,8 @@ impl TimePeriod {
/// ///
/// This function should only be used when encoding the time period for /// This function should only be used when encoding the time period for
/// cryptographic purposes. /// cryptographic purposes.
pub fn length_in_sec(&self) -> u64 { pub fn length(&self) -> IntegerMinutes<u32> {
self.length_in_sec.into() self.length
} }
} }

View File

@ -120,7 +120,7 @@ fn disaster_srv(period: TimePeriod) -> SharedRandVal {
use digest::Digest; use digest::Digest;
let mut d = tor_llcrypto::d::Sha3_256::new(); let mut d = tor_llcrypto::d::Sha3_256::new();
d.update(b"shared-random-disaster"); d.update(b"shared-random-disaster");
d.update((period.length_in_sec() / 60).to_be_bytes()); d.update(u64::from(period.length().as_minutes()).to_be_bytes());
d.update(period.interval_num().to_be_bytes()); d.update(period.interval_num().to_be_bytes());
let v: [u8; 32] = d.finalize().into(); let v: [u8; 32] = d.finalize().into();
@ -406,7 +406,7 @@ mod test {
use digest::Digest; use digest::Digest;
use tor_llcrypto::d::Sha3_256; use tor_llcrypto::d::Sha3_256;
let period = TimePeriod::new(d("1 day"), t("1970-01-02T17:33:00Z"), d("12 hours")).unwrap(); let period = TimePeriod::new(d("1 day"), t("1970-01-02T17:33:00Z"), d("12 hours")).unwrap();
assert_eq!(period.length_in_sec(), 86400); assert_eq!(period.length().as_minutes(), 86400 / 60);
assert_eq!(period.interval_num(), 1); assert_eq!(period.interval_num(), 1);
let dsrv = disaster_srv(period); let dsrv = disaster_srv(period);