diff options
Diffstat (limited to 'src/cli/roughtime.cpp')
-rw-r--r-- | src/cli/roughtime.cpp | 215 |
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 |