/* * Roughtime * (C) 2019 Nuno Goncalves * * Botan is released under the Simplified BSD License (see license.txt) */ #include "cli.h" #if defined(BOTAN_HAS_ROUGHTIME) #include #include #include #include #include #include #include #include #include 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= List of servers that will queried in sequence. File contents syntax: 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= 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 . File contents syntax: )"; } std::string group() const override { return "misc"; } std::string description() const override { return "Retrieve time from Roughtime server"; } void query(std::unique_ptr& 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 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