Skip to content

Commit a9ccd99

Browse files
committed
chore: make NUC validator time management less error prone
1 parent 0d18924 commit a9ccd99

File tree

1 file changed

+65
-25
lines changed

1 file changed

+65
-25
lines changed

libs/nucs/src/validator.rs

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ pub type ValidationResult = Result<(), ValidationError>;
2323
/// Parameters to be used during validation.
2424
#[derive(Debug)]
2525
pub struct ValidationParameters {
26-
/// The timestamp to use for token temporal checks.
27-
pub current_time: DateTime<Utc>,
28-
2926
/// The maximum allowed chain length.
3027
pub max_chain_length: usize,
3128

@@ -42,7 +39,6 @@ pub struct ValidationParameters {
4239
impl Default for ValidationParameters {
4340
fn default() -> Self {
4441
Self {
45-
current_time: Utc::now(),
4642
max_chain_length: MAX_CHAIN_LENGTH,
4743
max_policy_width: MAX_POLICY_WIDTH,
4844
max_policy_depth: MAX_POLICY_DEPTH,
@@ -65,15 +61,17 @@ pub enum TokenTypeRequirements {
6561
None,
6662
}
6763

64+
/// A NUC validator.
6865
pub struct NucValidator {
6966
root_keys: HashSet<Box<[u8]>>,
67+
time_provider: Box<dyn TimeProvider>,
7068
}
7169

7270
impl NucValidator {
7371
/// Construct a new NUC validator.
7472
pub fn new(root_keys: &[PublicKey]) -> Self {
7573
let root_keys = root_keys.iter().map(|pk| pk.to_sec1_bytes()).collect();
76-
Self { root_keys }
74+
Self { root_keys, time_provider: Box::new(SystemClockTimeProvider) }
7775
}
7876

7977
/// Validate a NUC.
@@ -97,8 +95,9 @@ impl NucValidator {
9795

9896
// Create a sequence [root, ..., token]
9997
let token_chain = iter::once(token).chain(proofs.iter().copied()).rev();
98+
let now = self.time_provider.current_time();
10099
Self::validate_proofs(token, &proofs, &self.root_keys)?;
101-
Self::validate_token_chain(token_chain, &parameters)?;
100+
Self::validate_token_chain(token_chain, &parameters, now)?;
102101
Self::validate_token(token, &proofs, &parameters.token_requirements, context)?;
103102

104103
// Signature validation is done at the end as it's arguably the most expensive part of the
@@ -171,15 +170,19 @@ impl NucValidator {
171170
}
172171

173172
// Validations applied to the entire chain (proofs + token).
174-
fn validate_token_chain<'a, I>(mut tokens: I, parameters: &ValidationParameters) -> Result<(), ValidationError>
173+
fn validate_token_chain<'a, I>(
174+
mut tokens: I,
175+
parameters: &ValidationParameters,
176+
current_time: DateTime<Utc>,
177+
) -> Result<(), ValidationError>
175178
where
176179
I: Iterator<Item = &'a NucToken> + Clone,
177180
{
178181
for (previous, current) in tokens.clone().tuple_windows() {
179182
Self::validate_relationship_properties(previous, current)?;
180183
}
181184
for token in tokens.clone() {
182-
Self::validate_temporal_properties(token, &parameters.current_time)?;
185+
Self::validate_temporal_properties(token, &current_time)?;
183186
}
184187
for token in tokens.clone() {
185188
if let TokenBody::Delegation(policies) = &token.body {
@@ -408,6 +411,18 @@ impl fmt::Display for ValidationKind {
408411
}
409412
}
410413

414+
trait TimeProvider: Send + Sync + 'static {
415+
fn current_time(&self) -> DateTime<Utc>;
416+
}
417+
418+
struct SystemClockTimeProvider;
419+
420+
impl TimeProvider for SystemClockTimeProvider {
421+
fn current_time(&self) -> DateTime<Utc> {
422+
Utc::now()
423+
}
424+
}
425+
411426
#[cfg(test)]
412427
mod tests {
413428
use super::*;
@@ -466,22 +481,46 @@ mod tests {
466481
}
467482
}
468483

484+
enum TimeConfig {
485+
System,
486+
Mock(DateTime<Utc>),
487+
}
488+
489+
struct MockTimeProvider(DateTime<Utc>);
490+
491+
impl TimeProvider for MockTimeProvider {
492+
fn current_time(&self) -> DateTime<Utc> {
493+
self.0
494+
}
495+
}
496+
469497
struct Asserter {
470498
parameters: ValidationParameters,
471499
root_keys: Vec<PublicKey>,
472500
context: HashMap<&'static str, serde_json::Value>,
501+
time_config: TimeConfig,
473502
}
474503

475504
impl Asserter {
476505
fn new(parameters: ValidationParameters) -> Self {
477-
Self { parameters, root_keys: ROOT_PUBLIC_KEYS.clone(), context: Default::default() }
506+
Self {
507+
parameters,
508+
root_keys: ROOT_PUBLIC_KEYS.clone(),
509+
context: Default::default(),
510+
time_config: TimeConfig::System,
511+
}
478512
}
479513

480514
fn with_context(mut self, context: HashMap<&'static str, serde_json::Value>) -> Self {
481515
self.context = context;
482516
self
483517
}
484518

519+
fn with_current_time(mut self, time: DateTime<Utc>) -> Self {
520+
self.time_config = TimeConfig::Mock(time);
521+
self
522+
}
523+
485524
fn log_tokens(envelope: &NucTokenEnvelope) {
486525
// Log this so we can debug tests based on their output
487526
println!("Token being asserted: {}", serde_json::to_string_pretty(envelope.token().token()).unwrap());
@@ -491,22 +530,28 @@ mod tests {
491530
);
492531
}
493532

494-
fn assert_failure<E: Into<ValidationError>>(self, envelope: NucTokenEnvelope, expected_failure: E) {
533+
fn validate(self, envelope: NucTokenEnvelope) -> Result<ValidatedNucToken, ValidationError> {
495534
Self::log_tokens(&envelope);
496535

536+
let mut validator = NucValidator::new(&self.root_keys);
537+
validator.time_provider = match self.time_config {
538+
TimeConfig::System => Box::new(SystemClockTimeProvider),
539+
TimeConfig::Mock(time) => Box::new(MockTimeProvider(time)),
540+
};
541+
validator.validate(envelope, self.parameters, &self.context)
542+
}
543+
544+
fn assert_failure<E: Into<ValidationError>>(self, envelope: NucTokenEnvelope, expected_failure: E) {
497545
let expected_failure = expected_failure.into();
498-
match NucValidator::new(&self.root_keys).validate(envelope, self.parameters, &self.context) {
546+
match self.validate(envelope) {
499547
Ok(_) => panic!("validation succeeded"),
500548
Err(e) if e.to_string() == expected_failure.to_string() => (),
501549
Err(e) => panic!("unexpected type of failure: {e}"),
502550
};
503551
}
504552

505553
fn assert_success(self, envelope: NucTokenEnvelope) -> ValidatedNucToken {
506-
Self::log_tokens(&envelope);
507-
NucValidator::new(&self.root_keys)
508-
.validate(envelope, self.parameters, &self.context)
509-
.expect("validation failed")
554+
self.validate(envelope).expect("validation failed")
510555
}
511556
}
512557

@@ -757,8 +802,7 @@ mod tests {
757802
let root = base.clone().not_before(root_not_before).issued_by_root();
758803
let last = base.not_before(last_not_before).issued_by(key);
759804
let envelope = Chainer::default().chain([root, last]);
760-
let parameters = ValidationParameters { current_time: now, ..Default::default() };
761-
Asserter::new(parameters).assert_failure(envelope, ValidationKind::NotBeforeBackwards);
805+
Asserter::default().with_current_time(now).assert_failure(envelope, ValidationKind::NotBeforeBackwards);
762806
}
763807

764808
#[test]
@@ -771,8 +815,7 @@ mod tests {
771815
let root = base.clone().not_before(not_before).issued_by_root();
772816
let last = base.issued_by(key);
773817
let envelope = Chainer::default().chain([root, last]);
774-
let parameters = ValidationParameters { current_time: now, ..Default::default() };
775-
Asserter::new(parameters).assert_failure(envelope, ValidationKind::NotBeforeNotMet);
818+
Asserter::default().with_current_time(now).assert_failure(envelope, ValidationKind::NotBeforeNotMet);
776819
}
777820

778821
#[test]
@@ -785,8 +828,7 @@ mod tests {
785828
let root = base.clone().issued_by_root();
786829
let last = base.not_before(not_before).issued_by(key);
787830
let envelope = Chainer::default().chain([root, last]);
788-
let parameters = ValidationParameters { current_time: now, ..Default::default() };
789-
Asserter::new(parameters).assert_failure(envelope, ValidationKind::NotBeforeNotMet);
831+
Asserter::default().with_current_time(now).assert_failure(envelope, ValidationKind::NotBeforeNotMet);
790832
}
791833

792834
#[test]
@@ -916,8 +958,7 @@ mod tests {
916958
let root = base.clone().expires_at(expires_at).issued_by_root();
917959
let last = base.issued_by(key);
918960
let envelope = Chainer::default().chain([root, last]);
919-
let parameters = ValidationParameters { current_time: now, ..Default::default() };
920-
Asserter::new(parameters).assert_failure(envelope, ValidationKind::TokenExpired);
961+
Asserter::default().with_current_time(now).assert_failure(envelope, ValidationKind::TokenExpired);
921962
}
922963

923964
#[test]
@@ -930,8 +971,7 @@ mod tests {
930971
let root = base.clone().issued_by_root();
931972
let last = base.expires_at(expires_at).issued_by(key);
932973
let envelope = Chainer::default().chain([root, last]);
933-
let parameters = ValidationParameters { current_time: now, ..Default::default() };
934-
Asserter::new(parameters).assert_failure(envelope, ValidationKind::TokenExpired);
974+
Asserter::default().with_current_time(now).assert_failure(envelope, ValidationKind::TokenExpired);
935975
}
936976

937977
#[test]

0 commit comments

Comments
 (0)