ca-config.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. /*
  2. * Define and handle the configuration dialog box for SSH host CAs,
  3. * using the same portable dialog specification API as config.c.
  4. */
  5. #include "putty.h"
  6. #include "dialog.h"
  7. #include "storage.h"
  8. #include "tree234.h"
  9. #include "ssh.h"
  10. const bool has_ca_config_box = true;
  11. #define NRSATYPES 3
  12. struct ca_state {
  13. dlgcontrol *ca_name_edit;
  14. dlgcontrol *ca_reclist;
  15. dlgcontrol *ca_pubkey_edit;
  16. dlgcontrol *ca_pubkey_info;
  17. dlgcontrol *ca_validity_edit;
  18. dlgcontrol *rsa_type_checkboxes[NRSATYPES];
  19. char *name, *pubkey, *validity;
  20. tree234 *ca_names; /* stores plain 'char *' */
  21. ca_options opts;
  22. strbuf *ca_pubkey_blob;
  23. };
  24. static int ca_name_compare(void *av, void *bv)
  25. {
  26. return strcmp((const char *)av, (const char *)bv);
  27. }
  28. static inline void clear_string_tree(tree234 *t)
  29. {
  30. char *p;
  31. while ((p = delpos234(t, 0)) != NULL)
  32. sfree(p);
  33. }
  34. static void ca_state_free(void *vctx)
  35. {
  36. struct ca_state *st = (struct ca_state *)vctx;
  37. clear_string_tree(st->ca_names);
  38. freetree234(st->ca_names);
  39. sfree(st->name);
  40. sfree(st->validity);
  41. sfree(st);
  42. }
  43. static void ca_refresh_name_list(struct ca_state *st)
  44. {
  45. clear_string_tree(st->ca_names);
  46. host_ca_enum *hce = enum_host_ca_start();
  47. if (hce) {
  48. strbuf *namebuf = strbuf_new();
  49. while (strbuf_clear(namebuf), enum_host_ca_next(hce, namebuf)) {
  50. char *name = dupstr(namebuf->s);
  51. char *added = add234(st->ca_names, name);
  52. /* Just imaginable that concurrent filesystem access might
  53. * cause a repetition; avoid leaking memory if so */
  54. if (added != name)
  55. sfree(name);
  56. }
  57. strbuf_free(namebuf);
  58. enum_host_ca_finish(hce);
  59. }
  60. }
  61. static void set_from_hca(struct ca_state *st, host_ca *hca)
  62. {
  63. sfree(st->name);
  64. st->name = dupstr(hca->name ? hca->name : "");
  65. sfree(st->pubkey);
  66. if (hca->ca_public_key)
  67. st->pubkey = strbuf_to_str(
  68. base64_encode_sb(ptrlen_from_strbuf(hca->ca_public_key), 0));
  69. else
  70. st->pubkey = dupstr("");
  71. st->validity = dupstr(hca->validity_expression ?
  72. hca->validity_expression : "");
  73. st->opts = hca->opts; /* structure copy */
  74. }
  75. static void ca_refresh_pubkey_info(struct ca_state *st, dlgparam *dp)
  76. {
  77. char *text = NULL;
  78. ssh_key *key = NULL;
  79. strbuf *blob = strbuf_new();
  80. ptrlen data = ptrlen_from_asciz(st->pubkey);
  81. if (st->ca_pubkey_blob)
  82. strbuf_free(st->ca_pubkey_blob);
  83. st->ca_pubkey_blob = NULL;
  84. if (!data.len) {
  85. text = dupstr(" ");
  86. goto out;
  87. }
  88. /*
  89. * See if we have a plain base64-encoded public key blob.
  90. */
  91. if (base64_valid(data)) {
  92. base64_decode_bs(BinarySink_UPCAST(blob), data);
  93. } else {
  94. /*
  95. * Otherwise, try to decode as if it was a public key _file_.
  96. */
  97. BinarySource src[1];
  98. BinarySource_BARE_INIT_PL(src, data);
  99. const char *error;
  100. if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(blob), NULL, &error)) {
  101. text = dupprintf("Cannot decode key: %s", error);
  102. goto out;
  103. }
  104. }
  105. ptrlen alg_name = pubkey_blob_to_alg_name(ptrlen_from_strbuf(blob));
  106. if (!alg_name.len) {
  107. text = dupstr("Invalid key (no key type)");
  108. goto out;
  109. }
  110. const ssh_keyalg *alg = find_pubkey_alg_len(alg_name);
  111. if (!alg) {
  112. text = dupprintf("Unrecognised key type '%.*s'",
  113. PTRLEN_PRINTF(alg_name));
  114. goto out;
  115. }
  116. if (alg->is_certificate) {
  117. text = dupprintf("CA key may not be a certificate (type is '%.*s')",
  118. PTRLEN_PRINTF(alg_name));
  119. goto out;
  120. }
  121. key = ssh_key_new_pub(alg, ptrlen_from_strbuf(blob));
  122. if (!key) {
  123. text = dupprintf("Invalid '%.*s' key data", PTRLEN_PRINTF(alg_name));
  124. goto out;
  125. }
  126. text = ssh2_fingerprint(key, SSH_FPTYPE_DEFAULT);
  127. st->ca_pubkey_blob = blob;
  128. blob = NULL; /* prevent free */
  129. out:
  130. dlg_text_set(st->ca_pubkey_info, dp, text);
  131. if (key)
  132. ssh_key_free(key);
  133. sfree(text);
  134. if (blob)
  135. strbuf_free(blob);
  136. }
  137. static void ca_load_selected_record(struct ca_state *st, dlgparam *dp)
  138. {
  139. int i = dlg_listbox_index(st->ca_reclist, dp);
  140. if (i < 0) {
  141. dlg_beep(dp);
  142. return;
  143. }
  144. const char *name = index234(st->ca_names, i);
  145. if (!name) { /* in case the list box and the tree got out of sync */
  146. dlg_beep(dp);
  147. return;
  148. }
  149. host_ca *hca = host_ca_load(name);
  150. if (!hca) {
  151. char *msg = dupprintf("Unable to load host CA record '%s'", name);
  152. dlg_error_msg(dp, msg);
  153. sfree(msg);
  154. return;
  155. }
  156. set_from_hca(st, hca);
  157. host_ca_free(hca);
  158. dlg_refresh(st->ca_name_edit, dp);
  159. dlg_refresh(st->ca_pubkey_edit, dp);
  160. dlg_refresh(st->ca_validity_edit, dp);
  161. for (size_t i = 0; i < NRSATYPES; i++)
  162. dlg_refresh(st->rsa_type_checkboxes[i], dp);
  163. ca_refresh_pubkey_info(st, dp);
  164. }
  165. static void ca_ok_handler(dlgcontrol *ctrl, dlgparam *dp,
  166. void *data, int event)
  167. {
  168. if (event == EVENT_ACTION)
  169. dlg_end(dp, 0);
  170. }
  171. static void ca_name_handler(dlgcontrol *ctrl, dlgparam *dp,
  172. void *data, int event)
  173. {
  174. struct ca_state *st = (struct ca_state *)ctrl->context.p;
  175. if (event == EVENT_REFRESH) {
  176. dlg_editbox_set(ctrl, dp, st->name);
  177. } else if (event == EVENT_VALCHANGE) {
  178. sfree(st->name);
  179. st->name = dlg_editbox_get(ctrl, dp);
  180. /*
  181. * Try to auto-select the typed name in the list.
  182. */
  183. int index;
  184. if (!findrelpos234(st->ca_names, st->name, NULL, REL234_GE, &index))
  185. index = count234(st->ca_names) - 1;
  186. if (index >= 0)
  187. dlg_listbox_select(st->ca_reclist, dp, index);
  188. }
  189. }
  190. static void ca_reclist_handler(dlgcontrol *ctrl, dlgparam *dp,
  191. void *data, int event)
  192. {
  193. struct ca_state *st = (struct ca_state *)ctrl->context.p;
  194. if (event == EVENT_REFRESH) {
  195. dlg_update_start(ctrl, dp);
  196. dlg_listbox_clear(ctrl, dp);
  197. const char *name;
  198. for (int i = 0; (name = index234(st->ca_names, i)) != NULL; i++)
  199. dlg_listbox_add(ctrl, dp, name);
  200. dlg_update_done(ctrl, dp);
  201. } else if (event == EVENT_ACTION) {
  202. /* Double-clicking a session loads it */
  203. ca_load_selected_record(st, dp);
  204. }
  205. }
  206. static void ca_load_handler(dlgcontrol *ctrl, dlgparam *dp,
  207. void *data, int event)
  208. {
  209. struct ca_state *st = (struct ca_state *)ctrl->context.p;
  210. if (event == EVENT_ACTION) {
  211. ca_load_selected_record(st, dp);
  212. }
  213. }
  214. static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp,
  215. void *data, int event)
  216. {
  217. struct ca_state *st = (struct ca_state *)ctrl->context.p;
  218. if (event == EVENT_ACTION) {
  219. if (!*st->validity) {
  220. dlg_error_msg(dp, "No validity expression configured "
  221. "for this key");
  222. return;
  223. }
  224. char *error_msg;
  225. ptrlen error_loc;
  226. if (!cert_expr_valid(st->validity, &error_msg, &error_loc)) {
  227. char *error_full = dupprintf("Error in expression: %s", error_msg);
  228. dlg_error_msg(dp, error_full);
  229. dlg_set_focus(st->ca_validity_edit, dp);
  230. dlg_editbox_select_range(
  231. st->ca_validity_edit, dp,
  232. (const char *)error_loc.ptr - st->validity, error_loc.len);
  233. sfree(error_msg);
  234. sfree(error_full);
  235. return;
  236. }
  237. if (!st->ca_pubkey_blob) {
  238. dlg_error_msg(dp, "No valid CA public key entered");
  239. return;
  240. }
  241. host_ca *hca = snew(host_ca);
  242. memset(hca, 0, sizeof(*hca));
  243. hca->name = dupstr(st->name);
  244. hca->ca_public_key = strbuf_dup(ptrlen_from_strbuf(
  245. st->ca_pubkey_blob));
  246. hca->validity_expression = dupstr(st->validity);
  247. hca->opts = st->opts; /* structure copy */
  248. char *error = host_ca_save(hca);
  249. host_ca_free(hca);
  250. if (error) {
  251. dlg_error_msg(dp, error);
  252. sfree(error);
  253. } else {
  254. ca_refresh_name_list(st);
  255. dlg_refresh(st->ca_reclist, dp);
  256. }
  257. }
  258. }
  259. static void ca_delete_handler(dlgcontrol *ctrl, dlgparam *dp,
  260. void *data, int event)
  261. {
  262. struct ca_state *st = (struct ca_state *)ctrl->context.p;
  263. if (event == EVENT_ACTION) {
  264. int i = dlg_listbox_index(st->ca_reclist, dp);
  265. if (i < 0) {
  266. dlg_beep(dp);
  267. return;
  268. }
  269. const char *name = index234(st->ca_names, i);
  270. if (!name) { /* in case the list box and the tree got out of sync */
  271. dlg_beep(dp);
  272. return;
  273. }
  274. char *error = host_ca_delete(name);
  275. if (error) {
  276. dlg_error_msg(dp, error);
  277. sfree(error);
  278. } else {
  279. ca_refresh_name_list(st);
  280. dlg_refresh(st->ca_reclist, dp);
  281. }
  282. }
  283. }
  284. static void ca_pubkey_edit_handler(dlgcontrol *ctrl, dlgparam *dp,
  285. void *data, int event)
  286. {
  287. struct ca_state *st = (struct ca_state *)ctrl->context.p;
  288. if (event == EVENT_REFRESH) {
  289. dlg_editbox_set(ctrl, dp, st->pubkey);
  290. } else if (event == EVENT_VALCHANGE) {
  291. sfree(st->pubkey);
  292. st->pubkey = dlg_editbox_get(ctrl, dp);
  293. ca_refresh_pubkey_info(st, dp);
  294. }
  295. }
  296. static void ca_pubkey_file_handler(dlgcontrol *ctrl, dlgparam *dp,
  297. void *data, int event)
  298. {
  299. struct ca_state *st = (struct ca_state *)ctrl->context.p;
  300. if (event == EVENT_ACTION) {
  301. Filename *filename = dlg_filesel_get(ctrl, dp);
  302. strbuf *keyblob = strbuf_new();
  303. const char *load_error;
  304. bool ok = ppk_loadpub_f(filename, NULL, BinarySink_UPCAST(keyblob),
  305. NULL, &load_error);
  306. if (!ok) {
  307. char *message = dupprintf(
  308. "Unable to load public key from '%s': %s",
  309. filename_to_str(filename), load_error);
  310. dlg_error_msg(dp, message);
  311. sfree(message);
  312. } else {
  313. sfree(st->pubkey);
  314. st->pubkey = strbuf_to_str(
  315. base64_encode_sb(ptrlen_from_strbuf(keyblob), 0));
  316. dlg_refresh(st->ca_pubkey_edit, dp);
  317. }
  318. filename_free(filename);
  319. strbuf_free(keyblob);
  320. }
  321. }
  322. static void ca_validity_handler(dlgcontrol *ctrl, dlgparam *dp,
  323. void *data, int event)
  324. {
  325. struct ca_state *st = (struct ca_state *)ctrl->context.p;
  326. if (event == EVENT_REFRESH) {
  327. dlg_editbox_set(ctrl, dp, st->validity);
  328. } else if (event == EVENT_VALCHANGE) {
  329. sfree(st->validity);
  330. st->validity = dlg_editbox_get(ctrl, dp);
  331. }
  332. }
  333. static void ca_rsa_type_handler(dlgcontrol *ctrl, dlgparam *dp,
  334. void *data, int event)
  335. {
  336. struct ca_state *st = (struct ca_state *)ctrl->context.p;
  337. size_t offset = ctrl->context2.i;
  338. bool *option = (bool *)((char *)&st->opts + offset);
  339. if (event == EVENT_REFRESH) {
  340. dlg_checkbox_set(ctrl, dp, *option);
  341. } else if (event == EVENT_VALCHANGE) {
  342. *option = dlg_checkbox_get(ctrl, dp);
  343. }
  344. }
  345. void setup_ca_config_box(struct controlbox *b)
  346. {
  347. struct controlset *s;
  348. dlgcontrol *c;
  349. /* Internal state for manipulating the host CA system */
  350. struct ca_state *st = (struct ca_state *)ctrl_alloc_with_free(
  351. b, sizeof(struct ca_state), ca_state_free);
  352. memset(st, 0, sizeof(*st));
  353. st->ca_names = newtree234(ca_name_compare);
  354. st->validity = dupstr("");
  355. ca_refresh_name_list(st);
  356. /* Initialise the settings to a default blank host_ca */
  357. {
  358. host_ca *hca = host_ca_new();
  359. set_from_hca(st, hca);
  360. host_ca_free(hca);
  361. }
  362. /* Action area, with the Done button in it */
  363. s = ctrl_getset(b, "", "", "");
  364. ctrl_columns(s, 5, 20, 20, 20, 20, 20);
  365. c = ctrl_pushbutton(s, "Done", 'o', HELPCTX(ssh_kex_cert),
  366. ca_ok_handler, P(st));
  367. c->button.iscancel = true;
  368. c->column = 4;
  369. /* Load/save box, as similar as possible to the main saved sessions one */
  370. s = ctrl_getset(b, "Main", "loadsave",
  371. "Load, save or delete a host CA record");
  372. ctrl_columns(s, 2, 75, 25);
  373. c = ctrl_editbox(s, "Name for this CA (shown in log messages)",
  374. 'n', 100, HELPCTX(ssh_kex_cert),
  375. ca_name_handler, P(st), P(NULL));
  376. c->column = 0;
  377. st->ca_name_edit = c;
  378. /* Reset columns so that the buttons are alongside the list, rather
  379. * than alongside that edit box. */
  380. ctrl_columns(s, 1, 100);
  381. ctrl_columns(s, 2, 75, 25);
  382. c = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(ssh_kex_cert),
  383. ca_reclist_handler, P(st));
  384. c->column = 0;
  385. c->listbox.height = 6;
  386. st->ca_reclist = c;
  387. c = ctrl_pushbutton(s, "Load", 'l', HELPCTX(ssh_kex_cert),
  388. ca_load_handler, P(st));
  389. c->column = 1;
  390. c = ctrl_pushbutton(s, "Save", 'v', HELPCTX(ssh_kex_cert),
  391. ca_save_handler, P(st));
  392. c->column = 1;
  393. c = ctrl_pushbutton(s, "Delete", 'd', HELPCTX(ssh_kex_cert),
  394. ca_delete_handler, P(st));
  395. c->column = 1;
  396. s = ctrl_getset(b, "Main", "pubkey", "Public key for this CA record");
  397. ctrl_columns(s, 2, 75, 25);
  398. c = ctrl_editbox(s, "Public key of certification authority", 'k', 100,
  399. HELPCTX(ssh_kex_cert), ca_pubkey_edit_handler,
  400. P(st), P(NULL));
  401. c->column = 0;
  402. st->ca_pubkey_edit = c;
  403. c = ctrl_filesel(s, "Read from file", NO_SHORTCUT, NULL, false,
  404. "Select public key file of certification authority",
  405. HELPCTX(ssh_kex_cert), ca_pubkey_file_handler, P(st));
  406. c->fileselect.just_button = true;
  407. c->align_next_to = st->ca_pubkey_edit;
  408. c->column = 1;
  409. ctrl_columns(s, 1, 100);
  410. st->ca_pubkey_info = c = ctrl_text(s, " ", HELPCTX(ssh_kex_cert));
  411. c->text.wrap = false;
  412. s = ctrl_getset(b, "Main", "options", "What this CA is trusted to do");
  413. c = ctrl_editbox(s, "Valid hosts this key is trusted to certify", 'h', 100,
  414. HELPCTX(ssh_cert_valid_expr), ca_validity_handler,
  415. P(st), P(NULL));
  416. st->ca_validity_edit = c;
  417. ctrl_columns(s, 4, 44, 18, 18, 18);
  418. c = ctrl_text(s, "Signature types (RSA keys only):",
  419. HELPCTX(ssh_cert_rsa_hash));
  420. c->column = 0;
  421. dlgcontrol *sigtypelabel = c;
  422. c = ctrl_checkbox(s, "SHA-1", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash),
  423. ca_rsa_type_handler, P(st));
  424. c->column = 1;
  425. c->align_next_to = sigtypelabel;
  426. c->context2 = I(offsetof(ca_options, permit_rsa_sha1));
  427. st->rsa_type_checkboxes[0] = c;
  428. c = ctrl_checkbox(s, "SHA-256", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash),
  429. ca_rsa_type_handler, P(st));
  430. c->column = 2;
  431. c->align_next_to = sigtypelabel;
  432. c->context2 = I(offsetof(ca_options, permit_rsa_sha256));
  433. st->rsa_type_checkboxes[1] = c;
  434. c = ctrl_checkbox(s, "SHA-512", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash),
  435. ca_rsa_type_handler, P(st));
  436. c->column = 3;
  437. c->align_next_to = sigtypelabel;
  438. c->context2 = I(offsetof(ca_options, permit_rsa_sha512));
  439. st->rsa_type_checkboxes[2] = c;
  440. ctrl_columns(s, 1, 100);
  441. }