Create a root set for our current roots, and it works!!!!

This commit is contained in:
Adam Ierymenko 2022-05-06 22:28:29 -04:00
parent 083e2bc666
commit a4db105f23
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
7 changed files with 368 additions and 44 deletions

View file

@ -8,6 +8,10 @@
use crate::util::buffer::Buffer;
/// Must be larger than any object we want to use with to_bytes() or from_bytes().
/// This hack can go away once Rust allows us to reference trait consts as generics.
const TEMP_BUF_SIZE: usize = 131072;
/// A super-lightweight zero-allocation serialization interface.
pub trait Marshalable: Sized {
const MAX_MARSHAL_SIZE: usize;
@ -26,7 +30,6 @@ pub trait Marshalable: Sized {
///
/// This will return an Err if the buffer is too small or some other error occurs. It's just
/// a shortcut to creating a buffer and marshaling into it.
#[inline(always)]
fn to_buffer<const BL: usize>(&self) -> std::io::Result<Buffer<BL>> {
assert!(BL >= Self::MAX_MARSHAL_SIZE);
let mut tmp = Buffer::new();
@ -37,9 +40,28 @@ pub trait Marshalable: Sized {
/// Unmarshal this object from a buffer.
///
/// This is just a shortcut to calling unmarshal() with a zero cursor and then discarding the cursor.
#[inline(always)]
fn from_buffer<const BL: usize>(buf: &Buffer<BL>) -> std::io::Result<Self> {
let mut tmp = 0;
Self::unmarshal(buf, &mut tmp)
}
/// Marshal and convert to a Rust vector.
fn to_bytes(&self) -> Vec<u8> {
assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE);
let mut tmp = Buffer::<TEMP_BUF_SIZE>::new_boxed();
assert!(self.marshal(&mut tmp).is_ok());
tmp.as_bytes().to_vec()
}
/// Unmarshal from a raw slice.
fn from_bytes(b: &[u8]) -> std::io::Result<Self> {
if b.len() <= TEMP_BUF_SIZE {
let mut tmp = Buffer::<TEMP_BUF_SIZE>::new_boxed();
assert!(tmp.append_bytes(b).is_ok());
let mut cursor = 0;
Self::unmarshal(&tmp, &mut cursor)
} else {
Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "object too large"))
}
}
}

View file

@ -0,0 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* (c)2021 ZeroTier, Inc.
* https://www.zerotier.com/
*/
pub mod rootset;

View file

@ -0,0 +1,118 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* (c)2021 ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::io::Write;
use clap::ArgMatches;
use crate::{exitcode, Flags};
use zerotier_network_hypervisor::util::marshalable::Marshalable;
use zerotier_network_hypervisor::vl1::RootSet;
pub async fn cmd(flags: Flags, cmd_args: &ArgMatches) -> i32 {
match cmd_args.subcommand() {
Some(("trust", sc_args)) => todo!(),
Some(("untrust", sc_args)) => todo!(),
Some(("list", _)) => todo!(),
Some(("sign", sc_args)) => {
let path = sc_args.value_of("path");
let secret_arg = sc_args.value_of("secret");
if path.is_some() && secret_arg.is_some() {
let path = path.unwrap();
let secret_arg = secret_arg.unwrap();
let secret = crate::utils::parse_cli_identity(secret_arg, true).await;
let json_data = crate::utils::read_limit(path, 1048576).await;
if secret.is_err() {
eprintln!("ERROR: unable to parse '{}' or read as a file.", secret_arg);
return exitcode::ERR_IOERR;
}
let secret = secret.unwrap();
if !secret.secret.is_some() {
eprintln!("ERROR: identity does not include secret key, which is required for signing.");
return exitcode::ERR_IOERR;
}
if json_data.is_err() {
eprintln!("ERROR: unable to read '{}'.", path);
return exitcode::ERR_IOERR;
}
let json_data = json_data.unwrap();
let root_set = serde_json::from_slice::<RootSet>(json_data.as_slice());
if root_set.is_err() {
eprintln!("ERROR: root set JSON parsing failed: {}", root_set.err().unwrap().to_string());
return exitcode::ERR_IOERR;
}
let mut root_set = root_set.unwrap();
if !root_set.sign(&secret) {
eprintln!("ERROR: root set signing failed, invalid identity?");
return exitcode::ERR_INTERNAL;
}
println!("{}", crate::utils::to_json_pretty(&root_set));
} else {
eprintln!("ERROR: 'rootset sign' requires a path to a root set in JSON format and a secret identity.");
return exitcode::ERR_IOERR;
}
}
Some(("verify", sc_args)) => {
let path = sc_args.value_of("path");
if path.is_some() {
let path = path.unwrap();
let json_data = crate::utils::read_limit(path, 1048576).await;
if json_data.is_err() {
eprintln!("ERROR: unable to read '{}'.", path);
return exitcode::ERR_IOERR;
}
let json_data = json_data.unwrap();
let root_set = serde_json::from_slice::<RootSet>(json_data.as_slice());
if root_set.is_err() {
eprintln!("ERROR: root set JSON parsing failed: {}", root_set.err().unwrap().to_string());
return exitcode::ERR_IOERR;
}
let root_set = root_set.unwrap();
if root_set.verify() {
println!("OK");
} else {
println!("FAILED");
return exitcode::ERR_DATA_FORMAT;
}
} else {
eprintln!("ERROR: 'rootset marshal' requires a path to a root set in JSON format.");
return exitcode::ERR_IOERR;
}
}
Some(("marshal", sc_args)) => {
let path = sc_args.value_of("path");
if path.is_some() {
let path = path.unwrap();
let json_data = crate::utils::read_limit(path, 1048576).await;
if json_data.is_err() {
eprintln!("ERROR: unable to read '{}'.", path);
return exitcode::ERR_IOERR;
}
let json_data = json_data.unwrap();
let root_set = serde_json::from_slice::<RootSet>(json_data.as_slice());
if root_set.is_err() {
eprintln!("ERROR: root set JSON parsing failed: {}", root_set.err().unwrap().to_string());
return exitcode::ERR_IOERR;
}
let _ = std::io::stdout().write_all(root_set.unwrap().to_bytes().as_slice());
} else {
eprintln!("ERROR: 'rootset marshal' requires a path to a root set in JSON format.");
return exitcode::ERR_IOERR;
}
}
_ => panic!(),
}
return exitcode::OK;
}

View file

@ -0,0 +1,134 @@
/* This is a forked and hacked version of PrettyFormatter from:
*
* https://github.com/serde-rs/json/blob/master/src/ser.rs
*
* It is therefore under the same Apache license.
*/
use serde_json::ser::Formatter;
#[derive(Clone, Debug)]
pub struct JsonFormatter<'a> {
current_indent: usize,
has_value: bool,
indent: &'a [u8],
}
fn indent<W>(wr: &mut W, n: usize, s: &[u8]) -> std::io::Result<()>
where
W: ?Sized + std::io::Write,
{
for _ in 0..n {
wr.write_all(s)?;
}
Ok(())
}
impl<'a> JsonFormatter<'a> {
pub fn new() -> Self {
JsonFormatter::with_indent(b" ")
}
pub fn with_indent(indent: &'a [u8]) -> Self {
JsonFormatter { current_indent: 0, has_value: false, indent }
}
}
impl<'a> Default for JsonFormatter<'a> {
fn default() -> Self {
JsonFormatter::new()
}
}
impl<'a> Formatter for JsonFormatter<'a> {
fn begin_array<W>(&mut self, writer: &mut W) -> std::io::Result<()>
where
W: ?Sized + std::io::Write,
{
self.current_indent += 1;
self.has_value = false;
writer.write_all(b"[")
}
fn end_array<W>(&mut self, writer: &mut W) -> std::io::Result<()>
where
W: ?Sized + std::io::Write,
{
self.current_indent -= 1;
if self.has_value {
writer.write_all(b" ]")
} else {
writer.write_all(b"]")
}
}
fn begin_array_value<W>(&mut self, writer: &mut W, first: bool) -> std::io::Result<()>
where
W: ?Sized + std::io::Write,
{
if first {
writer.write_all(b" ")?;
} else {
writer.write_all(b", ")?;
}
Ok(())
}
fn end_array_value<W>(&mut self, _writer: &mut W) -> std::io::Result<()>
where
W: ?Sized + std::io::Write,
{
self.has_value = true;
Ok(())
}
fn begin_object<W>(&mut self, writer: &mut W) -> std::io::Result<()>
where
W: ?Sized + std::io::Write,
{
self.current_indent += 1;
self.has_value = false;
writer.write_all(b"{")
}
fn end_object<W>(&mut self, writer: &mut W) -> std::io::Result<()>
where
W: ?Sized + std::io::Write,
{
self.current_indent -= 1;
if self.has_value {
writer.write_all(b"\n")?;
indent(writer, self.current_indent, self.indent)?;
}
writer.write_all(b"}")
}
fn begin_object_key<W>(&mut self, writer: &mut W, first: bool) -> std::io::Result<()>
where
W: ?Sized + std::io::Write,
{
if first {
writer.write_all(b"\n")?;
} else {
writer.write_all(b",\n")?;
}
indent(writer, self.current_indent, self.indent)
}
fn begin_object_value<W>(&mut self, writer: &mut W) -> std::io::Result<()>
where
W: ?Sized + std::io::Write,
{
writer.write_all(b": ")
}
fn end_object_value<W>(&mut self, _writer: &mut W) -> std::io::Result<()>
where
W: ?Sized + std::io::Write,
{
self.has_value = true;
Ok(())
}
}

View file

@ -13,8 +13,10 @@ use clap::{Arg, ArgMatches, Command};
use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
pub mod cli;
pub mod exitcode;
pub mod getifaddrs;
pub mod jsonformatter;
pub mod localconfig;
pub mod utils;
pub mod vnic;
@ -83,6 +85,8 @@ Advanced Operations:
· untrust <root set name> Stop using a root set
· list List root sets in use
sign <path> <?identity secret> Sign a root set with an identity
verify <path> Load and verify a root set
marshal <path> Dump root set as binary to stdout
service Start local service
(usually not invoked manually)
@ -101,13 +105,6 @@ pub fn print_help() {
let _ = std::io::stdout().write_all(h.as_bytes());
}
pub struct GlobalCommandLineFlags {
pub json_output: bool,
pub base_path: String,
pub auth_token_path_override: Option<String>,
pub auth_token_override: Option<String>,
}
#[cfg(any(target_os = "macos"))]
pub fn platform_default_home_path() -> String {
"/Library/Application Support/ZeroTier".into()
@ -118,16 +115,16 @@ pub fn platform_default_home_path() -> String {
"/var/lib/zerotier".into()
}
async fn async_main(cli_args: Box<ArgMatches>) -> i32 {
let global_cli_flags = GlobalCommandLineFlags {
json_output: cli_args.is_present("json"),
base_path: cli_args.value_of("path").map_or_else(platform_default_home_path, |p| p.to_string()),
auth_token_path_override: cli_args.value_of("token_path").map(|p| p.to_string()),
auth_token_override: cli_args.value_of("token").map(|t| t.to_string()),
};
pub struct Flags {
pub json_output: bool,
pub base_path: String,
pub auth_token_path_override: Option<String>,
pub auth_token_override: Option<String>,
}
async fn async_main(flags: Flags, global_args: ArgMatches) -> i32 {
#[allow(unused)]
return match cli_args.subcommand() {
return match global_args.subcommand() {
Some(("help", _)) => {
print_help();
exitcode::OK
@ -137,29 +134,30 @@ async fn async_main(cli_args: Box<ArgMatches>) -> i32 {
exitcode::OK
}
Some(("status", _)) => todo!(),
Some(("set", args)) => todo!(),
Some(("peer", args)) => todo!(),
Some(("network", args)) => todo!(),
Some(("join", args)) => todo!(),
Some(("leave", args)) => todo!(),
Some(("set", cmd_args)) => todo!(),
Some(("peer", cmd_args)) => todo!(),
Some(("network", cmd_args)) => todo!(),
Some(("join", cmd_args)) => todo!(),
Some(("leave", cmd_args)) => todo!(),
Some(("service", _)) => todo!(),
Some(("identity", args)) => todo!(),
Some(("rootset", args)) => todo!(),
Some(("identity", cmd_args)) => todo!(),
Some(("rootset", cmd_args)) => cli::rootset::cmd(flags, cmd_args).await,
_ => {
print_help();
eprintln!("Invalid command line. Use 'help' for help.");
exitcode::ERR_USAGE
}
};
}
fn main() {
let cli_args = Box::new({
let global_args = {
let help = make_help();
Command::new("zerotier")
.arg(Arg::new("json").short('j'))
.arg(Arg::new("path").short('p').takes_value(true))
.arg(Arg::new("token_path").short('t').takes_value(true))
.arg(Arg::new("token").short('T').takes_value(true))
.subcommand_required(true)
.subcommand(Command::new("help"))
.subcommand(Command::new("version"))
.subcommand(Command::new("status"))
@ -198,7 +196,9 @@ fn main() {
.subcommand(Command::new("trust").arg(Arg::new("path").index(1).required(true)))
.subcommand(Command::new("untrust").arg(Arg::new("name").index(1).required(true)))
.subcommand(Command::new("list"))
.subcommand(Command::new("sign").arg(Arg::new("path").index(1).required(true)).arg(Arg::new("secret").index(2).required(true))),
.subcommand(Command::new("sign").arg(Arg::new("path").index(1).required(true)).arg(Arg::new("secret").index(2).required(true)))
.subcommand(Command::new("verify").arg(Arg::new("path").index(1).required(true)))
.subcommand(Command::new("marshal").arg(Arg::new("path").index(1).required(true))),
)
.override_help(help.as_str())
.override_usage("")
@ -207,7 +207,7 @@ fn main() {
.disable_help_flag(true)
.try_get_matches_from(std::env::args())
.unwrap_or_else(|e| {
if e.kind() == clap::ErrorKind::DisplayHelp {
if e.kind() == clap::ErrorKind::DisplayHelp || e.kind() == clap::ErrorKind::MissingSubcommand {
print_help();
std::process::exit(exitcode::OK);
} else {
@ -225,18 +225,25 @@ fn main() {
}
}
if invalid.is_empty() {
println!("Invalid command line. Use 'help' for help.");
eprintln!("Invalid command line. Use 'help' for help.");
} else {
if suggested.is_empty() {
println!("Unrecognized option '{}'. Use 'help' for help.", invalid);
eprintln!("Unrecognized option '{}'. Use 'help' for help.", invalid);
} else {
println!("Unrecognized option '{}', did you mean {}? Use 'help' for help.", invalid, suggested);
eprintln!("Unrecognized option '{}', did you mean {}? Use 'help' for help.", invalid, suggested);
}
}
std::process::exit(exitcode::ERR_USAGE);
}
})
});
};
std::process::exit(tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(async_main(cli_args)));
let flags = Flags {
json_output: global_args.is_present("json"),
base_path: global_args.value_of("path").map_or_else(platform_default_home_path, |p| p.to_string()),
auth_token_path_override: global_args.value_of("token_path").map(|p| p.to_string()),
auth_token_override: global_args.value_of("token").map(|t| t.to_string()),
};
std::process::exit(tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(async_main(flags, global_args)));
}

View file

@ -6,17 +6,20 @@
* https://www.zerotier.com/
*/
use std::error::Error;
use std::str::FromStr;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde::{Serialize, Serializer};
use lazy_static::lazy_static;
use tokio::fs::File;
use tokio::io::{AsyncRead, AsyncReadExt};
use crate::jsonformatter::JsonFormatter;
use zerotier_network_hypervisor::vl1::Identity;
lazy_static! {
@ -63,16 +66,6 @@ pub fn is_valid_port(v: &str) -> Result<(), String> {
Err(format!("invalid TCP/IP port number: {}", v))
}
/// Shortcut to use serde_json to serialize an object, returns "null" on error.
pub fn to_json<O: serde::Serialize>(o: &O) -> String {
serde_json::to_string(o).unwrap_or("null".into())
}
/// Shortcut to use serde_json to serialize an object, returns "null" on error.
pub fn to_json_pretty<O: serde::Serialize>(o: &O) -> String {
serde_json::to_string_pretty(o).unwrap_or("null".into())
}
/// Recursively patch a JSON object.
///
/// This is slightly different from a usual JSON merge. For objects in the target their fields
@ -131,6 +124,22 @@ pub fn json_patch_object<O: Serialize + DeserializeOwned + Eq>(obj: O, patch: &s
)
}
/// Shortcut to use serde_json to serialize an object, returns "null" on error.
pub fn to_json<O: serde::Serialize>(o: &O) -> String {
serde_json::to_string(o).unwrap_or("null".into())
}
/// Shortcut to use serde_json to serialize an object, returns "null" on error.
pub fn to_json_pretty<O: serde::Serialize>(o: &O) -> String {
let mut buf = Vec::new();
let mut ser = serde_json::Serializer::with_formatter(&mut buf, JsonFormatter::new());
if o.serialize(&mut ser).is_ok() {
String::from_utf8(buf).unwrap_or_else(|_| "null".into())
} else {
"null".into()
}
}
/// Convenience function to read up to limit bytes from a file.
///
/// If the file is larger than limit, the excess is not read.

View file

@ -0,0 +1,25 @@
{
"name": "root.zerotier.com",
"revision": 1,
"members": [ {
"identity": "62f865ae71:0:e2076c57de870e6288d7d5e7404408b1545efca37d67f77b87e9e54168c25d3ef1a9abf2905ea5e785c01dff23887ad4232d95c7a8fd2c27111a72bd159322dc",
"endpoints": [ "udp:50.7.252.138/9993", "udp:2001:49f0:d0db:2::2/9993" ],
"signature": [ 1, 45, 14, 211, 108, 240, 151, 85, 43, 241, 113, 18, 24, 45, 198, 197, 67, 254, 96, 138, 194, 77, 170, 156, 168, 31, 240, 55, 168, 108, 69, 135, 253, 198, 153, 36, 166, 200, 222, 157, 122, 50, 149, 149, 40, 35, 125, 93, 78, 228, 51, 245, 53, 238, 133, 84, 188, 190, 98, 145, 177, 19, 54, 154, 0 ],
"flags": 0
}, {
"identity": "778cde7190:0:3f6681a99e5ad1895e9fba33e6212d4454e168bcec7112101bf000956ed8e92e42892cb6f2ec410881a84ab19da50e1287ba3d926c3a1f755cccf299a1207055",
"endpoints": [ "udp:103.195.103.66/9993", "udp:2605:9880:400:c3:254:f2bc:a1f7:19/9993" ],
"signature": [ 1, 202, 181, 145, 69, 58, 169, 42, 149, 210, 160, 77, 220, 56, 246, 54, 210, 161, 144, 158, 103, 70, 104, 236, 58, 66, 127, 100, 117, 242, 208, 70, 68, 87, 142, 163, 222, 231, 146, 60, 205, 180, 202, 18, 181, 137, 216, 204, 109, 118, 224, 86, 220, 26, 142, 61, 18, 50, 174, 173, 44, 167, 231, 249, 0 ],
"flags": 0
}, {
"identity": "cafe04eba9:0:6c6a9d1dea55c1616bfe2a2b8f0ff9a8cacaf70374fb1f39e3bef81cbfebef17b7228268a0a2a29d3488c752565c6c965cbd6506ec24397cc8a5d9d15285a87f",
"endpoints": [ "udp:84.17.53.155/9993", "udp:2a02:6ea0:d405::9993/9993" ],
"signature": [ 1, 129, 31, 37, 249, 242, 179, 153, 184, 117, 15, 192, 41, 69, 112, 196, 189, 18, 57, 96, 33, 82, 31, 142, 57, 251, 151, 118, 86, 71, 11, 170, 197, 11, 20, 55, 74, 66, 10, 248, 133, 216, 88, 212, 34, 139, 128, 179, 246, 241, 8, 126, 105, 195, 126, 235, 140, 219, 66, 92, 166, 203, 111, 132, 0 ],
"flags": 0
}, {
"identity": "cafe9efeb9:0:ccdef76bc7b97ded904eabc5df09886d9c1514a610036cb9139cc214001a2958978efcec15712dd3948c6e6b3a8e893df01ff493d1f8d9806a860c5420571bf0",
"endpoints": [ "udp:104.194.8.134/9993", "udp:2605:9880:200:1200:30:571:e34:51/9993" ],
"signature": [ 1, 254, 236, 249, 244, 29, 229, 55, 85, 171, 15, 42, 222, 51, 237, 237, 47, 54, 158, 123, 96, 24, 101, 207, 63, 82, 113, 254, 154, 225, 188, 147, 75, 115, 243, 200, 253, 221, 198, 234, 74, 168, 126, 13, 137, 143, 13, 56, 73, 206, 242, 29, 97, 8, 221, 31, 236, 187, 86, 190, 15, 65, 184, 253, 13 ],
"flags": 0
} ]
}