Skip to content

Commit c4eefe5

Browse files
Merge #105
105: chore: make NUC validator time management less error prone r=mfontanini a=mfontanini The NUC validator's parameters included the current time. This is error prone because it requires the parameters to be created right before the validator is created. This is not too bad as it can be in other languages because the parameters are taken by value and they're not `Clone` so you can't reuse the same set of parameters with a fixed timestamp forever but still, it opens up the possibility for bugs to sneak in. Co-authored-by: Matias Fontanini <[email protected]>
2 parents 6938912 + a9ccd99 commit c4eefe5

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)