~sircmpwn/himitsu-devel

himitsu: hiq: add -M to store/query for multi-line secrets v1 REJECTED

Willow Barraco: 1
 hiq: add -M to store/query for multi-line secrets

 2 files changed, 69 insertions(+), 6 deletions(-)
I was pretty sure this would be nacked, but felt that sharing it would
give ideas or point to limits. Now I'm not sure this feature could
land, in any form, on the himitsu main project.

Thanks for the clues! I might write an external cli for that eventually
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/himitsu-devel/patches/54720/mbox | git am -3
Learn more about email & git

[PATCH himitsu] hiq: add -M to store/query for multi-line secrets Export this patch

Signed-off-by: Willow Barraco <contact@willowbarraco.fr>
---
 cmd/hiq/main.ha | 68 +++++++++++++++++++++++++++++++++++++++++++++----
 docs/hiq.1.scd  |  7 ++++-
 2 files changed, 69 insertions(+), 6 deletions(-)

diff --git a/cmd/hiq/main.ha b/cmd/hiq/main.ha
index 6386423..6d9fa59 100644
--- a/cmd/hiq/main.ha
+++ b/cmd/hiq/main.ha
@@ -30,6 +30,7 @@ export fn main() void = {
		('d', "decrypt private keys"),
		('D', "delete matching keys"),
		('F', "field", "select a field for output"),
		('M', "field", "multi-line field data"),
		('l', "soft lock the keyring"),
		('L', "hard lock the keyring"),
		('Q', "terminate the daemon (if started with -D)"),
@@ -42,6 +43,7 @@ export fn main() void = {
	let op = client::operation::QUERY;
	let flags: client::flags = 0;
	let field: (str | void) = void;
	let multiline: (str | void) = void;
	let one = false;
	let lock = lockop::NONE;

@@ -57,6 +59,8 @@ export fn main() void = {
			op = client::operation::DEL;
		case 'F' =>
			field = opt.1;
		case 'M' =>
			multiline = opt.1;
		case 'l' =>
			lock = lockop::SOFT;
		case 'L' =>
@@ -97,7 +101,43 @@ export fn main() void = {
		};
	};

	if (op == client::operation::ADD && len(cmd.args) == 0) {
	if (op == client::operation::ADD && len(cmd.args) != 0 && !(multiline is void)) {
		if (tty::isatty(os::stderr_file) && tty::isatty(os::stdin_file)) {
			fmt::errorln("Enter the content of the value then press ^d:")!;
		};
		const multiline = multiline as str;

		let scan = bufio::newscanner(os::stdin);
		defer bufio::finish(&scan);

		let lines: []str = [];
		defer strings::freeall(lines);
		for (let line => bufio::scan_line(&scan)) {
			let line = match (line) {
			case let line: const str =>
				yield line;
			case let e: io::error =>
				fmt::fatal("Error reading data", io::strerror(e));
			case utf8::invalid =>
				fmt::fatal("Error: Query is not valid UTF-8");
			};
			append(lines, strings::dup(line));
		};
		const secret = strings::join("\0", lines...);
		defer free(secret);

		const item = strings::concat(multiline, "=", secret);
		append(cmd.args, item);

		const query = match (query::parse_items(cmd.args)) {
		case let q: query::query =>
			yield q;
		case let err: query::error =>
			fmt::fatal(query::strerror(err));
		};
		defer query::finish(&query);
		send(conn, op, &query, flags, field, multiline, one);
	} else if (op == client::operation::ADD && len(cmd.args) == 0) {
		if (tty::isatty(os::stderr_file) && tty::isatty(os::stdin_file)) {
			fmt::errorln("Enter new keys, one per line, then press ^d:")!;
		};
@@ -125,7 +165,7 @@ export fn main() void = {
					strings::dup(line));
			};
			defer query::finish(&query);
			send(conn, client::operation::ADD, &query, flags, field, one);
			send(conn, client::operation::ADD, &query, flags, field, multiline, one);
		};
	} else {
		const query = match (query::parse_items(cmd.args)) {
@@ -135,7 +175,7 @@ export fn main() void = {
			fmt::fatal(query::strerror(err));
		};
		defer query::finish(&query);
		send(conn, op, &query, flags, field, one);
		send(conn, op, &query, flags, field, multiline, one);
	};
};

@@ -145,6 +185,7 @@ fn send(
	query: *query::query,
	flags: client::flags,
	field: (str | void),
	multiline: (str | void),
	one: bool,
) void = {
	const keys = match (client::query(conn, op, query, flags)) {
@@ -168,6 +209,7 @@ fn send(
			fmt::fatal("Error executing query:", client::strerror(err));
		case done => break;
		};

		match (field) {
		case let field: str =>
			for (let item &.. key.items) {
@@ -175,9 +217,25 @@ fn send(
					fmt::fprintln(&buf, item.value)!;
				};
			};
		case void =>
			query::unparse(&buf, &key)!;
			continue;
		case void => yield;
		};

		match (multiline) {
		case let multiline: str =>
			for (let item &.. key.items) {
				if (item.key == multiline) {
					let lines = strings::split(item.value, "\0");
					for (const line .. lines) {
						fmt::fprintln(&buf, line)!;
					};
				};
			};
			continue;
		case void => yield;
		};

		query::unparse(&buf, &key)!;
	};

	fmt::print(memio::string(&buf)!)!;
diff --git a/docs/hiq.1.scd b/docs/hiq.1.scd
index 221ae35..ab54661 100644
--- a/docs/hiq.1.scd
+++ b/docs/hiq.1.scd
@@ -6,7 +6,7 @@ hiq - query the *himitsu*(7) key store

# SYNOPSIS

*hiq* [-h1adDlLQs] [-F _field_] _query_...
*hiq* [-h1adDlLQs] [-F _field_] [-M _field_] _query_...

# DESCRIPTION

@@ -47,6 +47,11 @@ have already been quoted properly.
	Select a specific field to print. By default, the full key is printed in
	the format described by *KEY FORMAT* in *himitsu*(7).

*-M* _field_
	Select a specific field to use as a multi-line value. It use the stdin
	while adding, and the stdout when querying. The data is encoded using
	NULL char as line return.

*-l*
	Soft locks the keyring by removing the secret key from memory. Keeps
	non-private data of entries in memory.
-- 
2.46.0
I'm not in favor of introducing multiline field support into hiq, since
it breaks the query format once applied. Multiline values do not play
well with other unix tools.

There are some other cases this can fail easily:

 * When the value contains already an "\0" before adding it as a 
   multiline value, it will introduce another unintended line break.
 * When your query returns more than one entry with a multiline value
   You won't be able to tell where one begins and the next one stops.
 
Also how can one store an entry with more than one multiline fields?

The intended way to store values, that contain control characters, is to
encode them with external tools. E.g. base64. Before adding them to
himitsu.