| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- using GLib;
- using Json;
- namespace Spry.Cli {
- /**
- * `spry keys` — runs `statum-genkeys` and merges its key material into
- * the "statum" section of the application's `web-config.json`.
- *
- * Key material is maintained as PAIRS (a secret and its public half):
- * when either half of a pair is missing the whole pair is regenerated,
- * so a half-deleted file is repaired with a matching pair rather than a
- * mismatched one. Any run that (re)writes key material prints a
- * prominent warning: regenerating a pair invalidates existing sessions
- * and sealed blobs. Complete pairs and every other configuration
- * section are left untouched.
- */
- public class Keys : GLib.Object {
- /**
- * The static key fields Statum reads from the "statum" section,
- * ordered secret/public pair-wise so missing halves can be repaired
- * by regenerating the whole pair.
- */
- public const string[] STATUM_KEYS = {
- "signing_secret_key",
- "signing_public_key",
- "encryption_signing_secret_key",
- "encryption_signing_public_key",
- "encryption_sealing_secret_key",
- "encryption_sealing_public_key"
- };
- /**
- * Fills any missing "statum" key pairs in `config_path`, creating
- * the file (and the section) when absent, and repairs half-present
- * pairs by regenerating them whole. Does nothing when every key is
- * already configured.
- */
- public static void run(string config_path, string? tools_dir, string? libdir) throws Error {
- if (has_all_keys(config_path)) {
- stdout.printf("All static keys are already configured in %s — nothing to do.\n", config_path);
- return;
- }
- var generated = spawn_genkeys(tools_dir, libdir);
- string[] repaired;
- var added = merge(config_path, generated, out repaired);
- foreach (var key in added) {
- stdout.printf("Added %s to the statum section of %s\n", key, config_path);
- }
- foreach (var pair in repaired) {
- stdout.printf("Repaired an incomplete pair in %s — %s were regenerated\n", config_path, pair);
- }
- stderr.printf("*** WARNING: new static key material was generated in %s.\n", config_path);
- stderr.printf("*** Existing sessions and sealed blobs are invalidated by this change.\n");
- }
- private static bool has_all_keys(string config_path) {
- if (!FileUtils.test(config_path, FileTest.EXISTS)) {
- return false;
- }
- try {
- var parser = new Parser();
- parser.load_from_file(config_path);
- var root = parser.get_root();
- if (root == null || root.get_object() == null || !((!)root).get_object().has_member("statum")) {
- return false;
- }
- var statum = ((!)root).get_object().get_object_member("statum");
- foreach (var key in STATUM_KEYS) {
- if (!statum.has_member(key) || statum.get_string_member(key) == null
- || ((!)statum.get_string_member(key)).length == 0) {
- return false;
- }
- }
- return true;
- } catch (Error e) {
- return false;
- }
- }
- private static Json.Object spawn_genkeys(string? tools_dir, string? libdir) throws Error {
- var tool = Tools.resolve("statum-genkeys", tools_dir);
- var launcher = new SubprocessLauncher(SubprocessFlags.STDOUT_PIPE | SubprocessFlags.STDERR_PIPE);
- Tools.apply_env(launcher, libdir);
- var process = launcher.spawnv({ tool });
- string stdout_buf;
- string stderr_buf;
- process.communicate_utf8(null, null, out stdout_buf, out stderr_buf);
- if (!process.get_successful()) {
- throw Generator.cli_error("statum-genkeys failed: %s", (stderr_buf ?? "").strip());
- }
- var parser = new Parser();
- parser.load_from_data(stdout_buf ?? "");
- var root = parser.get_root();
- if (root == null || root.get_object() == null || !((!)root).get_object().has_member("statum")) {
- throw Generator.cli_error("unexpected statum-genkeys output");
- }
- return ((!)root).get_object().get_object_member("statum");
- }
- /**
- * Merges the generated material pair-wise: complete pairs are kept,
- * pairs with a missing half are regenerated whole (`repaired` lists
- * them as `"secret + public"` labels), entirely absent pairs are
- * added (`added` lists their keys). The file is rewritten with mode
- * 0600 because it carries key material.
- */
- private static string[] merge(string config_path, Json.Object generated, out string[] repaired) throws Error {
- repaired = new string[0];
- var root = new Json.Object();
- if (FileUtils.test(config_path, FileTest.EXISTS)) {
- var parser = new Parser();
- try {
- parser.load_from_file(config_path);
- } catch (Error e) {
- throw Generator.cli_error("%s is not valid JSON: %s", config_path, e.message);
- }
- var existing = parser.get_root();
- if (existing == null || existing.get_object() == null) {
- throw Generator.cli_error("%s has no JSON object at the top level", config_path);
- }
- root = ((!)existing).get_object();
- }
- if (!root.has_member("statum")) {
- root.set_object_member("statum", new Json.Object());
- }
- var statum = root.get_object_member("statum");
- var added = new string[0];
- var repaired_pairs = new string[0];
- for (int i = 0; i + 1 < STATUM_KEYS.length; i += 2) {
- var secret_key = STATUM_KEYS[i];
- var public_key = STATUM_KEYS[i + 1];
- var secret = member_value(statum, secret_key);
- var public_value = member_value(statum, public_key);
- if (secret.length > 0 && public_value.length > 0) {
- continue;
- }
- if (secret.length > 0 || public_value.length > 0) {
- repaired_pairs += secret_key + " + " + public_key;
- } else {
- added += secret_key;
- added += public_key;
- }
- statum.set_string_member(secret_key, generated_value(generated, secret_key));
- statum.set_string_member(public_key, generated_value(generated, public_key));
- }
- repaired = repaired_pairs;
- var node = new Json.Node(Json.NodeType.OBJECT);
- node.set_object(root);
- Generator.write_file(config_path, Json.to_string(node, true) + "\n", 0600);
- return added;
- }
- private static string member_value(Json.Object obj, string key) {
- if (!obj.has_member(key) || obj.get_string_member(key) == null) {
- return "";
- }
- return ((!)obj.get_string_member(key)) ?? "";
- }
- private static string generated_value(Json.Object generated, string key) throws Error {
- if (!generated.has_member(key) || generated.get_string_member(key) == null) {
- throw Generator.cli_error("statum-genkeys output lacks %s", key);
- }
- return (!)generated.get_string_member(key);
- }
- }
- }
|