Revision control

Copy as Markdown

Other Tools

/*
* Roughtime
* (C) 2019 Nuno Goncalves <nunojpg@gmail.com>
*
* 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