1 /*
2 * (C) 2019 Nuno Goncalves <nunojpg@gmail.com>
3 *
4 * Botan is released under the Simplified BSD License (see license.txt)
5 */
6 
7 #include "tests.h"
8 
9 #include <cassert>
10 
11 #include "test_rng.h"
12 
13 #if defined(BOTAN_HAS_BIGINT)
14    #include <botan/bigint.h>
15 #endif
16 
17 #if defined(BOTAN_HAS_ROUGHTIME)
18    #include <botan/base64.h>
19    #include <botan/ed25519.h>
20    #include <botan/hex.h>
21    #include <botan/roughtime.h>
22 #endif
23 namespace Botan_Tests {
24 
25 #if defined(BOTAN_HAS_ROUGHTIME)
26 
27 class Roughtime_Request_Tests final : public Text_Based_Test
28    {
29    public:
Roughtime_Request_Tests()30       Roughtime_Request_Tests() :
31          Text_Based_Test("misc/roughtime_request.vec", "Nonce,Request") {}
32 
run_one_test(const std::string & type,const VarMap & vars)33       Test::Result run_one_test(const std::string& type, const VarMap& vars) override
34          {
35          Test::Result result("roughtime request");
36 
37          const auto nonce = vars.get_req_bin("Nonce");
38          const auto request_v = vars.get_req_bin("Request");
39 
40          const auto request = Botan::Roughtime::encode_request(nonce);
41          result.test_eq(
42             "encode",
43             type == "Valid",
44             request == Botan::typecast_copy<std::array<uint8_t, 1024>>(request_v.data()));
45 
46          return result;
47          }
48    };
49 
50 BOTAN_REGISTER_TEST("roughtime", "roughtime_request", Roughtime_Request_Tests);
51 
52 
53 class Roughtime_Response_Tests final : public Text_Based_Test
54    {
55    public:
Roughtime_Response_Tests()56       Roughtime_Response_Tests() :
57          Text_Based_Test("misc/roughtime_response.vec",
58                          "Response",
59                          "Nonce,Pubkey,MidpointMicroSeconds,RadiusMicroSeconds") {}
60 
run_one_test(const std::string & type,const VarMap & vars)61       Test::Result run_one_test(const std::string& type, const VarMap& vars) override
62          {
63          Test::Result result("roughtime response");
64 
65          const auto response_v = vars.get_req_bin("Response");
66          const auto n = vars.has_key("Nonce") ? vars.get_req_bin("Nonce") : std::vector<uint8_t>(64);
67          assert(n.size() == 64);
68          const Botan::Roughtime::Nonce nonce(n);
69          try
70             {
71             const auto response = Botan::Roughtime::Response::from_bits(response_v, nonce);
72 
73             const auto pubkey = vars.get_req_bin("Pubkey");
74             assert(pubkey.size() == 32);
75 
76             if(!response.validate(Botan::Ed25519_PublicKey(pubkey)))
77                {
78                result.confirm("fail_validation", type == "Invalid");
79                }
80             else
81                {
82                const auto midpoint = Botan::Roughtime::Response::sys_microseconds64(
83                                         std::chrono::microseconds(
84                                            vars.get_req_u64("MidpointMicroSeconds")));
85                const auto radius = std::chrono::microseconds(
86                                       vars.get_req_u32("RadiusMicroSeconds"));
87 
88                result.confirm("midpoint", response.utc_midpoint() == midpoint);
89                result.confirm("radius", response.utc_radius() == radius);
90                result.confirm("OK", type == "Valid");
91                }
92             }
93          catch(const Botan::Roughtime::Roughtime_Error& e)
94             {
95             result.confirm(e.what(), type == "Invalid");
96             }
97 
98          return result;
99          }
100    };
101 
102 BOTAN_REGISTER_TEST("roughtime", "roughtime_response", Roughtime_Response_Tests);
103 
104 class Roughtime_nonce_from_blind_Tests final : public Text_Based_Test
105    {
106    public:
Roughtime_nonce_from_blind_Tests()107       Roughtime_nonce_from_blind_Tests() :
108          Text_Based_Test("misc/roughtime_nonce_from_blind.vec", "Response,Blind,Nonce") {}
109 
run_one_test(const std::string & type,const VarMap & vars)110       Test::Result run_one_test(const std::string& type, const VarMap& vars) override
111          {
112          Test::Result result("roughtime nonce_from_blind");
113 
114          const auto response = vars.get_req_bin("Response");
115          const auto blind = vars.get_req_bin("Blind");
116          const auto nonce = vars.get_req_bin("Nonce");
117 
118          result.test_eq("fail_validation",
119                         Botan::Roughtime::nonce_from_blind(response, blind) == nonce,
120                         type == "Valid");
121 
122          return result;
123          }
124    };
125 
126 BOTAN_REGISTER_TEST("roughtime", "roughtime_nonce_from_blind", Roughtime_nonce_from_blind_Tests);
127 
128 
129 
130 class Roughtime final : public Test
131    {
test_nonce()132       Test::Result test_nonce()
133          {
134          Test::Result result("roughtime nonce");
135 
136          auto rand64 = Botan::unlock(Test::rng().random_vec(64));;
137          Botan::Roughtime::Nonce nonce_v(rand64);
138          result.confirm("nonce from vector", nonce_v.get_nonce() == Botan::typecast_copy<std::array<uint8_t, 64>>
139                         (rand64.data()));
140          Botan::Roughtime::Nonce nonce_a(Botan::typecast_copy<std::array<uint8_t, 64>>(rand64.data()));
141          result.confirm("nonce from array", nonce_v.get_nonce() == Botan::typecast_copy<std::array<uint8_t, 64>>(rand64.data()));
142          rand64.push_back(10);
143          result.test_throws("vector oversize", [&rand64]() {Botan::Roughtime::Nonce nonce_v2(rand64);}); //size 65
144          rand64.pop_back();
145          rand64.pop_back();
146          result.test_throws("vector undersize", [&rand64]() {Botan::Roughtime::Nonce nonce_v2(rand64);}); //size 63
147 
148          return result;
149          }
150 
test_chain()151       Test::Result test_chain()
152          {
153          Test::Result result("roughtime chain");
154 
155          Botan::Roughtime::Chain c1;
156          result.confirm("default constructed is empty", c1.links().empty() && c1.responses().empty());
157 
158          auto rand64 = Botan::unlock(Test::rng().random_vec(64));;
159          Botan::Roughtime::Nonce nonce_v(rand64);
160          result.confirm("empty chain nonce is blind",
161                         c1.next_nonce(nonce_v).get_nonce() == Botan::typecast_copy<std::array<uint8_t, 64>>(rand64.data()));
162 
163          const std::string chain_str =
164             "ed25519 bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= eu9yhsJfVfguVSqGZdE8WKIxaBBM0ZG3Vmuc+IyZmG2YVmrIktUByDdwIFw6F4rZqmSFsBO85ljoVPz5bVPCOw== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWBnGOEajOwPA6G7oL47seBP4C7eEpr57H43C2/fK/kMA0UGZVUdf4KNX8oxOK6JIcsbVk8qhghTwA70qtwpYmQkDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AJrA8tEqPBQAqisiuAxgy2Pj7UJAiWbCdzGz1xcCnja3T+AqhC8fwpeIwW4GPy/vEb/awXW2DgSLKJfzWIAz+2lsR7t4UjNPvAgAAAEAAAABTSUcAREVMRes9Ch4X0HIw5KdOTB8xK4VDFSJBD/G9t7Et/CU7UW61OiTBXYYQTG2JekWZmGa0OHX1JPGG+APkpbsNw0BKUgYDAAAAIAAAACgAAABQVUJLTUlOVE1BWFR/9BWjpsWTQ1f6iUJea3EfZ1MkX3ftJiV3ABqNLpncFwAAAAAAAAAA//////////8AAAAA\n"
165             "ed25519 gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo= uLeTON9D+2HqJMzK6sYWLNDEdtBl9t/9yw1cVAOm0/sONH5Oqdq9dVPkC9syjuWbglCiCPVF+FbOtcxCkrgMmA== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWOw1jl0uSiBEH9HE8/6r7zxoSc01f48vw+UzH8+VJoPelnvVJBj4lnH8uRLh5Aw0i4Du7XM1dp2u0r/I5PzhMQoDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AUBo+tEqPBQC47l77to7ESFTVhlw1SC74P5ssx6gpuJ6eP+1916GuUiySGE/x3Fp0c3otUGAdsRQou5p9PDTeane/YEeVq4/8AgAAAEAAAABTSUcAREVMRe5T1ml8wHyWAcEtHP/U5Rg/jFXTEXOSglngSa4aI/CECVdy4ZNWeP6vv+2//ZW7lQsrWo7ZkXpvm9BdBONRSQIDAAAAIAAAACgAAABQVUJLTUlOVE1BWFQpXlenV0OfVisvp9jDHXLw8vymZVK9Pgw9k6Edf8ZEhUgSGEc5jwUASHLvZE2PBQAAAAAA\n";
166 
167          Botan::Roughtime::Chain c2(chain_str);
168          result.confirm("have two elements", c2.links().size() == 2 && c2.responses().size() == 2);
169          result.confirm("serialize loopback", c2.to_string() == chain_str);
170 
171          c1.append(c2.links()[0], 1);
172          result.confirm("append ok", c1.links().size() == 1 && c1.responses().size() == 1);
173          c1.append(c2.links()[1], 1);
174          result.confirm("max size", c1.links().size() == 1 && c1.responses().size() == 1);
175 
176          result.test_throws("non-positive max chain size", [&]() {c1.append(c2.links()[1], 0);});
177          result.test_throws("1 field", [&]() {Botan::Roughtime::Chain a("ed25519");});
178          result.test_throws("2 fields", [&]() {Botan::Roughtime::Chain a("ed25519 bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE=");});
179          result.test_throws("3 fields", [&]() {Botan::Roughtime::Chain a("ed25519 bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= eu9yhsJfVfguVSqGZdE8WKIxaBBM0ZG3Vmuc+IyZmG2YVmrIktUByDdwIFw6F4rZqmSFsBO85ljoVPz5bVPCOw==");});
180          result.test_throws("5 fields", [&]() {Botan::Roughtime::Chain a("ed25519 bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= eu9yhsJfVfguVSqGZdE8WKIxaBBM0ZG3Vmuc+IyZmG2YVmrIktUByDdwIFw6F4rZqmSFsBO85ljoVPz5bVPCOw== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWBnGOEajOwPA6G7oL47seBP4C7eEpr57H43C2/fK/kMA0UGZVUdf4KNX8oxOK6JIcsbVk8qhghTwA70qtwpYmQkDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AJrA8tEqPBQAqisiuAxgy2Pj7UJAiWbCdzGz1xcCnja3T+AqhC8fwpeIwW4GPy/vEb/awXW2DgSLKJfzWIAz+2lsR7t4UjNPvAgAAAEAAAABTSUcAREVMRes9Ch4X0HIw5KdOTB8xK4VDFSJBD/G9t7Et/CU7UW61OiTBXYYQTG2JekWZmGa0OHX1JPGG+APkpbsNw0BKUgYDAAAAIAAAACgAAABQVUJLTUlOVE1BWFR/9BWjpsWTQ1f6iUJea3EfZ1MkX3ftJiV3ABqNLpncFwAAAAAAAAAA//////////8AAAAA abc");});
181          result.test_throws("invalid key type", [&]() {Botan::Roughtime::Chain a("rsa bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= eu9yhsJfVfguVSqGZdE8WKIxaBBM0ZG3Vmuc+IyZmG2YVmrIktUByDdwIFw6F4rZqmSFsBO85ljoVPz5bVPCOw== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWBnGOEajOwPA6G7oL47seBP4C7eEpr57H43C2/fK/kMA0UGZVUdf4KNX8oxOK6JIcsbVk8qhghTwA70qtwpYmQkDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AJrA8tEqPBQAqisiuAxgy2Pj7UJAiWbCdzGz1xcCnja3T+AqhC8fwpeIwW4GPy/vEb/awXW2DgSLKJfzWIAz+2lsR7t4UjNPvAgAAAEAAAABTSUcAREVMRes9Ch4X0HIw5KdOTB8xK4VDFSJBD/G9t7Et/CU7UW61OiTBXYYQTG2JekWZmGa0OHX1JPGG+APkpbsNw0BKUgYDAAAAIAAAACgAAABQVUJLTUlOVE1BWFR/9BWjpsWTQ1f6iUJea3EfZ1MkX3ftJiV3ABqNLpncFwAAAAAAAAAA//////////8AAAAA");});
182          result.test_throws("invalid key", [&]() {Botan::Roughtime::Chain a("ed25519 bbT+RPS7zKX6wssPibzmwWqU9ffRV5oj2OresSmhE= eu9yhsJfVfguVSqGZdE8WKIxaBBM0ZG3Vmuc+IyZmG2YVmrIktUByDdwIFw6F4rZqmSFsBO85ljoVPz5bVPCOw== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWBnGOEajOwPA6G7oL47seBP4C7eEpr57H43C2/fK/kMA0UGZVUdf4KNX8oxOK6JIcsbVk8qhghTwA70qtwpYmQkDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AJrA8tEqPBQAqisiuAxgy2Pj7UJAiWbCdzGz1xcCnja3T+AqhC8fwpeIwW4GPy/vEb/awXW2DgSLKJfzWIAz+2lsR7t4UjNPvAgAAAEAAAABTSUcAREVMRes9Ch4X0HIw5KdOTB8xK4VDFSJBD/G9t7Et/CU7UW61OiTBXYYQTG2JekWZmGa0OHX1JPGG+APkpbsNw0BKUgYDAAAAIAAAACgAAABQVUJLTUlOVE1BWFR/9BWjpsWTQ1f6iUJea3EfZ1MkX3ftJiV3ABqNLpncFwAAAAAAAAAA//////////8AAAAA");});
183          result.test_throws("invalid nonce", [&]() {Botan::Roughtime::Chain a("ed25519 bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= eu9yhsJfVfguVSqGZdE8WKIxaBBM0ZG3Vmuc+IyZmG2UByDdwIFw6F4rZqmSFsBO85ljoVPz5bVPCOw== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWBnGOEajOwPA6G7oL47seBP4C7eEpr57H43C2/fK/kMA0UGZVUdf4KNX8oxOK6JIcsbVk8qhghTwA70qtwpYmQkDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AJrA8tEqPBQAqisiuAxgy2Pj7UJAiWbCdzGz1xcCnja3T+AqhC8fwpeIwW4GPy/vEb/awXW2DgSLKJfzWIAz+2lsR7t4UjNPvAgAAAEAAAABTSUcAREVMRes9Ch4X0HIw5KdOTB8xK4VDFSJBD/G9t7Et/CU7UW61OiTBXYYQTG2JekWZmGa0OHX1JPGG+APkpbsNw0BKUgYDAAAAIAAAACgAAABQVUJLTUlOVE1BWFR/9BWjpsWTQ1f6iUJea3EfZ1MkX3ftJiV3ABqNLpncFwAAAAAAAAAA//////////8AAAAA");});
184 
185          return result;
186          }
187 
test_server_information()188       Test::Result test_server_information()
189          {
190          Test::Result result("roughtime server_information");
191 
192          const auto servers = Botan::Roughtime::servers_from_str(
193                                  "Chainpoint-Roughtime ed25519 bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= udp roughtime.chainpoint.org:2002\n"
194                                  "Cloudflare-Roughtime ed25519 gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo= udp roughtime.cloudflare.com:2002\n"
195                                  "Google-Sandbox-Roughtime ed25519 etPaaIxcBMY1oUeGpwvPMCJMwlRVNxv51KK/tktoJTQ= udp roughtime.sandbox.google.com:2002\n"
196                                  "int08h-Roughtime ed25519 AW5uAoTSTDfG5NfY1bTh08GUnOqlRb+HVhbJ3ODJvsE= udp roughtime.int08h.com:2002\n"
197                                  "ticktock ed25519 cj8GsiNlRkqiDElAeNMSBBMwrAl15hYPgX50+GWX/lA= udp ticktock.mixmin.net:5333\n"
198                               );
199 
200          result.confirm("size", servers.size() == 5);
201          result.test_eq("name", servers[0].name(), "Chainpoint-Roughtime");
202          result.test_eq("name", servers[4].name(), "ticktock");
203          result.confirm("public key", servers[0].public_key().get_public_key() == Botan::Ed25519_PublicKey(
204                            Botan::base64_decode("bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE=")).get_public_key());
205          result.confirm("single address", servers[0].addresses().size()==1);
206          result.test_eq("address", servers[0].addresses()[0], "roughtime.chainpoint.org:2002");
207 
208          result.test_throws("1 field", [&]() {Botan::Roughtime::servers_from_str("A");});
209          result.test_throws("2 fields", [&]() {Botan::Roughtime::servers_from_str("A ed25519");});
210          result.test_throws("3 fields", [&]() {Botan::Roughtime::servers_from_str("A ed25519 bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE=");});
211          result.test_throws("4 fields", [&]() {Botan::Roughtime::servers_from_str("A ed25519 bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= udp");});
212          result.test_throws("invalid address", [&]() {Botan::Roughtime::servers_from_str("A ed25519 bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= udp ");});
213          result.test_throws("invalid key type", [&]() {Botan::Roughtime::servers_from_str("A rsa bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= udp roughtime.chainpoint.org:2002");});
214          result.test_throws("invalid key", [&]() {Botan::Roughtime::servers_from_str("A ed25519 bbT+RP7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= udp roughtime.chainpoint.org:2002");});
215          result.test_throws("invalid protocol", [&]() {Botan::Roughtime::servers_from_str("A ed25519 bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= tcp roughtime.chainpoint.org:2002");});
216 
217          return result;
218          }
219 
test_request_online()220       Test::Result test_request_online()
221          {
222          Test::Result result("roughtime request online");
223 
224          Botan::Roughtime::Nonce nonce(Test::rng());
225          try
226             {
227             const auto response_raw = Botan::Roughtime::online_request("roughtime.cloudflare.com:2002", nonce,
228                                       std::chrono::seconds(5));
229             const auto now = std::chrono::system_clock::now();
230             const auto response = Botan::Roughtime::Response::from_bits(response_raw, nonce);
231             std::chrono::milliseconds local_clock_max_error(1000);
232             const auto diff_abs = now >= response.utc_midpoint() ? now - response.utc_midpoint() : response.utc_midpoint() - now;
233             result.confirm("online", diff_abs <= (response.utc_radius() + local_clock_max_error));
234             }
235          catch(const std::exception& e)
236             {
237             result.test_failure(e.what());
238             }
239          return result;
240          }
241 
242 
243    public:
run()244       std::vector<Test::Result> run() override
245          {
246          std::vector<Test::Result> results;
247          results.push_back(test_nonce());
248          results.push_back(test_chain());
249          results.push_back(test_server_information());
250 
251          if(Test::options().run_online_tests())
252             {
253             results.push_back(test_request_online());
254             }
255 
256          return results;
257          }
258    };
259 
260 BOTAN_REGISTER_TEST("roughtime", "roughtime", Roughtime);
261 
262 #endif
263 
264 }
265