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) {
// 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 false;

View file

@ -120,20 +120,12 @@ impl PartialEq for Endpoint {
impl Eq for Endpoint {}
impl serde::Serialize for Endpoint {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
serializer.serialize_str(self.to_string().as_str())
}
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { serializer.serialize_str(self.to_string().as_str()) }
}
struct EndpointVisitor;
impl<'de> serde::de::Visitor<'de> for EndpointVisitor {
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 {
let id = Endpoint::new_from_string(s);
if id.is_err() {
@ -142,9 +134,6 @@ impl<'de> serde::de::Visitor<'de> for EndpointVisitor {
return Ok(id.ok().unwrap() as Self::Value);
}
}
impl<'de> serde::Deserialize<'de> for Endpoint {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
deserializer.deserialize_str(EndpointVisitor)
}
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { deserializer.deserialize_str(EndpointVisitor) }
}

View file

@ -13,10 +13,10 @@
use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_uint};
use std::ptr::null;
use crate::*;
use crate::capi as ztcore;
use std::ptr::null;
pub struct Locator {
pub(crate) capi: *const ztcore::ZT_Locator,
@ -84,6 +84,20 @@ impl Locator {
}
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 {
@ -125,20 +139,12 @@ impl PartialEq for Locator {
impl Eq for Locator {}
impl serde::Serialize for Locator {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
serializer.serialize_str(self.to_string().as_str())
}
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { serializer.serialize_str(self.to_string().as_str()) }
}
struct LocatorVisitor;
impl<'de> serde::de::Visitor<'de> for LocatorVisitor {
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 {
let id = Locator::new_from_string(s);
if id.is_err() {
@ -147,9 +153,6 @@ impl<'de> serde::de::Visitor<'de> for LocatorVisitor {
return Ok(id.ok().unwrap() as Self::Value);
}
}
impl<'de> serde::Deserialize<'de> for Locator {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
deserializer.deserialize_str(LocatorVisitor)
}
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { deserializer.deserialize_str(LocatorVisitor) }
}

View file

@ -67,7 +67,7 @@ Common Operations:
Advanced Operations:
service Start this node
(usually not run directly)
(usually not invoked directly)
controller <command> [option]
· list List networks on controller
@ -110,16 +110,8 @@ Advanced Operations:
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.
The latter two options are equivalent in terms of specificity and may be
used if stronger security guarantees are desired than those provided by
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)
Identities and locators can be specified as either paths to files on the
filesystem or verbatim objects in string format. This is auto-detected."###, ver.0, ver.1, ver.2)
}
pub(crate) fn print_help() {

View file

@ -19,8 +19,6 @@ use dialoguer::Input;
use zerotier_core::*;
use crate::store::Store;
use crate::utils::read_identity;
use futures::SinkExt;
fn list(store: &Store, auth_token: &Option<String>) -> i32 {
0
@ -98,7 +96,7 @@ fn newcsr<'a>(store: &Store, cli_args: &ArgMatches<'a>, auth_token: &Option<Stri
if identity.is_empty() {
break;
}
let identity = read_identity(identity.as_str(), true);
let identity = crate::utils::read_identity(identity.as_str(), true);
if identity.is_err() {
println!("ERROR: identity invalid or unable to read from file.");
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)
.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)
.interact_text()
.unwrap_or_default();
let locator = if locator.is_empty() {
None
} else {
let l = Locator::new_from_string(locator.as_str());
let l = crate::utils::read_locator(locator.as_str());
if l.is_err() {
println!("ERROR: locator invalid: {}", l.err().unwrap().to_str());
println!("ERROR: locator invalid: {}", l.err().unwrap());
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 {

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 {
0
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
} else {
println!("FAILED");
1
}
}
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
}

View file

@ -18,7 +18,7 @@ use std::mem::MaybeUninit;
use std::os::raw::c_uint;
use std::path::Path;
use zerotier_core::Identity;
use zerotier_core::{Identity, Locator};
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.
/// 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> {
let id = Identity::new_from_string(input);
if id.is_err() {
let input = Path::new(input);
if !input.exists() || !input.is_file() {
return Err(format!("invalid identity: {}", id.err().unwrap().to_str()));
}
let parse_func = |s: &str| {
Identity::new_from_string(s).map_or_else(|e| {
Err(format!("invalid identity: {}", e.to_str()))
}, |id| {
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| {
Err(e.to_string())
}, |v| {
String::from_utf8(v).map_or_else(|e| {
Err(e.to_string())
}, |s| {
Identity::new_from_string(s.as_str()).map_or_else(|_| {
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)
}
})
parse_func(s.as_str())
})
})
} else {
let id = id.ok().unwrap();
if validate && !id.validate() {
Err(String::from("invalid identity: local validation failed"))
} else {
Ok(id)
}
parse_func(input)
}
}
/// 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 {
parse_func(input)
}
}