• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 39195077976

18 Oct 2026 05:18PM UTC coverage: 92.258% (+0.06%) from 92.195%
39195077976

push

github

randombit
Update release notes [ci skip]

137358 of 148884 relevant lines covered (92.26%)

8967647.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.22
/src/cli/roughtime.cpp
1
/*
2
* Roughtime
3
* (C) 2019 Nuno Goncalves <nunojpg@gmail.com>
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "cli.h"
9

10
#if defined(BOTAN_HAS_ROUGHTIME)
11

12
   #include <botan/base64.h>
13
   #include <botan/ed25519.h>
14
   #include <botan/roughtime.h>
15
   #include <botan/internal/calendar.h>
16

17
   #include <fstream>
18
   #include <iomanip>
19
   #include <sstream>
20

21
namespace Botan_CLI {
22

23
namespace {
24

25
// Format the time point as YYYY-MM-DDTHH:MM:SS in UTC
26
std::string format_utc_datetime(const std::chrono::system_clock::time_point& tp) {
8 ✔
27
   const Botan::calendar_point c(tp);
8 ✔
28
   std::ostringstream out;
8 ✔
29
   out << std::setfill('0') << std::setw(4) << c.year() << "-" << std::setw(2) << c.month() << "-" << std::setw(2)
8 ✔
30
       << c.day() << "T" << std::setw(2) << c.hour() << ":" << std::setw(2) << c.minutes() << ":" << std::setw(2)
8 ✔
31
       << c.seconds();
8 ✔
32
   return out.str();
16 ✔
33
}
8 ✔
34

35
class RoughtimeCheck final : public Command {
36
   public:
37
      RoughtimeCheck() : Command("roughtime_check --raw-time chain-file") {}
8 ✔
38

39
      std::string group() const override { return "misc"; }
1 ✔
40

41
      std::string description() const override { return "Parse and validate Roughtime chain file"; }
1 ✔
42

43
      void go() override {
3 ✔
44
         const auto chain = Botan::Roughtime::Chain(slurp_file_as_str(get_arg("chain-file")));
8 ✔
45
         unsigned i = 0;
2 ✔
46
         for(const auto& response : chain.responses()) {
8 ✔
47
            output() << std::setw(3) << ++i << ": UTC ";
6 ✔
48
            if(flag_set("raw-time")) {
6 ✔
49
               output()
1 ✔
50
                  << Botan::Roughtime::Response::sys_microseconds64(response.utc_midpoint()).time_since_epoch().count();
1 ✔
51
            } else {
52
               output() << format_utc_datetime(response.utc_midpoint());
10 ✔
53
            }
54
            output() << " (+-" << Botan::Roughtime::Response::microseconds32(response.utc_radius()).count() << "us)\n";
6 ✔
55
         }
2 ✔
56
      }
2 ✔
57
};
58

59
BOTAN_REGISTER_COMMAND("roughtime_check", RoughtimeCheck);
4 ✔
60

61
class Roughtime final : public Command {
62
   public:
63
      Roughtime() :
9 ✔
64
            Command(
65
               "roughtime --raw-time --chain-file=roughtime-chain --max-chain-size=128 --check-local-clock=60 --host= --pubkey= --servers-file=") {
9 ✔
66
      }
9 ✔
67

68
      std::string help_text() const override {
×
69
         return Command::help_text() + R"(
×
70

71
--servers-file=<filename>
72
   List of servers that will queried in sequence.
73

74
   File contents syntax:
75
      <name> <key type> <base 64 encoded public key> <protocol> <host:port>
76

77
   Example servers:
78
      Cloudflare-Roughtime ed25519 0GD7c3yP8xEc4Zl2zeuN2SlLvDVVocjsPSL8/Rl/7zg= udp roughtime.cloudflare.com:2003
79
      Google-Sandbox-Roughtime ed25519 etPaaIxcBMY1oUeGpwvPMCJMwlRVNxv51KK/tktoJTQ= udp roughtime.sandbox.google.com:2002
80

81
--chain-file=<filename>
82
   Successful queries are appended to this file.
83
   If limit of --max-chain-size records is reached, the oldest records are truncated.
84
   This queries records can be replayed using command roughtime_check <chain-file>.
85

86
   File contents syntax:
87
      <key type> <base 64 encoded public key> <base 64 encoded blind or nonce> <base 64 encoded server response>
88
)";
89
      }
90

91
      std::string group() const override { return "misc"; }
1 ✔
92

93
      std::string description() const override { return "Retrieve time from Roughtime server"; }
1 ✔
94

95
      /*
96
      * Query one server. Returns true if an authenticated response was
97
      * received; otherwise the failure is reported and false returned.
98
      */
99
      bool query(std::unique_ptr<Botan::Roughtime::Chain>& chain,
6 ✔
100
                 const size_t max_chain_size,
101
                 const std::string& address,
102
                 const Botan::Ed25519_PublicKey& public_key,
103
                 const std::string& prefix) {
104
         Botan::Roughtime::Nonce nonce{};
6 ✔
105
         Botan::Roughtime::Nonce blind{};
6 ✔
106
         if(chain) {
6 ✔
107
            blind = Botan::Roughtime::Nonce(rng());
3 ✔
108
            nonce = chain->next_nonce(blind);
3 ✔
109
         } else {
110
            nonce = Botan::Roughtime::Nonce(rng());
3 ✔
111
         }
112
         const auto response_raw = Botan::Roughtime::online_request(address, nonce, std::chrono::seconds(5));
6 ✔
113
         const auto response = Botan::Roughtime::Response::from_bits(response_raw, nonce);
6 ✔
114

115
         // Nothing from the response reaches stdout until it has been authenticated
116
         if(!response.validate(public_key)) {
5 ✔
117
            error_output() << "ERROR: Public key does not match!\n";
1 ✔
118
            set_return_code(1);
1 ✔
119
            return false;
1 ✔
120
         }
121

122
         std::ostringstream line;
4 ✔
123
         line << prefix << "UTC ";
4 ✔
124
         if(flag_set("raw-time")) {
4 ✔
125
            line << Botan::Roughtime::Response::sys_microseconds64(response.utc_midpoint()).time_since_epoch().count();
1 ✔
126
         } else {
127
            line << format_utc_datetime(response.utc_midpoint());
6 ✔
128
         }
129
         line << " (+-" << Botan::Roughtime::Response::microseconds32(response.utc_radius()).count() << "us)";
4 ✔
130

131
         const auto tolerance = get_arg_sz("check-local-clock");
4 ✔
132
         if(tolerance > 0) {
4 ✔
133
            const auto now = std::chrono::system_clock::now();
1 ✔
134
            const auto diff_abs =
1 ✔
135
               now >= response.utc_midpoint() ? now - response.utc_midpoint() : response.utc_midpoint() - now;
1 ✔
136
            if(diff_abs > (response.utc_radius() + std::chrono::seconds(tolerance))) {
1 ✔
137
               error_output() << "ERROR: Local clock mismatch\n";
1 ✔
138
               set_return_code(1);
1 ✔
139
               return false;
1 ✔
140
            }
141
            line << " Local clock match";
×
142
         }
143
         if(chain) {
3 ✔
144
            chain->append({response_raw, public_key, blind}, max_chain_size);
3 ✔
145
         }
146
         output() << line.str() << '\n';
3 ✔
147
         return true;
3 ✔
148
      }
9 ✔
149

150
      void go() override {
8 ✔
151
         const auto max_chain_size = get_arg_sz("max-chain-size");
8 ✔
152
         const auto chain_file = get_arg("chain-file");
8 ✔
153
         const auto servers_file = get_arg_or("servers-file", "");
17 ✔
154
         const auto host = get_arg_or("host", "");
17 ✔
155
         const auto pk = get_arg_or("pubkey", "");
17 ✔
156

157
         std::unique_ptr<Botan::Roughtime::Chain> chain;
8 ✔
158
         if(!chain_file.empty() && max_chain_size >= 1) {
8 ✔
159
            try {
4 ✔
160
               chain = std::make_unique<Botan::Roughtime::Chain>(slurp_file_as_str(chain_file));
6 ✔
161
            } catch(const CLI_IO_Error&) {
2 ✔
162
               // file is to still be created
163
               chain = std::make_unique<Botan::Roughtime::Chain>();
4 ✔
164
            }
2 ✔
165
         }
166

167
         const bool from_servers_file = !servers_file.empty();
8 ✔
168
         const bool from_host_and_pk = !host.empty() && !pk.empty();
8 ✔
169
         if(from_servers_file == from_host_and_pk) {
8 ✔
170
            error_output() << "Please specify either --servers-file or --host and --pubkey\n";
1 ✔
171
            set_return_code(1);
1 ✔
172
            return;
1 ✔
173
         }
174

175
         if(!servers_file.empty()) {
7 ✔
176
            const auto servers = Botan::Roughtime::servers_from_str(slurp_file_as_str(servers_file));
6 ✔
177

178
            for(const auto& s : servers) {
8 ✔
179
               std::ostringstream prefix;
4 ✔
180
               prefix << std::setw(25) << std::left << s.name() << ": ";
4 ✔
181

182
               bool server_ok = false;
4 ✔
183
               for(const auto& a : s.addresses()) {
5 ✔
184
                  try {
4 ✔
185
                     server_ok = query(chain, max_chain_size, a, s.public_key(), prefix.str());
5 ✔
186
                     break;
3 ✔
187
                  } catch(const std::exception& ex)  //network error, try next address
1 ✔
188
                  {
189
                     error_output() << ex.what() << '\n';
1 ✔
190
                  }
1 ✔
191
               }
192

193
               if(!server_ok) {
4 ✔
194
                  set_return_code(1);
3 ✔
195
               }
196
            }
4 ✔
197

198
         } else {
4 ✔
199
            query(chain, max_chain_size, host, Botan::Ed25519_PublicKey(Botan::base64_decode(pk)), "");
4 ✔
200
         }
201

202
         if(chain) {
6 ✔
203
            std::ofstream out(chain_file);
3 ✔
204
            out << chain->to_string();
6 ✔
205
         }
3 ✔
206
      }
11 ✔
207
};
208

209
BOTAN_REGISTER_COMMAND("roughtime", Roughtime);
9 ✔
210

211
}  // namespace
212

213
}  // namespace Botan_CLI
214

215
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc