Generalize typestates, make verified valid because its more correct for Identity.

This commit is contained in:
Adam Ierymenko 2023-01-03 15:24:46 -05:00
parent 7dabeb9595
commit 382688d251
15 changed files with 76 additions and 80 deletions

View file

@ -35,7 +35,7 @@ pub struct Controller {
reaper: Reaper,
runtime: tokio::runtime::Handle,
database: Arc<dyn Database>,
local_identity: Verified<Identity>,
local_identity: Valid<Identity>,
/// Handler for MULTICAST_LIKE and MULTICAST_GATHER messages.
multicast_authority: MulticastAuthority,
@ -256,7 +256,7 @@ impl Controller {
/// reason is returned with None or an acceptance reason with a network configuration is returned.
async fn authorize(
self: &Arc<Self>,
source_identity: &Verified<Identity>,
source_identity: &Valid<Identity>,
network_id: NetworkId,
time_clock: i64,
) -> Result<(AuthenticationResult, Option<NetworkConfig>), Box<dyn Error + Send + Sync>> {
@ -499,14 +499,14 @@ impl Controller {
impl InnerProtocolLayer for Controller {
#[inline(always)]
fn should_respond_to(&self, _: &Verified<Identity>) -> bool {
fn should_respond_to(&self, _: &Valid<Identity>) -> bool {
// Controllers always have to establish sessions to process requests. We don't really know if
// a member is relevant until we have looked up both the network and the member, since whether
// or not to "learn" unknown members is a network level option.
true
}
fn has_trust_relationship(&self, id: &Verified<Identity>) -> bool {
fn has_trust_relationship(&self, id: &Valid<Identity>) -> bool {
self.recently_authorized
.read()
.unwrap()

View file

@ -5,7 +5,7 @@ use async_trait::async_trait;
use notify::{RecursiveMode, Watcher};
use serde::de::DeserializeOwned;
use zerotier_network_hypervisor::vl1::{Address, Identity, Verified};
use zerotier_network_hypervisor::vl1::{Address, Identity, Valid};
use zerotier_network_hypervisor::vl2::NetworkId;
use zerotier_utils::reaper::Reaper;
use zerotier_utils::tokio::fs;
@ -32,7 +32,7 @@ const EVENT_HANDLER_TASK_TIMEOUT: Duration = Duration::from_secs(10);
/// is different from V1 so it'll need a converter to use with V1 FileDb controller data.
pub struct FileDatabase {
base_path: PathBuf,
local_identity: Verified<Identity>,
local_identity: Valid<Identity>,
change_sender: Sender<Change>,
tasks: Reaper,
cache: Cache,
@ -251,11 +251,11 @@ impl Drop for FileDatabase {
}
impl VL1DataStorage for FileDatabase {
fn load_node_identity(&self) -> Option<Verified<Identity>> {
fn load_node_identity(&self) -> Option<Valid<Identity>> {
load_node_identity(self.base_path.as_path())
}
fn save_node_identity(&self, id: &Verified<Identity>) -> bool {
fn save_node_identity(&self, id: &Valid<Identity>) -> bool {
save_node_identity(self.base_path.as_path(), id)
}
}

View file

@ -10,7 +10,7 @@ use tokio_postgres::types::Type;
use tokio_postgres::{Client, Statement};
use zerotier_crypto::secure_eq;
use zerotier_crypto::verified::Verified;
use zerotier_crypto::typestate::Valid;
use zerotier_network_hypervisor::vl1::{Address, Identity, InetAddress};
use zerotier_network_hypervisor::vl2::networkconfig::IpRoute;
@ -137,7 +137,7 @@ impl<'a> Drop for ConnectionHolder<'a> {
pub struct PostgresDatabase {
local_controller_id_str: String,
local_identity: Verified<Identity>,
local_identity: Valid<Identity>,
connections: Mutex<(Vec<Box<PostgresConnection>>, Sender<()>)>,
postgres_path: String,
runtime: Handle,
@ -148,7 +148,7 @@ impl PostgresDatabase {
runtime: Handle,
postgres_path: String,
num_connections: usize,
local_identity: Verified<Identity>,
local_identity: Valid<Identity>,
) -> Result<Arc<Self>, Error> {
assert!(num_connections > 0);
let (sender, _) = channel(4096);
@ -189,11 +189,11 @@ impl PostgresDatabase {
}
impl VL1DataStorage for PostgresDatabase {
fn load_node_identity(&self) -> Option<Verified<Identity>> {
fn load_node_identity(&self) -> Option<Valid<Identity>> {
Some(self.local_identity.clone())
}
fn save_node_identity(&self, id: &Verified<Identity>) -> bool {
fn save_node_identity(&self, id: &Valid<Identity>) -> bool {
panic!("local identity saving not supported by PostgresDatabase")
}
}

View file

@ -8,7 +8,7 @@ pub mod poly1305;
pub mod random;
pub mod salsa;
pub mod secret;
pub mod verified;
pub mod typestate;
pub mod x25519;
pub const ZEROES: [u8; 64] = [0_u8; 64];

View file

@ -4,31 +4,25 @@ use std::fmt::Debug;
use std::hash::Hash;
use std::ops::{Deref, DerefMut};
/// A zero-overhead typestate indicating that a credential has been verified as valid.
///
/// What this means is obviously specific to the credential.
///
/// The purpose of this is to make code more self-documenting and make it harder to accidentally
/// use an unverified/unvalidated credential (or other security critical object) where a verified
/// one is required.
/// Typestate indicating that a credential or other object has been internally validated.
#[repr(transparent)]
pub struct Verified<T>(T);
pub struct Valid<T>(T);
impl<T> AsRef<T> for Verified<T> {
impl<T> AsRef<T> for Valid<T> {
#[inline(always)]
fn as_ref(&self) -> &T {
&self.0
}
}
impl<T> AsMut<T> for Verified<T> {
impl<T> AsMut<T> for Valid<T> {
#[inline(always)]
fn as_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T> Deref for Verified<T> {
impl<T> Deref for Valid<T> {
type Target = T;
#[inline(always)]
@ -37,14 +31,14 @@ impl<T> Deref for Verified<T> {
}
}
impl<T> DerefMut for Verified<T> {
impl<T> DerefMut for Valid<T> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> Clone for Verified<T>
impl<T> Clone for Valid<T>
where
T: Clone,
{
@ -54,7 +48,7 @@ where
}
}
impl<T> PartialEq for Verified<T>
impl<T> PartialEq for Valid<T>
where
T: PartialEq,
{
@ -64,9 +58,9 @@ where
}
}
impl<T> Eq for Verified<T> where T: Eq {}
impl<T> Eq for Valid<T> where T: Eq {}
impl<T> Ord for Verified<T>
impl<T> Ord for Valid<T>
where
T: Ord,
{
@ -76,7 +70,7 @@ where
}
}
impl<T> PartialOrd for Verified<T>
impl<T> PartialOrd for Valid<T>
where
T: PartialOrd,
{
@ -86,7 +80,7 @@ where
}
}
impl<T> Hash for Verified<T>
impl<T> Hash for Valid<T>
where
T: Hash,
{
@ -96,7 +90,7 @@ where
}
}
impl<T> Debug for Verified<T>
impl<T> Debug for Valid<T>
where
T: Debug,
{
@ -106,7 +100,7 @@ where
}
}
impl<T> Verified<T> {
impl<T> Valid<T> {
/// Strip the Verified typestate off this object.
#[inline(always)]
pub fn unwrap(self) -> T {

View file

@ -9,11 +9,11 @@ use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use zerotier_crypto::{hash::*, secure_eq};
use zerotier_crypto::p384::*;
use zerotier_crypto::salsa::Salsa;
use zerotier_crypto::secret::Secret;
use zerotier_crypto::x25519::*;
use zerotier_crypto::{hash::*, secure_eq};
use zerotier_utils::arrayvec::ArrayVec;
use zerotier_utils::buffer::Buffer;
@ -23,7 +23,7 @@ use zerotier_utils::{base64_decode_url_nopad, base64_encode_url_nopad, hex};
use crate::protocol::{ADDRESS_SIZE, ADDRESS_SIZE_STRING, IDENTITY_POW_THRESHOLD};
use crate::vl1::Address;
use crate::vl1::Verified;
use crate::vl1::Valid;
/// Current maximum size for an identity signature.
pub const IDENTITY_MAX_SIGNATURE_SIZE: usize = P384_ECDSA_SIGNATURE_SIZE + 1;
@ -166,7 +166,7 @@ impl Identity {
const FLAG_INCLUDES_SECRETS: u8 = 0x80;
/// Generate a new identity.
pub fn generate() -> Verified<Self> {
pub fn generate() -> Valid<Self> {
// First generate an identity with just x25519 keys and derive its address.
let mut sha = SHA512::new();
let ed25519 = Ed25519KeyPair::generate();
@ -206,7 +206,7 @@ impl Identity {
assert!(id.upgrade().is_ok());
assert!(id.p384.is_some() && id.secret.as_ref().unwrap().p384.is_some());
Verified::assume_verified(id)
Valid::assume_verified(id)
}
/// Upgrade older x25519-only identities to hybrid identities with both x25519 and NIST P-384 curves.
@ -290,7 +290,7 @@ impl Identity {
/// Locally check the validity of this identity.
///
/// This is somewhat time consuming due to the memory-intensive work algorithm.
pub fn validate(self) -> Option<Verified<Self>> {
pub fn validate(self) -> Option<Valid<Self>> {
if let Some(p384) = self.p384.as_ref() {
let mut self_sign_buf: Vec<u8> = Vec::with_capacity(
ADDRESS_SIZE + 4 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE,
@ -321,7 +321,7 @@ impl Identity {
zt_address_derivation_work_function(&mut digest);
return if digest[0] < IDENTITY_POW_THRESHOLD && Address::from_bytes(&digest[59..64]).map_or(false, |a| a == self.address) {
Some(Verified::assume_verified(self))
Some(Valid::assume_verified(self))
} else {
None
};
@ -345,7 +345,7 @@ impl Identity {
/// For new identities with P-384 keys a hybrid agreement is performed using both X25519 and NIST P-384 ECDH.
/// The final key is derived as HMAC(x25519 secret, p-384 secret) to yield a FIPS-compliant key agreement with
/// the X25519 secret being used as a "salt" as far as FIPS is concerned.
pub fn agree(&self, other: &Verified<Identity>) -> Option<Secret<64>> {
pub fn agree(&self, other: &Valid<Identity>) -> Option<Secret<64>> {
if let Some(secret) = self.secret.as_ref() {
let c25519_secret: Secret<64> = Secret(SHA512::hash(&secret.x25519.agree(&other.x25519).0));

View file

@ -23,7 +23,7 @@ pub use path::Path;
pub use peer::Peer;
pub use rootset::{Root, RootSet};
pub use zerotier_crypto::verified::Verified;
pub use zerotier_crypto::typestate::Valid;
#[cfg(feature = "debug_events")]
#[allow(unused_macros)]

View file

@ -17,7 +17,7 @@ use crate::vl1::identity::Identity;
use crate::vl1::path::{Path, PathServiceResult};
use crate::vl1::peer::Peer;
use crate::vl1::rootset::RootSet;
use crate::vl1::Verified;
use crate::vl1::Valid;
use zerotier_crypto::random;
use zerotier_utils::error::InvalidParameterError;
@ -42,10 +42,10 @@ pub trait ApplicationLayer: Sync + Send {
fn event(&self, event: Event);
/// Load this node's identity from the data store.
fn load_node_identity(&self) -> Option<Verified<Identity>>;
fn load_node_identity(&self) -> Option<Valid<Identity>>;
/// Save this node's identity to the data store, returning true on success.
fn save_node_identity(&self, id: &Verified<Identity>) -> bool;
fn save_node_identity(&self, id: &Valid<Identity>) -> bool;
/// Get a pooled packet buffer for internal use.
fn get_buffer(&self) -> PooledPacketBuffer;
@ -84,7 +84,7 @@ pub trait ApplicationLayer: Sync + Send {
#[allow(unused_variables)]
fn should_use_physical_path<Application: ApplicationLayer + ?Sized>(
&self,
id: &Verified<Identity>,
id: &Valid<Identity>,
endpoint: &Endpoint,
local_socket: Option<&Application::LocalSocket>,
local_interface: Option<&Application::LocalInterface>,
@ -98,7 +98,7 @@ pub trait ApplicationLayer: Sync + Send {
#[allow(unused_variables)]
fn get_path_hints<Application: ApplicationLayer + ?Sized>(
&self,
id: &Verified<Identity>,
id: &Valid<Identity>,
) -> Option<Vec<(Endpoint, Option<Application::LocalSocket>, Option<Application::LocalInterface>)>> {
None
}
@ -133,7 +133,7 @@ pub trait InnerProtocolLayer: Sync + Send {
/// Check if this node should respond to messages from a given peer at all.
///
/// The default implementation always returns true.
fn should_respond_to(&self, id: &Verified<Identity>) -> bool {
fn should_respond_to(&self, id: &Valid<Identity>) -> bool {
true
}
@ -144,7 +144,7 @@ pub trait InnerProtocolLayer: Sync + Send {
/// some privileged relationship like mutual membership in a network.
///
/// The default implementation always returns true.
fn has_trust_relationship(&self, id: &Verified<Identity>) -> bool {
fn has_trust_relationship(&self, id: &Valid<Identity>) -> bool {
true
}
@ -207,7 +207,7 @@ pub trait InnerProtocolLayer: Sync + Send {
struct RootInfo {
/// Root sets to which we are a member.
sets: HashMap<String, Verified<RootSet>>,
sets: HashMap<String, Valid<RootSet>>,
/// Root peers and their statically defined endpoints (from root sets).
roots: HashMap<Arc<Peer>, Vec<Endpoint>>,
@ -251,7 +251,7 @@ pub struct Node {
pub instance_id: [u8; 16],
/// This node's identity and permanent keys.
pub identity: Verified<Identity>,
pub identity: Valid<Identity>,
/// Interval latches for periodic background tasks.
intervals: Mutex<BackgroundTaskIntervals>,
@ -352,7 +352,7 @@ impl Node {
/// Add a new root set or update the existing root set if the new root set is newer and otherwise matches.
#[inline]
pub fn add_update_root_set(&self, rs: Verified<RootSet>) -> bool {
pub fn add_update_root_set(&self, rs: Valid<RootSet>) -> bool {
let mut roots = self.roots.write().unwrap();
if let Some(entry) = roots.sets.get_mut(&rs.name) {
if rs.should_replace(entry) {
@ -468,8 +468,7 @@ impl Node {
if let Some(peer) = peers.get(&m.identity.address) {
new_roots.insert(peer.clone(), m.endpoints.as_ref().unwrap().iter().cloned().collect());
} else {
if let Some(peer) = Peer::new(&self.identity, Verified::assume_verified(m.identity.clone()), time_ticks)
{
if let Some(peer) = Peer::new(&self.identity, Valid::assume_verified(m.identity.clone()), time_ticks) {
drop(peers);
new_roots.insert(
self.peers
@ -968,7 +967,7 @@ impl Node {
/// This will only replace an existing root set with a newer one. It won't add a new root set, which must be
/// done by an authorized user or administrator not just by a root.
#[allow(unused)]
pub(crate) fn on_remote_update_root_set(&self, received_from: &Identity, rs: Verified<RootSet>) {
pub(crate) fn on_remote_update_root_set(&self, received_from: &Identity, rs: Valid<RootSet>) {
let mut roots = self.roots.write().unwrap();
if let Some(entry) = roots.sets.get_mut(&rs.name) {
if entry.members.iter().any(|m| m.identity.eq(received_from)) && rs.should_replace(entry) {

View file

@ -18,14 +18,14 @@ use crate::protocol::*;
use crate::vl1::address::Address;
use crate::vl1::debug_event;
use crate::vl1::node::*;
use crate::vl1::Verified;
use crate::vl1::Valid;
use crate::vl1::{Endpoint, Identity, Path};
use crate::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
pub(crate) const SERVICE_INTERVAL_MS: i64 = 10000;
pub struct Peer {
pub identity: Verified<Identity>,
pub identity: Valid<Identity>,
v1_proto_static_secret: v1::SymmetricSecret,
paths: Mutex<Vec<PeerPath>>,
@ -62,7 +62,7 @@ impl Peer {
///
/// This only returns None if this_node_identity does not have its secrets or if some
/// fatal error occurs performing key agreement between the two identities.
pub(crate) fn new(this_node_identity: &Verified<Identity>, id: Verified<Identity>, time_ticks: i64) -> Option<Self> {
pub(crate) fn new(this_node_identity: &Valid<Identity>, id: Valid<Identity>, time_ticks: i64) -> Option<Self> {
this_node_identity.agree(&id).map(|static_secret| -> Self {
Self {
identity: id,

View file

@ -6,7 +6,7 @@ use std::io::Write;
use crate::vl1::identity::{Identity, IDENTITY_MAX_SIGNATURE_SIZE};
use crate::vl1::Endpoint;
use zerotier_crypto::verified::Verified;
use zerotier_crypto::typestate::Valid;
use zerotier_utils::arrayvec::ArrayVec;
use zerotier_utils::buffer::Buffer;
use zerotier_utils::marshalable::{Marshalable, UnmarshalError};
@ -91,7 +91,7 @@ impl RootSet {
}
/// Get the ZeroTier default root set, which contains roots run by ZeroTier Inc.
pub fn zerotier_default() -> Verified<Self> {
pub fn zerotier_default() -> Valid<Self> {
let mut cursor = 0;
let rs = include_bytes!("../../default-rootset/root.zerotier.com.bin");
//let rs = include_bytes!("../../default-rootset/test-root.bin");
@ -107,7 +107,7 @@ impl RootSet {
}
/// Verify signatures present in this root cluster definition.
pub fn verify(self) -> Option<Verified<Self>> {
pub fn verify(self) -> Option<Valid<Self>> {
if self.members.is_empty() {
return None;
}
@ -119,7 +119,7 @@ impl RootSet {
}
}
return Some(Verified::assume_verified(self));
return Some(Valid::assume_verified(self));
}
/// Add a member to this definition, replacing any current entry with this address.

View file

@ -1,6 +1,6 @@
use std::io::Write;
use zerotier_crypto::verified::Verified;
use zerotier_crypto::typestate::Valid;
use zerotier_utils::arrayvec::ArrayVec;
use serde::{Deserialize, Serialize};
@ -26,7 +26,7 @@ impl Revocation {
threshold: i64,
target: Address,
issued_to: Address,
signer: &Verified<Identity>,
signer: &Valid<Identity>,
fast_propagate: bool,
) -> Option<Self> {
let mut r = Self {

View file

@ -11,11 +11,11 @@ pub struct Switch {}
#[allow(unused_variables)]
impl InnerProtocolLayer for Switch {
fn should_respond_to(&self, id: &zerotier_crypto::verified::Verified<crate::vl1::Identity>) -> bool {
fn should_respond_to(&self, id: &zerotier_crypto::typestate::Valid<crate::vl1::Identity>) -> bool {
true
}
fn has_trust_relationship(&self, id: &zerotier_crypto::verified::Verified<crate::vl1::Identity>) -> bool {
fn has_trust_relationship(&self, id: &zerotier_crypto::typestate::Valid<crate::vl1::Identity>) -> bool {
true
}

View file

@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use zerotier_crypto::hash::SHA384;
use zerotier_crypto::secure_eq;
use zerotier_crypto::verified::Verified;
use zerotier_crypto::typestate::Valid;
use zerotier_utils::arrayvec::ArrayVec;
use zerotier_utils::blob::Blob;
use zerotier_utils::error::InvalidParameterError;
@ -171,10 +171,13 @@ impl CertificateOfMembership {
}
/// Verify this certificate of membership.
pub fn verify(self, issuer: &Identity, expect_issued_to: &Identity) -> Option<Verified<Self>> {
if secure_eq(&Self::v1_proto_issued_to_fingerprint(expect_issued_to), &self.issued_to_fingerprint.as_bytes()[..32]) {
pub fn verify(self, issuer: &Identity, expect_issued_to: &Identity) -> Option<Valid<Self>> {
if secure_eq(
&Self::v1_proto_issued_to_fingerprint(expect_issued_to),
&self.issued_to_fingerprint.as_bytes()[..32],
) {
if issuer.verify(&self.v1_proto_get_qualifier_bytes(), self.signature.as_bytes()) {
return Some(Verified::assume_verified(self));
return Some(Valid::assume_verified(self));
}
}
return None;

View file

@ -8,7 +8,7 @@ use serde::de::DeserializeOwned;
use serde::Serialize;
use zerotier_crypto::random::next_u32_secure;
use zerotier_network_hypervisor::vl1::{Identity, Verified};
use zerotier_network_hypervisor::vl1::{Identity, Valid};
use zerotier_utils::io::{fs_restrict_permissions, read_limit, DEFAULT_FILE_IO_READ_LIMIT};
use zerotier_utils::json::to_json_pretty;
@ -22,7 +22,7 @@ pub const CONFIG_FILENAME: &'static str = "local.conf";
const AUTH_TOKEN_DEFAULT_LENGTH: usize = 48;
const AUTH_TOKEN_POSSIBLE_CHARS: &'static str = "0123456789abcdefghijklmnopqrstuvwxyz";
pub fn load_node_identity(base_path: &Path) -> Option<Verified<Identity>> {
pub fn load_node_identity(base_path: &Path) -> Option<Valid<Identity>> {
let id_data = read_limit(base_path.join(IDENTITY_SECRET_FILENAME), 4096);
if id_data.is_err() {
return None;
@ -31,10 +31,10 @@ pub fn load_node_identity(base_path: &Path) -> Option<Verified<Identity>> {
if id_data.is_err() {
return None;
}
Some(Verified::assume_verified(id_data.unwrap()))
Some(Valid::assume_verified(id_data.unwrap()))
}
pub fn save_node_identity(base_path: &Path, id: &Verified<Identity>) -> bool {
pub fn save_node_identity(base_path: &Path, id: &Valid<Identity>) -> bool {
assert!(id.secret.is_some());
let id_secret_str = id.to_secret_string();
let id_public_str = id.to_string();
@ -53,11 +53,11 @@ pub struct DataDir<Config: PartialEq + Eq + Clone + Send + Sync + Default + Seri
}
impl<Config: PartialEq + Eq + Clone + Send + Sync + Default + Serialize + DeserializeOwned + 'static> VL1DataStorage for DataDir<Config> {
fn load_node_identity(&self) -> Option<Verified<Identity>> {
fn load_node_identity(&self) -> Option<Valid<Identity>> {
load_node_identity(self.base_path.as_path())
}
fn save_node_identity(&self, id: &Verified<Identity>) -> bool {
fn save_node_identity(&self, id: &Valid<Identity>) -> bool {
save_node_identity(self.base_path.as_path(), id)
}
}

View file

@ -22,8 +22,8 @@ const UPDATE_UDP_BINDINGS_EVERY_SECS: usize = 10;
/// Trait to implement to provide storage for VL1-related state information.
pub trait VL1DataStorage: Sync + Send {
fn load_node_identity(&self) -> Option<Verified<Identity>>;
fn save_node_identity(&self, id: &Verified<Identity>) -> bool;
fn load_node_identity(&self) -> Option<Valid<Identity>>;
fn save_node_identity(&self, id: &Valid<Identity>) -> bool;
}
/// VL1 service that connects to the physical network and hosts an inner protocol like ZeroTier VL2.
@ -218,12 +218,12 @@ impl<Inner: InnerProtocolLayer + ?Sized + 'static> ApplicationLayer for VL1Servi
}
#[inline]
fn load_node_identity(&self) -> Option<Verified<Identity>> {
fn load_node_identity(&self) -> Option<Valid<Identity>> {
self.vl1_data_storage.load_node_identity()
}
#[inline]
fn save_node_identity(&self, id: &Verified<Identity>) -> bool {
fn save_node_identity(&self, id: &Valid<Identity>) -> bool {
self.vl1_data_storage.save_node_identity(id)
}