Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

update-bundle/tests/w13_trusted_time.rs

//! **W13's contract with everything already built against this crate.**
//!
//! The seam W13 needed was already here — `verify_member_map` has taken
//! `now: DateTime<Utc>` since the crate was written, and `verify_control_plane_at`
//! and `verify_detached_by_trusted_signer_at` were already the `_at` pattern.
//! W13 fed that seam. It was allowed to ADD, and forbidden to CHANGE: a consumer
//! that compiled against this crate before W13 must compile against it after.
//!
//! This file is that guard, and it is a COMPILE-TIME one where it can be: each
//! published function is coerced to a function pointer of its exact pre-W13
//! signature. A parameter added, removed or retyped stops this file compiling,
//! which is louder than an assertion nobody reads.

#![allow(clippy::type_complexity)] // the signatures are spelled out on purpose: the test pins them

use chrono::{DateTime, Utc};
use std::path::Path;

use holger_update_bundle::policy::TrustPolicy;
use holger_update_bundle::rollback::AcceptedState;
use holger_update_bundle::time::{HostRtc, TrustedTime, HOST_RTC_NAME};
use holger_update_bundle::verify::{TargetSpec, VerifyReport};
use holger_update_bundle::{BundleError, MemberMap};

/// The published signatures still exist, unchanged, and delegate to the named
/// host RTC rather than to a bare `Utc::now()` nobody can name.
///
/// The coercions below are the test. If `verify_member_map` ever grew a
/// `&dyn TrustedTime` parameter instead of gaining a sibling, this file would not
/// compile — which is the point: `holger-traits` is published and so is this
/// crate's shape, and W13 may not break a caller to gain a feature.
#[test]
fn the_published_signature_still_exists_and_delegates() {
    // ── The three pre-W13 public shapes, pinned as function pointers. ────────
    let _verify_member_map: fn(
        &MemberMap,
        &TrustPolicy,
        &TargetSpec,
        Option<&AcceptedState>,
        DateTime<Utc>,
    ) -> Result<VerifyReport, BundleError> = holger_update_bundle::verify_member_map;

    let _verify_bundle_file: fn(
        &Path,
        &TrustPolicy,
        &TargetSpec,
        Option<&AcceptedState>,
    ) -> Result<VerifyReport, BundleError> = holger_update_bundle::verify_bundle_file;

    let _extract_bundle_file: fn(
        &Path,
        &TrustPolicy,
        &TargetSpec,
        &Path,
    ) -> Result<VerifyReport, BundleError> = holger_update_bundle::extract_bundle_file;

    // The `_at` pattern that predates W13 is untouched too — W13 followed it
    // rather than inventing a second convention.
    let _verify_control_plane: fn(
        &MemberMap,
        &TrustPolicy,
    ) -> Result<
        holger_update_bundle::verify::ControlVerdict,
        BundleError,
    > = holger_update_bundle::verify::verify_control_plane;

    // ── And the additions, which are siblings and not replacements. ──────────
    let _verify_member_map_with_time: fn(
        &MemberMap,
        &TrustPolicy,
        &TargetSpec,
        Option<&AcceptedState>,
        &dyn TrustedTime,
    ) -> Result<VerifyReport, BundleError> =
        holger_update_bundle::verify::verify_member_map_with_time;

    let _verify_bundle_file_at: fn(
        &Path,
        &TrustPolicy,
        &TargetSpec,
        Option<&AcceptedState>,
        &dyn TrustedTime,
    ) -> Result<VerifyReport, BundleError> = holger_update_bundle::verify_bundle_file_at;

    let _extract_bundle_file_at: fn(
        &Path,
        &TrustPolicy,
        &TargetSpec,
        &Path,
        &dyn TrustedTime,
    ) -> Result<VerifyReport, BundleError> = holger_update_bundle::extract_bundle_file_at;

    // ── The delegation is to a NAMED source, and the name is recorded. ───────
    //
    // A delegation to `Utc::now()` would leave the verdict unable to say what
    // clock it rested on, which is the defect W13 exists to close. The source
    // code is the evidence: `verify_bundle_file` hands `&HostRtc` on, and
    // `HostRtc` declares itself untrusted.
    let api = include_str!("../src/api.rs");
    assert!(
        api.contains("verify_bundle_file_at(path, policy, target, local_state, &HostRtc)"),
        "verify_bundle_file must delegate to the `_at` form with the host RTC NAMED"
    );
    assert!(
        api.contains("extract_bundle_file_at(path, policy, target, dest, &HostRtc)"),
        "extract_bundle_file must delegate to the `_at` form with the host RTC NAMED"
    );
    assert!(
        !api.contains("Utc::now()"),
        "no api.rs door may still reach for the wall clock directly — that is the \
         unnamed source W13 replaced"
    );

    assert_eq!(HostRtc.source_name(), HOST_RTC_NAME);
    assert!(
        !HostRtc.is_trusted(),
        "the delegation target must declare itself UNTRUSTED, or the recorded name \
         is a reassurance instead of a warning"
    );
}

/// A caller that still hands a bare instant gets the honest answer in its own
/// verdict: the instant came from somewhere this crate cannot name.
///
/// This is what keeps the old signature from being a quiet way to launder an
/// unverified clock into a report that looks the same as a verified one.
#[test]
fn a_bare_instant_is_recorded_as_unnamed_not_as_trusted() {
    let verify = include_str!("../src/verify.rs");
    assert!(
        verify.contains("time source: unnamed"),
        "verify_member_map's bare-instant path must record `time source: unnamed`"
    );
    assert!(
        verify.contains("(UNTRUSTED — this box has no verified time"),
        "the untrusted host RTC must be recorded as UNTRUSTED, in those words"
    );
    assert!(
        verify.contains("format!(\"time source: {} (verified)\", t.source_name())"),
        "only a source that says it is trusted may be recorded as verified"
    );
}