mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-07-25 11:42:50 +02:00
Create and verify locators via CLI.
This commit is contained in:
parent
a0a79fa1b7
commit
68fe57decd
7 changed files with 103 additions and 72 deletions
|
@ -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;
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue