Create and verify locators via CLI.

This commit is contained in:
Adam Ierymenko 2021-03-11 21:37:04 -05:00
parent a0a79fa1b7
commit 68fe57decd
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
7 changed files with 103 additions and 72 deletions

View file

@ -115,6 +115,7 @@ bool Endpoint::fromString(const char *s) noexcept
} }
} else if (strchr(s, '/') != nullptr) { } else if (strchr(s, '/') != nullptr) {
// IP/port is parsed as an IP_UDP endpoint for backward compatibility. // IP/port is parsed as an IP_UDP endpoint for backward compatibility.
this->type = ZT_ENDPOINT_TYPE_IP_UDP;
return asInetAddress(this->value.ss).fromString(s); return asInetAddress(this->value.ss).fromString(s);
} }
return false; return false;

View file

@ -120,20 +120,12 @@ impl PartialEq for Endpoint {
impl Eq for Endpoint {} impl Eq for Endpoint {}
impl serde::Serialize for Endpoint { impl serde::Serialize for Endpoint {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { serializer.serialize_str(self.to_string().as_str()) }
serializer.serialize_str(self.to_string().as_str())
} }
}
struct EndpointVisitor; struct EndpointVisitor;
impl<'de> serde::de::Visitor<'de> for EndpointVisitor { impl<'de> serde::de::Visitor<'de> for EndpointVisitor {
type Value = Endpoint; type Value = Endpoint;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("Endpoint value in string form") }
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("Endpoint value in string form")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error { fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error {
let id = Endpoint::new_from_string(s); let id = Endpoint::new_from_string(s);
if id.is_err() { if id.is_err() {
@ -142,9 +134,6 @@ impl<'de> serde::de::Visitor<'de> for EndpointVisitor {
return Ok(id.ok().unwrap() as Self::Value); return Ok(id.ok().unwrap() as Self::Value);
} }
} }
impl<'de> serde::Deserialize<'de> for Endpoint { impl<'de> serde::Deserialize<'de> for Endpoint {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { deserializer.deserialize_str(EndpointVisitor) }
deserializer.deserialize_str(EndpointVisitor)
}
} }

View file

@ -13,10 +13,10 @@
use std::ffi::CString; use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_uint}; use std::os::raw::{c_char, c_int, c_uint};
use std::ptr::null;
use crate::*; use crate::*;
use crate::capi as ztcore; use crate::capi as ztcore;
use std::ptr::null;
pub struct Locator { pub struct Locator {
pub(crate) capi: *const ztcore::ZT_Locator, pub(crate) capi: *const ztcore::ZT_Locator,
@ -84,6 +84,20 @@ impl Locator {
} }
eps eps
} }
pub fn verify(&self, id: &Identity) -> bool {
unsafe { ztcore::ZT_Locator_verify(self.capi, id.capi) != 0 }
}
pub fn signer(&self) -> Fingerprint {
unsafe {
let fp = ztcore::ZT_Locator_fingerprint(self.capi);
if fp.is_null() {
panic!("ZT_Locator_fingerprint() returned null, should not be allowed");
}
Fingerprint::new_from_capi(&*fp)
}
}
} }
impl Drop for Locator { impl Drop for Locator {
@ -125,20 +139,12 @@ impl PartialEq for Locator {
impl Eq for Locator {} impl Eq for Locator {}
impl serde::Serialize for Locator { impl serde::Serialize for Locator {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { serializer.serialize_str(self.to_string().as_str()) }
serializer.serialize_str(self.to_string().as_str())
} }
}
struct LocatorVisitor; struct LocatorVisitor;
impl<'de> serde::de::Visitor<'de> for LocatorVisitor { impl<'de> serde::de::Visitor<'de> for LocatorVisitor {
type Value = Locator; type Value = Locator;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("Locator value in string form") }
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("Locator value in string form")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error { fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error {
let id = Locator::new_from_string(s); let id = Locator::new_from_string(s);
if id.is_err() { if id.is_err() {
@ -147,9 +153,6 @@ impl<'de> serde::de::Visitor<'de> for LocatorVisitor {
return Ok(id.ok().unwrap() as Self::Value); return Ok(id.ok().unwrap() as Self::Value);
} }
} }
impl<'de> serde::Deserialize<'de> for Locator { impl<'de> serde::Deserialize<'de> for Locator {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { deserializer.deserialize_str(LocatorVisitor) }
deserializer.deserialize_str(LocatorVisitor)
}
} }

View file

@ -67,7 +67,7 @@ Common Operations:
Advanced Operations: Advanced Operations:
service Start this node service Start this node
(usually not run directly) (usually not invoked directly)
controller <command> [option] controller <command> [option]
· list List networks on controller · list List networks on controller
@ -110,16 +110,8 @@ Advanced Operations:
An <address> may be specified as a 10-digit short ZeroTier address, a An <address> may be specified as a 10-digit short ZeroTier address, a
fingerprint containing both an address and a SHA384 hash, or an identity. fingerprint containing both an address and a SHA384 hash, or an identity.
The latter two options are equivalent in terms of specificity and may be Identities and locators can be specified as either paths to files on the
used if stronger security guarantees are desired than those provided by filesystem or verbatim objects in string format. This is auto-detected."###, ver.0, ver.1, ver.2)
the basic ZeroTier addressing system. Fields of type <identity> must be
full identities and may be specified either verbatim or as a path to a file.
An <endpoint> is a place where a peer may be reached. Currently these are
just 'IP/port' format addresses but other types may be added in the future.
The 'service' command starts a node. It will run until the node receives
an exit signal and is normally not used directly."###, ver.0, ver.1, ver.2)
} }
pub(crate) fn print_help() { pub(crate) fn print_help() {

View file

@ -19,8 +19,6 @@ use dialoguer::Input;
use zerotier_core::*; use zerotier_core::*;
use crate::store::Store; use crate::store::Store;
use crate::utils::read_identity;
use futures::SinkExt;
fn list(store: &Store, auth_token: &Option<String>) -> i32 { fn list(store: &Store, auth_token: &Option<String>) -> i32 {
0 0
@ -98,7 +96,7 @@ fn newcsr<'a>(store: &Store, cli_args: &ArgMatches<'a>, auth_token: &Option<Stri
if identity.is_empty() { if identity.is_empty() {
break; break;
} }
let identity = read_identity(identity.as_str(), true); let identity = crate::utils::read_identity(identity.as_str(), true);
if identity.is_err() { if identity.is_err() {
println!("ERROR: identity invalid or unable to read from file."); println!("ERROR: identity invalid or unable to read from file.");
return 1; return 1;
@ -110,19 +108,24 @@ fn newcsr<'a>(store: &Store, cli_args: &ArgMatches<'a>, auth_token: &Option<Stri
} }
let locator: String = Input::with_theme(theme) let locator: String = Input::with_theme(theme)
.with_prompt(format!(" [{}] Locator for {} (optional)", identities.len() + 1, identity.address.to_string())) .with_prompt(format!(" [{}] Locator or path to locator for {} (optional)", identities.len() + 1, identity.address.to_string()))
.allow_empty(true) .allow_empty(true)
.interact_text() .interact_text()
.unwrap_or_default(); .unwrap_or_default();
let locator = if locator.is_empty() { let locator = if locator.is_empty() {
None None
} else { } else {
let l = Locator::new_from_string(locator.as_str()); let l = crate::utils::read_locator(locator.as_str());
if l.is_err() { if l.is_err() {
println!("ERROR: locator invalid: {}", l.err().unwrap().to_str()); println!("ERROR: locator invalid: {}", l.err().unwrap());
return 1; return 1;
} }
l.ok() let l = l.ok();
if !l.as_ref().unwrap().verify(&identity) {
println!("ERROR: locator not signed by this identity.");
return 1;
}
l
}; };
identities.push(CertificateIdentity { identities.push(CertificateIdentity {

View file

@ -70,10 +70,38 @@ fn new_<'a>(store: &Store, cli_args: &ArgMatches<'a>) -> i32 {
} }
fn verify<'a>(store: &Store, cli_args: &ArgMatches<'a>) -> i32 { fn verify<'a>(store: &Store, cli_args: &ArgMatches<'a>) -> i32 {
let identity = crate::utils::read_identity(cli_args.value_of("identity").unwrap(), true);
if identity.is_err() {
println!("ERROR: identity invalid: {}", identity.err().unwrap());
return 1;
}
let identity = identity.unwrap();
let locator = crate::utils::read_locator(cli_args.value_of("locator").unwrap());
if locator.is_err() {
println!("ERROR: locator invalid: {}", locator.err().unwrap());
return 1;
}
if locator.unwrap().verify(&identity) {
println!("OK");
0 0
} else {
println!("FAILED");
1
}
} }
fn show<'a>(store: &Store, cli_args: &ArgMatches<'a>) -> i32 { fn show<'a>(store: &Store, cli_args: &ArgMatches<'a>) -> i32 {
let locator = crate::utils::read_locator(cli_args.value_of("locator").unwrap());
if locator.is_err() {
println!("ERROR: locator invalid: {}", locator.err().unwrap());
return 1;
}
let locator = locator.unwrap();
println!("{} timestamp {}", locator.signer().to_string(), (locator.timestamp() as f64) / 1000.0);
let endpoints = locator.endpoints();
for ep in endpoints.iter() {
println!(" {}", (*ep).to_string())
}
0 0
} }

View file

@ -18,7 +18,7 @@ use std::mem::MaybeUninit;
use std::os::raw::c_uint; use std::os::raw::c_uint;
use std::path::Path; use std::path::Path;
use zerotier_core::Identity; use zerotier_core::{Identity, Locator};
use crate::osdep; use crate::osdep;
@ -57,38 +57,53 @@ pub(crate) fn read_limit<P: AsRef<Path>>(path: P, limit: usize) -> std::io::Resu
} }
/// Read an identity as either a literal or from a file. /// Read an identity as either a literal or from a file.
/// This is used in parsing command lines, allowing either a literal or a path
/// to be specified and automagically disambiguating.
pub(crate) fn read_identity(input: &str, validate: bool) -> Result<Identity, String> { pub(crate) fn read_identity(input: &str, validate: bool) -> Result<Identity, String> {
let id = Identity::new_from_string(input); let parse_func = |s: &str| {
if id.is_err() { Identity::new_from_string(s).map_or_else(|e| {
let input = Path::new(input); Err(format!("invalid identity: {}", e.to_str()))
if !input.exists() || !input.is_file() { }, |id| {
return Err(format!("invalid identity: {}", id.err().unwrap().to_str())); if !validate || id.validate() {
Ok(id)
} else {
Err(String::from("invalid identity: local validation failed"))
} }
})
};
if Path::new(input).exists() {
read_limit(input, 16384).map_or_else(|e| { read_limit(input, 16384).map_or_else(|e| {
Err(e.to_string()) Err(e.to_string())
}, |v| { }, |v| {
String::from_utf8(v).map_or_else(|e| { String::from_utf8(v).map_or_else(|e| {
Err(e.to_string()) Err(e.to_string())
}, |s| { }, |s| {
Identity::new_from_string(s.as_str()).map_or_else(|_| { parse_func(s.as_str())
Err(format!("Invalid identity in file {}", input.to_str().unwrap_or("")))
}, |id| {
if validate && !id.validate() {
Err(String::from("invalid identity: local validation failed"))
} else {
Ok(id)
}
})
}) })
}) })
} else { } else {
let id = id.ok().unwrap(); parse_func(input)
if validate && !id.validate() { }
Err(String::from("invalid identity: local validation failed")) }
/// Read a locator as either a literal or from a file.
pub(crate) fn read_locator(input: &str) -> Result<Locator, String> {
let parse_func = |s: &str| {
Locator::new_from_string(s).map_or_else(|e| {
Err(format!("invalid locator: {}", e.to_str()))
}, |loc| {
Ok(loc)
})
};
if Path::new(input).exists() {
read_limit(input, 16384).map_or_else(|e| {
Err(e.to_string())
}, |v| {
String::from_utf8(v).map_or_else(|e| {
Err(e.to_string())
}, |s| {
parse_func(s.as_str())
})
})
} else { } else {
Ok(id) parse_func(input)
}
} }
} }