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