1 /*
2 Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License, version 2.0,
6 as published by the Free Software Foundation.
7
8 This program is also distributed with certain software (including
9 but not limited to OpenSSL) that is licensed under separate terms,
10 as designated in a particular file or component or in included license
11 documentation. The authors of MySQL hereby grant you an additional
12 permission to link the program and your derivative works with the
13 separately licensed software that they have included with MySQL.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 */
24
25 #include "keyring_frontend.h"
26
27 #include <algorithm>
28 #include <cctype> // isprint()
29 #include <fstream>
30 #include <sstream>
31 #include <vector>
32
33 #ifdef RAPIDJSON_NO_SIZETYPEDEFINE
34 // if we build within the server, it will set RAPIDJSON_NO_SIZETYPEDEFINE
35 // globally and require to include my_rapidjson_size_t.h
36 #include "my_rapidjson_size_t.h"
37 #endif
38
39 #include <rapidjson/document.h>
40 #include <rapidjson/prettywriter.h>
41
42 #include "keyring/keyring_file.h"
43 #include "keyring/master_key_file.h"
44 #include "mysql/harness/arg_handler.h"
45 #include "mysql/harness/utility/string.h"
46 #include "mysqlrouter/keyring_info.h"
47 #include "mysqlrouter/utils.h"
48 #include "print_version.h" // build_version
49 #include "random_generator.h"
50 #include "welcome_copyright_notice.h" // ORACLE_WELCOME_COPYRIGHT_NOTICE
51
52 using namespace std::string_literals;
53
54 static const unsigned kKeyLength{32};
55
init_from_arguments(const std::vector<std::string> & arguments)56 void KeyringFrontend::init_from_arguments(
57 const std::vector<std::string> &arguments) {
58 prepare_command_options();
59 try {
60 arg_handler_.process(arguments);
61 } catch (const std::invalid_argument &e) {
62 // unknown options
63 throw UsageError(e.what());
64 }
65 }
66
KeyringFrontend(const std::string & exe_name,const std::vector<std::string> & args,std::istream & is,std::ostream & os,std::ostream & es)67 KeyringFrontend::KeyringFrontend(const std::string &exe_name,
68 const std::vector<std::string> &args,
69 std::istream &is, std::ostream &os,
70 std::ostream &es)
71 : program_name_{exe_name},
72 arg_handler_{true},
73 cin_{is},
74 cout_{os},
75 cerr_{es} {
76 init_from_arguments(args);
77 }
78
get_version()79 std::string KeyringFrontend::get_version() noexcept {
80 std::stringstream os;
81
82 std::string version_string;
83 build_version(std::string(MYSQL_ROUTER_PACKAGE_NAME), &version_string);
84
85 os << version_string << std::endl;
86 os << ORACLE_WELCOME_COPYRIGHT_NOTICE("2019") << std::endl;
87
88 return os.str();
89 }
90
get_help(const size_t screen_width) const91 std::string KeyringFrontend::get_help(const size_t screen_width) const {
92 std::stringstream os;
93
94 os << "Usage" << std::endl;
95 os << std::endl;
96
97 os << mysql_harness::join(
98 mysql_harness::utility::wrap_string(
99 program_name_ + " " + "[opts] <cmd> <filename> [<username>]",
100 screen_width, 2),
101 "\n")
102 << std::endl;
103 os << mysql_harness::join(
104 mysql_harness::utility::wrap_string(program_name_ + " " + "--help",
105 screen_width, 2),
106 "\n")
107 << std::endl;
108 os << mysql_harness::join(
109 mysql_harness::utility::wrap_string(
110 program_name_ + " " + "--version", screen_width, 2),
111 "\n")
112 << std::endl;
113
114 os << std::endl;
115 os << "Commands" << std::endl;
116 os << std::endl;
117
118 std::vector<std::pair<std::string, std::string>> cmd_help{
119 {"init", "initialize a keyring."},
120 {"set", "add or overwrite account of <username> in <filename>."},
121 {"delete", "delete entry from keyring."},
122 {"list", "list all entries in keyring."},
123 {"export", "export all entries of keyring as JSON."},
124 {"get", "field from keyring entry"},
125 {"master-delete", "keyring from master-keyfile"},
126 {"master-list", "list entries from master-keyfile"},
127 {"master-rename", "renames and entry in a master-keyfile"},
128 };
129
130 for (const auto &kv : cmd_help) {
131 os << " " << kv.first << std::endl
132 << mysql_harness::join(
133 mysql_harness::utility::wrap_string(kv.second, screen_width, 6),
134 "\n")
135 << std::endl;
136 }
137
138 os << std::endl;
139
140 os << "Options" << std::endl;
141
142 os << std::endl;
143
144 for (const auto &line : arg_handler_.option_descriptions(screen_width, 6)) {
145 os << line << std::endl;
146 }
147
148 return os.str();
149 }
150
151 /**
152 * load the masterkeyfile.
153 *
154 * @throws FrontendError on anything else.
155 * @returns success
156 * @retval true if loaded
157 * @retval false if file didn't exist
158 */
master_key_file_load(mysql_harness::MasterKeyFile & mkf)159 static bool master_key_file_load(mysql_harness::MasterKeyFile &mkf) {
160 try {
161 mkf.load();
162 } catch (const std::system_error &e) {
163 // if it doesn't exist, we'll later create it
164 if (e.code() != std::errc::no_such_file_or_directory) {
165 throw FrontendError("opening master-key-file failed: "s + e.what());
166 }
167
168 return false;
169 } catch (const std::exception &e) {
170 throw FrontendError("opening master-key-file failed: "s + e.what());
171 }
172
173 return true;
174 }
175
176 /**
177 * prepare the keyring.
178 *
179 * if keyring doesn't exist, generates a new random
180 *
181 * @throws FrontendError on anything else
182 * @returns if keyring changed and the keyrings random
183 */
keyring_file_prepare(mysql_harness::KeyringFile & kf,const std::string & keyring_filename)184 static std::pair<bool, std::string> keyring_file_prepare(
185 mysql_harness::KeyringFile &kf, const std::string &keyring_filename) {
186 if (keyring_filename.empty()) {
187 throw UsageError("expected <keyring> to be not empty");
188 }
189
190 try {
191 return {false, kf.read_header(keyring_filename)};
192 } catch (const std::system_error &e) {
193 // if it doesn't exist, we'll later create it
194 if (e.code() != std::errc::no_such_file_or_directory) {
195 throw FrontendError(e.what());
196 }
197
198 mysql_harness::RandomGenerator rg;
199 std::string kf_random = rg.generate_strong_password(kKeyLength);
200 kf.set_header(kf_random);
201
202 return {true, kf_random};
203 } catch (const std::exception &e) {
204 throw FrontendError(e.what());
205 }
206 }
207
keyring_file_load(mysql_harness::KeyringFile & kf,const std::string & keyring_filename,const std::string & kf_key)208 static bool keyring_file_load(mysql_harness::KeyringFile &kf,
209 const std::string &keyring_filename,
210 const std::string &kf_key) {
211 if (keyring_filename.empty()) {
212 throw UsageError("expected <keyring> to be not empty");
213 }
214
215 // load it, if it exists to be able to later save it with the same content
216 try {
217 kf.load(keyring_filename, kf_key);
218
219 return true;
220 } catch (const std::system_error &e) {
221 if (e.code() != std::errc::no_such_file_or_directory) {
222 throw FrontendError(e.what());
223 }
224
225 return false;
226 } catch (const std::exception &e) {
227 throw FrontendError("loading failed?"s + e.what());
228 }
229 }
230
231 /**
232 * prepare master-key-file for keyring.
233 *
234 * if keyring-file isn't known in master-key-file:
235 *
236 * - generates encryption-key for keyring
237 * - adds keyring to master-key-file
238 *
239 * otherwise, gets encryption-key from master-key-file
240 *
241 * @returns if-master-key-file-changed and the encryption-key for the keyring
242 */
master_key_file_prepare(mysql_harness::MasterKeyFile & mkf,mysql_harness::KeyringFile & kf,const std::string & keyring_filename,const std::string & kf_random)243 static std::pair<bool, std::string> master_key_file_prepare(
244 mysql_harness::MasterKeyFile &mkf, mysql_harness::KeyringFile &kf,
245 const std::string &keyring_filename, const std::string &kf_random) {
246 std::string kf_key;
247 try {
248 kf_key = mkf.get(keyring_filename, kf_random);
249 if (kf_key.empty()) {
250 // master-keyring doesn't exist or keyfile not known in master-keyring
251 mysql_harness::RandomGenerator rg;
252 kf_key = rg.generate_strong_password(kKeyLength);
253
254 mkf.add(keyring_filename, kf_key, kf_random);
255
256 return {true, kf_key};
257 } else {
258 keyring_file_load(kf, keyring_filename, kf_key);
259 }
260 } catch (const mysql_harness::decryption_error &) {
261 // file is known, but our key doesn't match
262 throw FrontendError(
263 "master-key-file knows key-file, but key doesn't match.");
264 }
265
266 return {false, kf_key};
267 }
268
cmd_init_with_master_key_file(const std::string & keyring_filename,const std::string & master_keyring_filename)269 static void cmd_init_with_master_key_file(
270 const std::string &keyring_filename,
271 const std::string &master_keyring_filename) {
272 mysql_harness::KeyringFile kf;
273 bool kf_changed{false};
274 std::string kf_random;
275 std::tie(kf_changed, kf_random) = keyring_file_prepare(kf, keyring_filename);
276
277 if (!kf_changed) {
278 size_t file_size;
279 try {
280 std::fstream f(keyring_filename);
281 f.seekg(0, std::ios_base::end);
282 file_size = f.tellg();
283 f.close();
284 } catch (const std::exception &e) {
285 throw FrontendError(e.what());
286 }
287
288 if (file_size > (4 // marker
289 + 4 // header length
290 + kf_random.size() // header
291 + 4 // payload-signature
292 + 4 // payload-version
293 + 4 // entries
294 + 4 // no idea
295 )) {
296 // keyring exists and header was parsed
297 throw FrontendError("keyfile '" + keyring_filename +
298 "' already exists and has entries");
299 }
300 }
301
302 mysql_harness::MasterKeyFile mkf(master_keyring_filename);
303
304 master_key_file_load(mkf);
305
306 bool mkf_changed{false};
307 std::string kf_key;
308 std::tie(mkf_changed, kf_key) =
309 master_key_file_prepare(mkf, kf, keyring_filename, kf_random);
310
311 // save master-key-file first to not create a keyring entry that can't be
312 // decoded.
313 try {
314 if (mkf_changed) mkf.save();
315 } catch (const std::exception &e) {
316 throw FrontendError("failed saving master-key-file: "s + e.what());
317 }
318 try {
319 if (kf_changed) kf.save(keyring_filename, kf_key);
320 } catch (const std::exception &e) {
321 throw FrontendError("failed saving keyring: "s + e.what());
322 }
323 }
324
master_key_reader_load(const std::string & master_key_reader,const std::string & keyring_filename)325 static std::pair<bool, std::string> master_key_reader_load(
326 const std::string &master_key_reader, const std::string &keyring_filename) {
327 try {
328 KeyringInfo kinfo;
329 kinfo.set_master_key_reader(master_key_reader);
330 if (!kinfo.read_master_key()) {
331 throw FrontendError("failed reading master-key for '" + keyring_filename +
332 "' from master-key-reader '"s + master_key_reader +
333 "'");
334 }
335 std::string kf_key = kinfo.get_master_key();
336 if (kf_key.empty()) {
337 // master-keyring doesn't exist or keyfile not known in master-keyring
338 mysql_harness::RandomGenerator rg;
339
340 return {true, rg.generate_strong_password(kKeyLength)};
341 } else {
342 return {false, kf_key};
343 }
344 } catch (const FrontendError &) {
345 throw;
346 } catch (const std::exception &e) {
347 throw FrontendError(
348 "failed reading master-key from --master-key-reader: "s + e.what());
349 }
350 }
351
cmd_init_with_master_key_reader(const std::string & keyring_filename,const std::string & master_key_reader,const std::string & master_key_writer)352 static void cmd_init_with_master_key_reader(
353 const std::string &keyring_filename, const std::string &master_key_reader,
354 const std::string &master_key_writer) {
355 mysql_harness::KeyringFile kf;
356 bool kf_changed{false};
357 std::string kf_random;
358 std::tie(kf_changed, kf_random) = keyring_file_prepare(kf, keyring_filename);
359
360 std::string kf_key;
361 bool mk_changed{false};
362 std::tie(mk_changed, kf_key) =
363 master_key_reader_load(master_key_reader, keyring_filename);
364
365 keyring_file_load(kf, keyring_filename, kf_key);
366
367 if (mk_changed) {
368 KeyringInfo kinfo;
369 kinfo.set_master_key_writer(master_key_writer);
370 if (!kinfo.write_master_key()) {
371 throw FrontendError("failed writing master-key for '" + keyring_filename +
372 "' to master-key-writer '" + master_key_writer + "'");
373 }
374 }
375 try {
376 if (kf_changed) kf.save(keyring_filename, kf_key);
377 } catch (const std::exception &e) {
378 throw FrontendError("failed saving keyfile: "s + e.what());
379 }
380 }
381
cmd_init_with_master_key(const std::string & keyring_filename,const std::string & kf_key)382 static void cmd_init_with_master_key(const std::string &keyring_filename,
383 const std::string &kf_key) {
384 if (kf_key.empty()) {
385 throw FrontendError("expected master-key for '" + keyring_filename +
386 "' to be not empty, but it is");
387 }
388
389 bool kf_changed{false};
390 mysql_harness::KeyringFile kf;
391 std::string kf_random;
392 std::tie(kf_changed, kf_random) = keyring_file_prepare(kf, keyring_filename);
393
394 keyring_file_load(kf, keyring_filename, kf_key);
395
396 try {
397 if (kf_changed) kf.save(keyring_filename, kf_key);
398 } catch (const std::exception &e) {
399 throw FrontendError("failed saving keyfile: "s + e.what());
400 }
401 }
402
cmd_master_delete(const std::string & master_keyring_filename,const std::string & keyring_filename)403 static void cmd_master_delete(const std::string &master_keyring_filename,
404 const std::string &keyring_filename) {
405 mysql_harness::MasterKeyFile mkf(master_keyring_filename);
406 try {
407 mkf.load();
408 } catch (const std::exception &e) {
409 throw FrontendError("opening master-key-file failed: "s + e.what());
410 }
411
412 bool mkf_changed = mkf.remove(keyring_filename);
413
414 if (mkf_changed) {
415 mkf.save();
416 } else {
417 throw FrontendError("Keyring '" + keyring_filename +
418 "' not found in master-key-file '" +
419 master_keyring_filename + "'");
420 }
421 }
422
cmd_master_list(std::ostream & cout,const std::string & master_keyring_filename)423 static void cmd_master_list(std::ostream &cout,
424 const std::string &master_keyring_filename) {
425 mysql_harness::MasterKeyFile mkf(master_keyring_filename);
426 try {
427 mkf.load();
428 } catch (const std::exception &e) {
429 throw FrontendError("opening master-key-file failed: "s + e.what());
430 }
431
432 for (auto it = mkf.entries().begin(); it != mkf.entries().end(); ++it) {
433 cout << it->first << std::endl;
434 }
435 }
436
cmd_master_rename(const std::string & master_keyring_filename,const std::string & old_key,const std::string & new_key)437 static void cmd_master_rename(const std::string &master_keyring_filename,
438 const std::string &old_key,
439 const std::string &new_key) {
440 mysql_harness::MasterKeyFile mkf(master_keyring_filename);
441 try {
442 mkf.load();
443 } catch (const std::exception &e) {
444 throw FrontendError("opening master-key-file failed: "s + e.what());
445 }
446
447 try {
448 mkf.add_encrypted(new_key, mkf.get_encrypted(old_key));
449 } catch (const std::out_of_range &) {
450 throw FrontendError("old-key '" + old_key +
451 "' not found in master-key-file '" +
452 master_keyring_filename + "'");
453 } catch (const std::invalid_argument &) {
454 throw FrontendError("new-key '" + new_key +
455 "' already exists in master-key-file '" +
456 master_keyring_filename + "'");
457 }
458 mkf.remove(old_key);
459
460 mkf.save();
461 }
462
cmd_export(std::ostream & os,const mysql_harness::KeyringFile & kf)463 static void cmd_export(std::ostream &os, const mysql_harness::KeyringFile &kf) {
464 rapidjson::StringBuffer json_buf;
465 rapidjson::PrettyWriter<rapidjson::StringBuffer> json_writer(json_buf);
466 rapidjson::Document json_doc;
467 rapidjson::Document::AllocatorType &allocator = json_doc.GetAllocator();
468 json_doc.SetObject();
469 for (auto const &entry : kf.entries()) {
470 rapidjson::Value el;
471
472 el.SetObject();
473
474 for (auto const &kv : entry.second) {
475 el.AddMember(
476 rapidjson::Value(kv.first.c_str(), kv.first.size(), allocator),
477 rapidjson::Value(kv.second.c_str(), kv.second.size(), allocator),
478 allocator);
479 }
480 json_doc.AddMember(
481 rapidjson::Value(entry.first.c_str(), entry.first.size(), allocator),
482 el.Move(), allocator);
483
484 } // free json_doc and json_writer early
485 json_doc.Accept(json_writer);
486
487 os << json_buf.GetString() << std::endl;
488 }
489
cmd_list(std::ostream & os,mysql_harness::KeyringFile & kf,const std::string & username)490 static bool cmd_list(std::ostream &os, mysql_harness::KeyringFile &kf,
491 const std::string &username) {
492 bool found{false};
493 if (username.empty()) {
494 found = true;
495 for (auto const &entry : kf.entries()) {
496 os << entry.first << std::endl;
497 }
498 } else {
499 for (auto const &entry : kf.entries()) {
500 if (entry.first == username) {
501 found = true;
502 for (auto const &kv : entry.second) {
503 os << kv.first << std::endl;
504 }
505 }
506 }
507 }
508 return found;
509 }
510
cmd_delete(mysql_harness::KeyringFile & kf,const std::string & username,const std::string & field)511 static bool cmd_delete(mysql_harness::KeyringFile &kf,
512 const std::string &username, const std::string &field) {
513 if (field.empty()) {
514 return kf.remove(username);
515 } else {
516 return kf.remove_attribute(username, field);
517 }
518 }
519
cmd_set(mysql_harness::KeyringFile & kf,const std::string & username,const std::string & field,const std::string & value)520 static void cmd_set(mysql_harness::KeyringFile &kf, const std::string &username,
521 const std::string &field, const std::string &value) {
522 kf.store(username, field, value);
523 }
524
cmd_get(std::ostream & os,mysql_harness::KeyringFile & kf,const std::string & username,const std::string & field)525 static void cmd_get(std::ostream &os, mysql_harness::KeyringFile &kf,
526 const std::string &username, const std::string &field) {
527 try {
528 os << kf.fetch(username, field) << std::endl;
529 } catch (const std::out_of_range &) {
530 throw FrontendError("'" + field + "' not found for user '" + username +
531 "'");
532 }
533 }
534
535 /**
536 * prepare arguments and cmds.
537 *
538 * - check command name
539 * - check argument counts
540 *
541 * @throws UsageError on error
542 */
prepare_args()543 void KeyringFrontend::prepare_args() {
544 // handle positional args
545 const auto &rest_args = arg_handler_.get_rest_arguments();
546 auto rest_args_count = rest_args.size();
547
548 if (config_.cmd != Cmd::ShowHelp && config_.cmd != Cmd::ShowVersion) {
549 if (rest_args_count == 0) {
550 throw UsageError("expected a <cmd>");
551 }
552
553 std::map<std::string, KeyringFrontend::Cmd> cmds{
554 {"init", KeyringFrontend::Cmd::Init},
555 {"set", KeyringFrontend::Cmd::Set},
556 {"delete", KeyringFrontend::Cmd::Delete},
557 {"list", KeyringFrontend::Cmd::List},
558 {"export", KeyringFrontend::Cmd::Export},
559 {"get", KeyringFrontend::Cmd::Get},
560 {"master-delete", KeyringFrontend::Cmd::MasterDelete},
561 {"master-list", KeyringFrontend::Cmd::MasterList},
562 {"master-rename", KeyringFrontend::Cmd::MasterRename},
563 };
564
565 const auto cmd = cmds.find(rest_args[0]);
566 if (cmd == cmds.end()) {
567 throw UsageError("unknown command: " + rest_args[0]);
568 }
569
570 config_.cmd = cmd->second;
571
572 --rest_args_count;
573 }
574
575 switch (config_.cmd) {
576 case Cmd::MasterDelete:
577 case Cmd::Init:
578 case Cmd::Export:
579 if (rest_args_count != 1) {
580 throw UsageError("expected one argument <filename>, got " +
581 std::to_string(rest_args_count) + " arguments");
582 }
583 config_.keyring_filename = rest_args[1];
584 break;
585 case Cmd::MasterList:
586 case Cmd::ShowVersion:
587 case Cmd::ShowHelp:
588 if (rest_args_count != 0) {
589 throw UsageError("expected no extra arguments, got " +
590 std::to_string(rest_args_count) + " arguments");
591 }
592 break;
593 case Cmd::List:
594 if (rest_args_count < 1 || rest_args_count > 2) {
595 throw UsageError("expected <filename> and optionally <username>, got " +
596 std::to_string(rest_args_count) + " arguments");
597 }
598
599 config_.keyring_filename = rest_args[1];
600 if (rest_args_count > 1) config_.username = rest_args[2];
601 break;
602 case Cmd::Get:
603 if (rest_args_count != 3) {
604 throw UsageError("expected <filename> <username> <key>, got " +
605 std::to_string(rest_args_count) + " arguments");
606 }
607
608 config_.keyring_filename = rest_args[1];
609 config_.username = rest_args[2];
610 config_.field = rest_args[3];
611 break;
612 case Cmd::Set:
613 if (rest_args_count != 4 && rest_args_count != 3) {
614 throw UsageError(
615 "expected <filename> <username> <key>, optionally <value>, got " +
616 std::to_string(rest_args_count) + " arguments");
617 }
618
619 config_.keyring_filename = rest_args[1];
620 config_.username = rest_args[2];
621 config_.field = rest_args[3];
622 if (rest_args_count == 4) {
623 config_.value = rest_args[4];
624 } else {
625 config_.value =
626 mysqlrouter::prompt_password("value for " + config_.field);
627 }
628 break;
629 case Cmd::Delete:
630 if (rest_args_count != 2 && rest_args_count != 3) {
631 throw UsageError(
632 "expected <filename> <username>, and optionally <key>, got " +
633 std::to_string(rest_args_count) + " arguments");
634 }
635
636 config_.keyring_filename = rest_args[1];
637 config_.username = rest_args[2];
638 if (rest_args_count == 3) config_.field = rest_args[3];
639 break;
640 case Cmd::MasterRename:
641 if (rest_args_count != 2) {
642 throw UsageError("expected 2 arguments <old-key> <new-key>, got " +
643 std::to_string(rest_args_count) + " arguments");
644 }
645
646 config_.keyring_filename = rest_args[1];
647 config_.username = rest_args[2];
648 break;
649 }
650 }
651
run()652 int KeyringFrontend::run() {
653 prepare_args();
654
655 switch (config_.cmd) {
656 case Cmd::ShowHelp:
657 cout_ << get_help() << std::endl;
658 return EXIT_SUCCESS;
659 case Cmd::ShowVersion:
660 cout_ << get_version() << std::endl;
661 return EXIT_SUCCESS;
662 default:
663 break;
664 }
665
666 if (!config_.master_keyring_filename.empty() &&
667 !config_.master_key_reader.empty()) {
668 throw UsageError(
669 "--master-key-file and --master-key-reader can't be used together");
670 }
671
672 if (!config_.master_keyring_filename.empty() &&
673 !config_.master_key_writer.empty()) {
674 throw UsageError(
675 "--master-key-file and --master-key-writer can't be used together");
676 }
677
678 switch (config_.cmd) {
679 case Cmd::Init:
680 if (!config_.master_keyring_filename.empty()) {
681 cmd_init_with_master_key_file(config_.keyring_filename,
682 config_.master_keyring_filename);
683
684 } else if (!config_.master_key_reader.empty() ||
685 !config_.master_key_writer.empty()) {
686 cmd_init_with_master_key_reader(config_.keyring_filename,
687 config_.master_key_reader,
688 config_.master_key_writer);
689
690 } else {
691 cmd_init_with_master_key(
692 config_.keyring_filename,
693 mysqlrouter::prompt_password("Please enter master key"));
694 }
695
696 return EXIT_SUCCESS;
697 case Cmd::MasterDelete:
698 if (config_.master_keyring_filename.empty()) {
699 throw UsageError("expected --master-key-file to be not empty");
700 }
701
702 cmd_master_delete(config_.master_keyring_filename,
703 config_.keyring_filename);
704
705 return EXIT_SUCCESS;
706 case Cmd::MasterList:
707 if (config_.master_keyring_filename.empty()) {
708 throw UsageError("expected --master-key-file to be not empty");
709 }
710
711 cmd_master_list(cout_, config_.master_keyring_filename);
712 return EXIT_SUCCESS;
713 case Cmd::MasterRename:
714 // master-rename uses the 'config_' struct slightly differently then
715 // the other commands:
716 //
717 // * config_.keyring_filename -> <old_key>
718 // * config_.username -> <new_key>
719 //
720 if (config_.master_keyring_filename.empty()) {
721 throw UsageError("expected --master-key-file to be not empty");
722 }
723 if (config_.keyring_filename.empty()) {
724 throw UsageError("expected <old-key> to be not empty");
725 }
726
727 if (config_.username.empty()) {
728 throw UsageError("expected <new-key> to be not empty");
729 }
730
731 for (const char c : config_.keyring_filename) {
732 if (!std::isprint(c)) {
733 throw UsageError(
734 "expected <old-key> to contain only printable characters");
735 }
736 }
737
738 for (const char c : config_.username) {
739 if (!std::isprint(c)) {
740 throw UsageError(
741 "expected <new-key> to contain only printable characters");
742 }
743 }
744 cmd_master_rename(config_.master_keyring_filename,
745 config_.keyring_filename, config_.username);
746 return EXIT_SUCCESS;
747 default:
748 break;
749 }
750
751 // Cmd::Init, Cmd::Master* are already handled.
752 //
753 // All other commands require a key from
754 // - the master-key-ring,
755 // - stdin
756 // - master-key-reader
757
758 std::string kf_key;
759 mysql_harness::KeyringFile kf;
760
761 if (!config_.master_keyring_filename.empty()) {
762 std::string kf_random;
763 try {
764 kf_random = kf.read_header(config_.keyring_filename);
765 } catch (const std::exception &e) {
766 throw FrontendError("opening keyring failed: "s + e.what());
767 }
768 mysql_harness::MasterKeyFile mkf(config_.master_keyring_filename);
769 try {
770 mkf.load();
771 kf_key = mkf.get(config_.keyring_filename, kf_random);
772 } catch (const std::exception &e) {
773 throw FrontendError("opening master-key-file failed: "s + e.what());
774 }
775
776 if (kf_key.empty()) {
777 throw FrontendError("couldn't find master key for " +
778 config_.keyring_filename + " in master-key-file " +
779 config_.master_keyring_filename);
780 }
781 } else if (!config_.master_key_reader.empty()) {
782 KeyringInfo kinfo;
783 kinfo.set_master_key_reader(config_.master_key_reader);
784 if (!kinfo.read_master_key()) {
785 throw FrontendError(
786 "failed reading master-key for '" + config_.keyring_filename +
787 "' from master-key-reader '" + config_.master_key_reader + "'");
788 }
789 kf_key = kinfo.get_master_key();
790 } else {
791 kf_key = mysqlrouter::prompt_password("Please enter master key");
792 }
793
794 if (kf_key.empty()) {
795 throw FrontendError("expected master-key for '" + config_.keyring_filename +
796 "' to be not empty, but it is");
797 }
798
799 // load keyring
800 keyring_file_load(kf, config_.keyring_filename, kf_key);
801
802 bool kf_changed = false;
803 switch (config_.cmd) {
804 case Cmd::ShowHelp:
805 case Cmd::ShowVersion:
806 case Cmd::Init:
807 case Cmd::MasterList:
808 case Cmd::MasterDelete:
809 case Cmd::MasterRename:
810 // unreachable
811 abort();
812 break;
813 case Cmd::Get:
814 cmd_get(cout_, kf, config_.username, config_.field);
815 break;
816 case Cmd::Set:
817 cmd_set(kf, config_.username, config_.field, config_.value);
818 kf_changed = true;
819
820 break;
821 case Cmd::Delete: {
822 if (!cmd_delete(kf, config_.username, config_.field)) {
823 return EXIT_FAILURE;
824 }
825 kf_changed = true;
826 break;
827 }
828 case Cmd::Export:
829 cmd_export(cout_, kf);
830 break;
831 case Cmd::List:
832 if (!cmd_list(cout_, kf, config_.username)) {
833 return EXIT_FAILURE;
834 }
835 break;
836 }
837
838 if (kf_changed) {
839 kf.save(config_.keyring_filename, kf_key);
840 }
841
842 return EXIT_SUCCESS;
843 }
844
prepare_command_options()845 void KeyringFrontend::prepare_command_options() {
846 // prepare default-kdf-name and the list of supported names
847 arg_handler_.add_option(
848 CmdOption::OptionNames({"-?", "--help"}), "Display this help and exit.",
849 CmdOptionValueReq::none, "", [this](const std::string &) {
850 if (config_.cmd != Cmd::ShowVersion) {
851 config_.cmd = Cmd::ShowHelp;
852 }
853 });
854 arg_handler_.add_option(
855 //
856 CmdOption::OptionNames({"-V", "--version"}),
857 "Display version information and exit.", CmdOptionValueReq::none, "",
858 [this](const std::string &) {
859 if (config_.cmd != Cmd::ShowHelp) {
860 config_.cmd = Cmd::ShowVersion;
861 }
862 });
863 arg_handler_.add_option(
864 CmdOption::OptionNames({"--master-key-file"}),
865 "Filename of the master keyfile.", CmdOptionValueReq::required, "",
866 [this](const std::string &v) {
867 if (v.empty()) {
868 throw UsageError("expected --master-key-file to be not empty.");
869 }
870 config_.master_keyring_filename = v;
871 });
872 arg_handler_.add_option(
873 CmdOption::OptionNames({"--master-key-reader"}),
874 "Executable which provides the master key for the keyfile.",
875 CmdOptionValueReq::required, "", [this](const std::string &v) {
876 if (v.empty()) {
877 throw UsageError("expected --master-key-reader to be not empty.");
878 }
879 config_.master_key_reader = v;
880 });
881 arg_handler_.add_option(
882 CmdOption::OptionNames({"--master-key-writer"}),
883 "Executable which can store the master key for the keyfile.",
884 CmdOptionValueReq::required, "", [this](const std::string &v) {
885 if (v.empty()) {
886 throw UsageError("expected --master-key-writer to be not empty.");
887 }
888 config_.master_key_writer = v;
889 });
890 }
891