aboutsummaryrefslogtreecommitdiffstats
path: root/src/cli
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2019-10-16 09:38:25 -0400
committerJack Lloyd <[email protected]>2019-10-16 09:38:25 -0400
commit5261274ea193f64deb27e88ed57d0830d8815913 (patch)
tree3921b8376c11dd4d04f4bce82a8c08236bfa6af4 /src/cli
parent26354d668b6b1c27aa40ea2dd13ff0dffe09134d (diff)
parent760419ef719721fd8ee8d7d8bb77d27bd66801fa (diff)
Merge GH #2143 Add Roughtime
Diffstat (limited to 'src/cli')
-rw-r--r--src/cli/roughtime.cpp215
1 files changed, 215 insertions, 0 deletions
diff --git a/src/cli/roughtime.cpp b/src/cli/roughtime.cpp
new file mode 100644
index 000000000..ff38fe1c4
--- /dev/null
+++ b/src/cli/roughtime.cpp
@@ -0,0 +1,215 @@
+/*
+* Roughtime
+* (C) 2019 Nuno Goncalves <[email protected]>
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include "cli.h"
+
+#if defined(BOTAN_HAS_ROUGHTIME)
+
+#include <botan/roughtime.h>
+#include <botan/hex.h>
+#include <botan/rng.h>
+#include <botan/base64.h>
+#include <botan/ed25519.h>
+#include <botan/hash.h>
+#include <botan/calendar.h>
+
+#include <iomanip>
+#include <fstream>
+
+namespace Botan_CLI {
+
+class RoughtimeCheck final : public Command
+ {
+ public:
+ RoughtimeCheck() : Command("roughtime_check --raw-time chain-file") {}
+
+ std::string group() const override
+ {
+ return "misc";
+ }
+
+ std::string description() const override
+ {
+ return "Parse and validate Roughtime chain file";
+ }
+
+ void go() override
+ {
+ const auto chain = Botan::Roughtime::Chain(slurp_file_as_str(get_arg("chain-file")));
+ unsigned i = 0;
+ for(const auto& response : chain.responses())
+ {
+ output() << std::setw(3) << ++i << ": UTC ";
+ if(flag_set("raw-time"))
+ { output() << Botan::Roughtime::Response::sys_microseconds64(response.utc_midpoint()).time_since_epoch().count(); }
+ else
+ { output() << Botan::calendar_value(response.utc_midpoint()).to_string(); }
+ output() << " (+-" << Botan::Roughtime::Response::microseconds32(response.utc_radius()).count() << "us)\n";
+ }
+ }
+ };
+
+BOTAN_REGISTER_COMMAND("roughtime_check", RoughtimeCheck);
+
+class Roughtime final : public Command
+ {
+ public:
+ Roughtime() :
+ Command("roughtime --raw-time --chain-file=roughtime-chain --max-chain-size=128 --check-local-clock=60 --host= --pubkey= --servers-file=") {}
+
+ std::string help_text() const override
+ {
+ return Command::help_text() + R"(
+
+--servers-file=<filename>
+ List of servers that will queried in sequence.
+
+ File contents syntax:
+ <name> <key type> <base 64 encoded public key> <protocol> <host:port>
+
+ Example servers:
+ Cloudflare-Roughtime ed25519 gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo= udp roughtime.cloudflare.com:2002
+ Google-Sandbox-Roughtime ed25519 etPaaIxcBMY1oUeGpwvPMCJMwlRVNxv51KK/tktoJTQ= udp roughtime.sandbox.google.com:2002
+
+--chain-file=<filename>
+ Succesfull queries are appended to this file.
+ If limit of --max-chain-size records is reached, the oldest records are truncated.
+ This queries records can be replayed using command roughtime_check <chain-file>.
+
+ File contents syntax:
+ <key type> <base 64 encoded public key> <base 64 encoded blind or nonce> <base 64 encoded server response>
+)";
+ }
+
+ std::string group() const override
+ {
+ return "misc";
+ }
+
+ std::string description() const override
+ {
+ return "Retrieve time from Roughtime server";
+ }
+
+ void query(std::unique_ptr<Botan::Roughtime::Chain>& chain,
+ const size_t max_chain_size,
+ const std::string& address,
+ const Botan::Ed25519_PublicKey& public_key)
+ {
+ Botan::Roughtime::Nonce nonce;
+ Botan::Roughtime::Nonce blind;
+ if(chain)
+ {
+ blind = Botan::Roughtime::Nonce(rng());
+ nonce = chain->next_nonce(blind);
+ }
+ else
+ {
+ nonce = Botan::Roughtime::Nonce(rng());
+ }
+ const auto response_raw = Botan::Roughtime::online_request(address, nonce, std::chrono::seconds(5));
+ const auto response = Botan::Roughtime::Response::from_bits(response_raw, nonce);
+ if(flag_set("raw-time"))
+ { output() << "UTC " << Botan::Roughtime::Response::sys_microseconds64(response.utc_midpoint()).time_since_epoch().count(); }
+ else
+ { output() << "UTC " << Botan::calendar_value(response.utc_midpoint()).to_string(); }
+ output() << " (+-" << Botan::Roughtime::Response::microseconds32(response.utc_radius()).count() << "us)";
+ if(!response.validate(public_key))
+ {
+ error_output() << "ERROR: Public key does not match!\n";
+ set_return_code(1);
+ return;
+ }
+ const auto tolerance = get_arg_sz("check-local-clock");
+ if(tolerance)
+ {
+ const auto now = std::chrono::system_clock::now();
+ const auto diff_abs = now >= response.utc_midpoint() ? now - response.utc_midpoint() : response.utc_midpoint() - now;
+ if(diff_abs > (response.utc_radius() + std::chrono::seconds(tolerance)))
+ {
+ error_output() << "ERROR: Local clock mismatch\n";
+ set_return_code(1);
+ return;
+ }
+ output() << " Local clock match";
+ }
+ if(chain)
+ chain->append({response_raw, public_key, blind}, max_chain_size);
+ output() << '\n';
+ }
+
+ void go() override
+ {
+
+ const auto max_chain_size = get_arg_sz("max-chain-size");
+ const auto chain_file = get_arg("chain-file");
+ const auto servers_file = get_arg_or("servers-file", "");
+ const auto host = get_arg_or("host", "");
+ const auto pk = get_arg_or("pubkey", "");
+
+ std::unique_ptr<Botan::Roughtime::Chain> chain;
+ if(!chain_file.empty() && max_chain_size >= 1)
+ {
+ try
+ {
+ chain.reset(new Botan::Roughtime::Chain(slurp_file_as_str(chain_file)));
+ }
+ catch(const CLI_IO_Error&)
+ {
+ chain.reset(new Botan::Roughtime::Chain()); //file is to still be created
+ }
+ }
+
+ const bool from_servers_file = !servers_file.empty();
+ const bool from_host_and_pk = !host.empty() && !pk.empty();
+ if(from_servers_file == from_host_and_pk)
+ {
+ error_output() << "Please specify either --servers-file or --host and --pubkey\n";
+ set_return_code(1);
+ return;
+ }
+
+ if(!servers_file.empty())
+ {
+ const auto servers = Botan::Roughtime::servers_from_str(slurp_file_as_str(servers_file));
+
+ for(const auto& s : servers)
+ {
+ output() << std::setw(25) << std::left << s.name() << ": ";
+ for(const auto& a : s.addresses())
+ {
+ try
+ {
+ query(chain, max_chain_size, a, s.public_key());
+ break;
+ }
+ catch(const std::exception& ex) //network error, try next address
+ {
+ error_output() << ex.what() << '\n';
+ }
+ }
+ }
+
+ }
+ else
+ {
+ query(chain, max_chain_size, host, Botan::Ed25519_PublicKey(Botan::base64_decode(pk)));
+ }
+
+ if(chain)
+ {
+ std::ofstream out(chain_file);
+ out << chain->to_string();
+ }
+ }
+ };
+
+BOTAN_REGISTER_COMMAND("roughtime", Roughtime);
+
+}
+
+#endif