1
1
//! Traits for wrapping up signed and/or time-bound objects
2
//!
3
//! # Overview
4
//!
5
//! Frequently (for testing reasons or otherwise), we want to ensure
6
//! that an object can only be used if a signature is valid, or if
7
//! some timestamp is recent enough.
8
//!
9
//! As an example, consider a self-signed certificate. You can parse
10
//! it cheaply enough (and find its key by doing so), but you probably
11
//! want to make sure that nobody will use that certificate unless its
12
//! signature is correct and its timestamps are not expired.
13
//!
14
//! With the tor-checkable crate, you can instead return an object
15
//! that represents the certificate in its unchecked state.  The
16
//! caller can access the certificate, but only after checking the
17
//! signature and the time.
18
//!
19
//! This crate is part of
20
//! [Arti](https://gitlab.torproject.org/tpo/core/arti/), a project to
21
//! implement [Tor](https://www.torproject.org/) in Rust.
22
//! Many other crates in Arti depend on it, but it should be generally
23
//! useful outside of Arti.
24
//!
25
//! ## Design notes and alternatives
26
//!
27
//! The types in this crate provide functions to return the underlying
28
//! objects without checking them.  This is very convenient for testing,
29
//! though you wouldn't want to do it in production code.  To prevent
30
//! mistakes, these functions all begin with the word `dangerously`.
31
//!
32
//! Another approach you might take is to put signature and timeliness
33
//! checks inside your parsing function.  But if you do that, it will
34
//! get hard to test your code: you will only be able to parse
35
//! certificates that are valid when the parser is running.  And if
36
//! you want to test parsing a new kind of certificate, you'll need to
37
//! make sure to put a valid signature on it.  (And all of this
38
//! signature parsing will slow down any attempts to fuzz your
39
//! parser.)
40
//!
41
//! You could have your parser take a flag to tell it whether to check
42
//! signatures and timeliness, but that could be error prone: if anybody
43
//! sets the flag wrong, they will skip doing the checks.
44

            
45
#![deny(missing_docs)]
46
#![warn(noop_method_call)]
47
#![deny(unreachable_pub)]
48
#![warn(clippy::all)]
49
#![deny(clippy::await_holding_lock)]
50
#![deny(clippy::cargo_common_metadata)]
51
#![deny(clippy::cast_lossless)]
52
#![deny(clippy::checked_conversions)]
53
#![warn(clippy::cognitive_complexity)]
54
#![deny(clippy::debug_assert_with_mut_call)]
55
#![deny(clippy::exhaustive_enums)]
56
#![deny(clippy::exhaustive_structs)]
57
#![deny(clippy::expl_impl_clone_on_copy)]
58
#![deny(clippy::fallible_impl_from)]
59
#![deny(clippy::implicit_clone)]
60
#![deny(clippy::large_stack_arrays)]
61
#![warn(clippy::manual_ok_or)]
62
#![deny(clippy::missing_docs_in_private_items)]
63
#![deny(clippy::missing_panics_doc)]
64
#![warn(clippy::needless_borrow)]
65
#![warn(clippy::needless_pass_by_value)]
66
#![warn(clippy::option_option)]
67
#![warn(clippy::rc_buffer)]
68
#![deny(clippy::ref_option_ref)]
69
#![warn(clippy::semicolon_if_nothing_returned)]
70
#![warn(clippy::trait_duplication_in_bounds)]
71
#![deny(clippy::unnecessary_wraps)]
72
#![warn(clippy::unseparated_literal_suffix)]
73
#![deny(clippy::unwrap_used)]
74

            
75
use std::time;
76
use thiserror::Error;
77

            
78
pub mod signed;
79
pub mod timed;
80

            
81
/// An error that can occur when checking whether a Timebound object is
82
/// currently valid.
83
4
#[derive(Debug, Clone, Error, PartialEq, Eq)]
84
#[non_exhaustive]
85
pub enum TimeValidityError {
86
    /// The object is not yet valid
87
    #[error("will not be valid for {0:?}")]
88
    NotYetValid(time::Duration),
89
    /// The object is expired
90
    #[error("has been expired for {0:?}")]
91
    Expired(time::Duration),
92
    /// The object isn't timely, and we don't know why, or won't say.
93
    #[error("is not currently valid")]
94
    Unspecified,
95
}
96

            
97
/// A Timebound object is one that is only valid for a given range of time.
98
///
99
/// It's better to wrap things in a TimeBound than to give them an is_valid()
100
/// valid method, so that you can make sure that nobody uses the object before
101
/// checking it.
102
pub trait Timebound<T>: Sized {
103
    /// An error type that's returned when the object is _not_ timely.
104
    type Error;
105

            
106
    /// Check whether this object is valid at a given time.
107
    ///
108
    /// Return Ok if the object is valid, and an error if the object is not.
109
    fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error>;
110

            
111
    /// Return the underlying object without checking whether it's valid.
112
    fn dangerously_assume_timely(self) -> T;
113

            
114
    /// Unwrap this Timebound object if it is valid at a given time.
115
    fn check_valid_at(self, t: &time::SystemTime) -> Result<T, Self::Error> {
116
36
        self.is_valid_at(t)?;
117
34
        Ok(self.dangerously_assume_timely())
118
36
    }
119

            
120
    /// Unwrap this Timebound object if it is valid now.
121
2
    fn check_valid_now(self) -> Result<T, Self::Error> {
122
2
        self.check_valid_at(&time::SystemTime::now())
123
2
    }
124

            
125
    /// Unwrap this object if it is valid at the provided time t.
126
    /// If no time is provided, check the object at the current time.
127
21
    fn check_valid_at_opt(self, t: Option<time::SystemTime>) -> Result<T, Self::Error> {
128
21
        match t {
129
22
            Some(when) => self.check_valid_at(&when),
130
            None => self.check_valid_now(),
131
        }
132
21
    }
133
}
134

            
135
/// A cryptographically signed object that can be validated without
136
/// additional public keys.
137
///
138
/// It's better to wrap things in a SelfSigned than to give them an is_valid()
139
/// method, so that you can make sure that nobody uses the object before
140
/// checking it.  It's better to wrap things in a SelfSigned than to check
141
/// them immediately, since you might want to defer the signature checking
142
/// operation to another thread.
143
pub trait SelfSigned<T>: Sized {
144
    /// An error type that's returned when the object is _not_ well-signed.
145
    type Error;
146
    /// Check the signature on this object
147
    fn is_well_signed(&self) -> Result<(), Self::Error>;
148
    /// Return the underlying object without checking its signature.
149
    fn dangerously_assume_wellsigned(self) -> T;
150

            
151
    /// Unwrap this object if the signature is valid
152
    fn check_signature(self) -> Result<T, Self::Error> {
153
18
        self.is_well_signed()?;
154
16
        Ok(self.dangerously_assume_wellsigned())
155
18
    }
156
}
157

            
158
/// A cryptographically signed object that needs an external public
159
/// key to validate it.
160
pub trait ExternallySigned<T>: Sized {
161
    /// The type of the public key object.
162
    ///
163
    /// You can use a tuple or a vector here if the object is signed
164
    /// with multiple keys.
165
    type Key: ?Sized;
166

            
167
    /// A type that describes what keys are missing for this object.
168
    type KeyHint;
169

            
170
    /// An error type that's returned when the object is _not_ well-signed.
171
    type Error;
172

            
173
    /// Check whether k is the right key for this object.  If not, return
174
    /// an error describing what key would be right.
175
    ///
176
    /// This function is allowed to return 'true' for a bad key, but never
177
    /// 'false' for a good key.
178
    fn key_is_correct(&self, k: &Self::Key) -> Result<(), Self::KeyHint>;
179

            
180
    /// Check the signature on this object
181
    fn is_well_signed(&self, k: &Self::Key) -> Result<(), Self::Error>;
182

            
183
    /// Unwrap this object without checking any signatures on it.
184
    fn dangerously_assume_wellsigned(self) -> T;
185

            
186
    /// Unwrap this object if it's correctly signed by a provided key.
187
    fn check_signature(self, k: &Self::Key) -> Result<T, Self::Error> {
188
9
        self.is_well_signed(k)?;
189
8
        Ok(self.dangerously_assume_wellsigned())
190
9
    }
191
}