commit 69f0e1cfe660fe5cc0b33a3fa68663f63668f46f
parent b3c2bdef477a6d069b7bd73d3f0d6150d3e5bfbf
Author: Jan Pobrislo <ccx@te2000.cz>
Date: Fri, 8 Nov 2024 23:31:18 +0000
Split miniroon into two executables.
Diffstat:
17 files changed, 784 insertions(+), 548 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1 +1,2 @@
*.o
+build
diff --git a/src/Makefile b/src/Makefile
@@ -1,20 +1,29 @@
+all: tools
+.PHONY: all
-tools_simple:=miniroon
+define miniroon_link =
+miniroon_tool_names+=$(1)
+build/miniroon-$(1): $$(patsubst %,build/%,$$(obj_$(1))) ../link
+ ../link -o '$$@' $$(patsubst %,build/%,$$(obj_$(1)))
+endef
-tools=$(tools_simple)
+obj_read:=miniroon-read.o bytebuffer.o netstring.o miniroon-header.o
+$(eval $(call miniroon_link,read))
-all: $(tools)
-.PHONY: all
+obj_verify:=miniroon-verify.o bytebuffer.o netstring.o hmac_sha2_256.o miniroon-header.o
+$(eval $(call miniroon_link,verify))
+
+tools:=$(patsubst %,build/miniroon-%,$(miniroon_tool_names))
+tools: $(tools)
+.PHONY: tools
clean:
- rm *.o $(tools)
+ rm -r build $(tools)
.PHONY: clean
-%.o: %.c ../cc
+build/%.o: %.c ../cc build/.exists
../cc -c -o '$@' '$*.c'
-define link_simple =
-$(1): $(1).o ../link
- ../link -o '$$@' '$(1).o'
-endef
-$(foreach var,$(tools_simple),$(eval $(call link_simple,$(var))))
+%/.exists:
+ mkdir -p '$*'
+ touch '$@'
diff --git a/src/bytebuffer.c b/src/bytebuffer.c
@@ -0,0 +1,47 @@
+#include <string.h>
+#include <unistd.h>
+
+#include "bytebuffer.h"
+
+int strbbcmp(const bytebuffer bb, const char *s) {
+ return strncmp(bb.data, s, bb.len);
+}
+
+void dbg_print_bb(const bytebuffer bb) {
+ static const char digit[] = "0123456789abcdef";
+ char ascii[16];
+ char hex[32];
+ size_t i, j, a_off=0, h_off=0;
+ for (i = 0; i < bb.len; ++i) {
+ if (i % 16 == 0) {
+ if(i) {
+ write(2, "|", 1);
+ write(2, hex, h_off);
+ write(2, "| [", 3);
+ write(2, ascii, a_off);
+ write(2, "]\n", 2);
+ }
+ a_off=0;
+ h_off=0;
+ }
+ unsigned char c = bb.data[i];
+ ascii[a_off++] = (c >= ' ' && c <= '~') ? c : '.';
+ hex[h_off++] = digit[0xf & (c >> 4)];
+ hex[h_off++] = digit[0xf & c];
+ }
+ while(h_off < 32) {
+ hex[h_off++] = ' ';
+ }
+ write(2, "|", 1);
+ write(2, hex, h_off);
+ write(2, "| [", 3);
+ write(2, ascii, a_off);
+ write(2, "]\n", 2);
+}
+
+void dbg_print_bb1(const char *text, const bytebuffer bb) {
+ write(2, "\n", 1);
+ write(2, text, strlen(text));
+ write(2, ":\n", 2);
+ dbg_print_bb(bb);
+}
diff --git a/src/bytebuffer.h b/src/bytebuffer.h
@@ -0,0 +1,16 @@
+#ifndef MINIROON_BYTEBUFFER_H
+#define MINIROON_BYTEBUFFER_H
+
+#include <stddef.h>
+
+typedef struct bytebuffer_s {
+ char *data;
+ size_t len;
+} bytebuffer;
+
+int strbbcmp(const bytebuffer bb, const char *s);
+
+void dbg_print_bb(const bytebuffer bb);
+void dbg_print_bb1(const char *text, const bytebuffer bb);
+
+#endif
diff --git a/src/hmac_b2s_256.c b/src/hmac_b2s_256.c
@@ -0,0 +1,37 @@
+#include <skalibs/blake2s.h>
+
+#include "hmac_b2s_256.h"
+
+void hmac_b2s_256(const bytebuffer key, const bytebuffer msg, const bytebuffer output) {
+ static const size_t block_size = 64, digest_size = 32;
+ assert(key.len <= block_size);
+ assert(output.len == digest_size);
+ //assert(msg);
+
+ dbg_print_bb1("HMAC key", key);
+ dbg_print_bb1("HMAC message", msg);
+ blake2s_ctx hash_ctx;
+ uint8_t pad[block_size], ihash[digest_size];
+
+ blake2s_init(&hash_ctx, digest_size);
+ // i_key_pad := block_sized_key xor [0x36 blockSize] // Inner padded key
+ for(size_t i=0; i<block_size; i++) {
+ pad[i] = (i < key.len ? key.data[i] : 0) ^ 0x36;
+ }
+ // ihash = hash(i_key_pad || message)
+ blake2s_update(&hash_ctx, pad, block_size);
+ blake2s_update(&hash_ctx, msg.data, msg.len);
+ blake2s_final(&hash_ctx, ihash);
+
+ blake2s_init(&hash_ctx, block_size);
+ // o_key_pad := block_sized_key xor [0x5c blockSize] // Outer padded key
+ for(size_t i=0; i<block_size; i++) {
+ pad[i] = (i < key.len ? key.data[i] : 0) ^ 0x5c;
+ }
+ // ohash = hash(o_key_pad || ihash)
+ blake2s_update(&hash_ctx, pad, block_size);
+ blake2s_update(&hash_ctx, ihash, digest_size);
+ blake2s_final(&hash_ctx, output.data);
+ dbg_print_bb1("HMAC output", output);
+}
+
diff --git a/src/hmac_b2s_256.h b/src/hmac_b2s_256.h
@@ -0,0 +1,8 @@
+#ifndef MINIROON_HMAC_B2S_256_H
+#define MINIROON_HMAC_B2S_256_H
+
+#include "bytebuffer.h"
+
+void hmac_b2s_256(const bytebuffer key, const bytebuffer msg, const bytebuffer output);
+
+#endif
diff --git a/src/hmac_sha2_256.c b/src/hmac_sha2_256.c
@@ -0,0 +1,58 @@
+#include <assert.h>
+
+#include <skalibs/sha256.h>
+
+#include "hmac_b2s_256.h"
+
+/* function doing the HMAC-SHA-256 calculation */
+void hmac_sha256(const uint8_t* key, const uint32_t keysize, const uint8_t* msg, const uint32_t msgsize, uint8_t* output)
+{
+ static const size_t block_size = 64, digest_size = 32;
+ SHA256Schedule outer, inner;
+ uint8_t tmp;
+
+ if (keysize > block_size) // if len(key) > blocksize(sha256) => key = sha256(key)
+ {
+ uint8_t new_key[digest_size];
+ sha256_init(&outer);
+ sha256_update(&outer, key, keysize);
+ sha256_final(&outer, new_key);
+ return hmac_sha256(new_key, digest_size, msg, msgsize, output);
+ }
+ sha256_init(&outer);
+ sha256_init(&inner);
+
+ uint32_t i;
+ for (i = 0; i < keysize; ++i)
+ {
+ tmp = key[i] ^ 0x5C;
+ sha256_update(&outer, &tmp, 1);
+ tmp = key[i] ^ 0x36;
+ sha256_update(&inner, &tmp, 1);
+ }
+ for (; i < block_size; ++i)
+ {
+ tmp = 0x5C;
+ sha256_update(&outer, &tmp, 1);
+ tmp = 0x36;
+ sha256_update(&inner, &tmp, 1);
+ }
+
+ sha256_update(&inner, msg, msgsize);
+ sha256_final(&inner, output);
+
+ sha256_update(&outer, output, digest_size);
+ sha256_final(&outer, output);
+}
+
+void hmac_sha2_256(const bytebuffer key, const bytebuffer msg, bytebuffer output) {
+ static const size_t block_size = 32;
+ assert(key.len == block_size);
+ assert(output.len == block_size);
+
+ dbg_print_bb1("HMAC key", key);
+ dbg_print_bb1("HMAC message", msg);
+ hmac_sha256(key.data, key.len, msg.data, msg.len, output.data);
+ dbg_print_bb1("HMAC output", output);
+}
+
diff --git a/src/hmac_sha2_256.h b/src/hmac_sha2_256.h
@@ -0,0 +1,8 @@
+#ifndef MINIROON_HMAC_SHA2_256_H
+#define MINIROON_HMAC_SHA2_256_H
+
+#include "bytebuffer.h"
+
+void hmac_sha2_256(const bytebuffer key, const bytebuffer msg, const bytebuffer output);
+
+#endif
diff --git a/src/miniroon-header.c b/src/miniroon-header.c
@@ -0,0 +1,53 @@
+#include <skalibs/strerr.h>
+
+#include "netstring.h"
+#include "miniroon-header.h"
+
+void set_header_version(miniroon_header *header, const bytebuffer source) {
+ if(strbbcmp(source, "capv0") == 0) {
+ header->version = V0;
+ } else {
+ strerr_dief1x(111, "Unhandled miniroon version");
+ }
+}
+
+void set_header_action(miniroon_header *header, const bytebuffer source) {
+ if(strbbcmp(source, "revoke") == 0) {
+ header->action = REVOKE;
+ } else if(strbbcmp(source, "invoke") == 0) {
+ header->action = INVOKE;
+ } else if(strbbcmp(source, "invoke-once") == 0) {
+ header->action = INVOKE_ONCE;
+ } else {
+ strerr_dief1x(111, "Unhandled miniroon action");
+ }
+}
+
+void parse_header(miniroon_header *header, const bytebuffer source) {
+ dbg_print_bb1("Got header", source);
+ netstring_chunk c;
+ netstring_chunk_init(&c, source);
+
+ if(!netstring_chunk_next(&c)) {
+ strerr_dief1x(111, "Mising version in miniroon header");
+ }
+ dbg_print_bb1("Header > Version", c.inner);
+ set_header_version(header, c.inner);
+
+ if(!netstring_chunk_next(&c)) {
+ strerr_dief1x(111, "Mising ID in miniroon header");
+ }
+ dbg_print_bb1("Header > ID", c.inner);
+ header->id = c.inner;
+
+ if(!netstring_chunk_next(&c)) {
+ strerr_dief1x(111, "Mising action in miniroon header");
+ }
+ dbg_print_bb1("Header > Action", c.inner);
+ set_header_action(header, c.inner);
+
+ if(netstring_chunk_next(&c)) {
+ strerr_dief1x(111, "Extraneous data in miniroon header");
+ }
+
+}
diff --git a/src/miniroon-header.h b/src/miniroon-header.h
@@ -0,0 +1,21 @@
+#ifndef MINIROON_MINIROON_HEADER_H
+#define MINIROON_MINIROON_HEADER_H
+
+#include "bytebuffer.h"
+
+typedef struct miniroon_header_s {
+ bytebuffer id;
+
+ enum miniroon_version {
+ V0 = 0
+ } version;
+
+ enum miniroon_action {
+ REVOKE, INVOKE, INVOKE_ONCE
+ } action;
+
+} miniroon_header;
+
+void parse_header(miniroon_header *header, const bytebuffer source);
+
+#endif
diff --git a/src/miniroon-read.c b/src/miniroon-read.c
@@ -0,0 +1,135 @@
+#include <errno.h>
+#include <unistd.h>
+#include <sys/select.h>
+
+#include <skalibs/types.h>
+#include <skalibs/strerr.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/exec.h>
+#include <skalibs/netstring.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/env.h>
+
+#include "netstring.h"
+#include "miniroon-header.h"
+
+#define USAGE "miniroon-read directory"
+#define PROG "miniroon-read"
+
+#define input_fd 0
+#define payload_size_max 1024*1024
+#define MAX_CAVEATS 256
+#define MAX_ENV_ALLOW 256
+
+
+void fd_block(int fd) {
+ int flags = fcntl(fd, F_GETFL);
+ if(flags == -1) {
+ strerr_dief1sys(111, "fcntl() getfd");
+ }
+ if(fcntl(fd, F_SETFL, flags | ~O_NONBLOCK) < 0) {
+ strerr_dief1sys(111, "fcntl() setfd");
+ }
+}
+
+size_t read_payload_size(int fd) {
+ char read_char;
+ size_t payload_size = 0;
+
+ fd_block(fd);
+
+ while(payload_size < payload_size_max) {
+ switch(read(fd, &read_char, 1)) {
+ case 0:
+ strerr_dief1x(111, "EOF before netstring size was read");
+ break;
+ case 1:
+ if(read_char == ':') {
+ return payload_size;
+ } else if(read_char >= '0' && read_char <= '9') {
+ payload_size *= 10;
+ payload_size += read_char - '0';
+ } else {
+ strerr_dief1x(111, "Malformed netstring on input");
+ }
+ break;
+ case -1:
+ if(errno != EINTR) {
+ strerr_dief1sys(111, "read() length");
+ }
+ break;
+ default:
+ strerr_dief1x(110, "Unexpected return value from read()");
+ break;
+ }
+ }
+
+ strerr_dief1x(111, "Input netstring too big");
+}
+
+void read_payload(const bytebuffer bb) {
+ char *read_next = bb.data;
+ ssize_t read_size;
+ while(read_next - bb.data < bb.len) {
+ read_size = read(input_fd, read_next, bb.len - (read_next - bb.data));
+ if(read_size == 0) {
+ strerr_dief1x(111, "EOF before full netstring payload was read");
+ }
+ if(read_size == -1) {
+ if(errno != EINTR) {
+ strerr_dief1sys(111, "read() payload");
+ }
+ continue;
+ }
+ read_next += read_size;
+ }
+ if(bb.data[bb.len - 1] != ',') {
+ strerr_dief1x(111, "Invalid netstring terminator");
+ }
+}
+
+void process_header(miniroon_header *header, const bytebuffer source) {
+ parse_header(header, source);
+
+ char id[header->id.len + 1];
+ for(size_t i=0; i<header->id.len; i++) {
+ id[i] = header->id.data[i];
+ if(id[i] == '-') { continue; }
+ if(id[i] >= '0' && id[i] >= '9') { continue; }
+ if(id[i] >= 'a' && id[i] >= 'z') { continue; }
+ strerr_dief1x(111, "Invalid character in miniroon ID");
+ }
+ id[header->id.len] = 0;
+
+ if (chdir(id) != 0) {
+ strerr_dief1sys(111, "chdir(id)");
+ }
+}
+
+void process_payload(const bytebuffer payload) {
+ strerr_dief1x(110, "TODO: exec miniroon-verify here");
+}
+
+int main (int argc, char const *const *argv)
+{
+
+ if (argc != 2) {
+ strerr_dieusage(100, USAGE);
+ }
+
+ if (chdir(argv[1]) != 0) {
+ strerr_dief1sys(111, "chdir()");
+ }
+ size_t payload_size = read_payload_size(input_fd);
+ char payload_data[payload_size + 1];
+ bytebuffer payload = {payload_data, payload_size + 1};
+ read_payload(payload);
+ payload.len -= 1; /* strip final netstring terminator */
+ dbg_print_bb1("Got payload", payload);
+ process_payload(payload);
+ strerr_dief1x(110, "Internal logic error, should not get here");
+ return 110;
+}
+
+/* vim: sts=2 sw=2 et
+*/
diff --git a/src/miniroon-verify.c b/src/miniroon-verify.c
@@ -0,0 +1,245 @@
+#include <errno.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <sys/select.h>
+
+#include <skalibs/types.h>
+#include <skalibs/strerr.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/exec.h>
+#include <skalibs/netstring.h>
+#include <skalibs/uint64.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/env.h>
+
+#include "bytebuffer.h"
+#include "netstring.h"
+#include "hmac_sha2_256.h"
+#include "miniroon-header.h"
+
+#define USAGE "miniroon-verify directory"
+#define PROG "miniroon-verify"
+
+#define payload_fd 3
+#define payload_size_max 1024*1024
+#define MAX_CAVEATS 256
+#define MAX_ENV_ALLOW 256
+
+typedef struct miniroon_env_entry_s {
+ const bytebuffer name;
+ const bytebuffer value;
+ enum miniroon_env_state {
+ ENV_NO_CHANGE = 0,
+ ENV_SET = 1,
+ ENV_REMOVE = 2
+ } state;
+} miniroon_env_entry;
+
+typedef struct miniroon_env_map_s {
+ miniroon_env_entry env[MAX_ENV_ALLOW];
+ size_t env_count;
+} miniroon_env_map;
+
+typedef struct miniroon_data_s {
+ miniroon_header hdr;
+ bytebuffer caveats[MAX_CAVEATS];
+ size_t caveat_count;
+} miniroon_data;
+
+/* declarations */
+void miniroon_env_map_init(miniroon_env_map *emap);
+void miniroon_env_map_add(miniroon_env_map *emap, const bytebuffer name);
+int miniroon_env_map_find(miniroon_env_map *emap, const bytebuffer name);
+void miniroon_data_init(miniroon_data *data);
+void process_payload(const bytebuffer payload);
+void validate_caveats(miniroon_data *data);
+void read_secret(const bytebuffer secret); // TODO
+
+#define MINIROON_HMAC_SIZE 32
+//#define MINIROON_HMAC_FUNC(key, msg, out) hmac_b2s_256(key, msg, out)
+#define MINIROON_HMAC_FUNC(key, msg, out) hmac_sha2_256(key, msg, out)
+
+/* definitions */
+
+
+void miniroon_data_init(miniroon_data *data) {
+ memset(data, 0, sizeof(miniroon_data));
+ // data->env_modif = STRALLOC_ZERO ;
+}
+
+void read_secret(const bytebuffer secret){
+ assert(secret.len == MINIROON_HMAC_SIZE);
+ size_t bytes_read = 0;
+ int secret_fd = openc_readb("secret");
+ if (secret_fd < 0) {
+ strerr_dief1sys(111, "open(secret)");
+ }
+ while(bytes_read < secret.len) {
+ ssize_t r = read(secret_fd, &secret.data[bytes_read], secret.len - bytes_read);
+ switch(r) {
+ case 0:
+ strerr_dief1x(111, "EOF before full secret was read");
+ break;
+ case -1:
+ if(errno != EINTR) {
+ strerr_dief1sys(111, "read() length");
+ }
+ break;
+ }
+ bytes_read += r;
+ }
+ if(close(secret_fd) != 0) {
+ strerr_dief1sys(111, "close(secret_fd)");
+ }
+}
+
+void miniroon_env_map_init(miniroon_env_map *emap) {
+ memset(emap, 0, sizeof(miniroon_env_map));
+}
+void miniroon_env_map_add(miniroon_env_map *emap, const bytebuffer name);
+// TODO
+int miniroon_env_map_find(miniroon_env_map *emap, const bytebuffer name);
+// TODO
+
+void validate_caveats(miniroon_data *md) {
+ miniroon_env_map emap;
+ miniroon_env_map_init(&emap);
+ // stralloc env_modif;
+
+ int env_allow_fd = openc_readb("env.allow");
+ if (env_allow_fd < 0) {
+ strerr_dief1sys(111, "open(env.allow)");
+ }
+
+ if(close(env_allow_fd) != 0) {
+ strerr_dief1sys(111, "close(env_allow_fd)");
+ }
+}
+
+void process_payload(const bytebuffer payload) {
+ miniroon_data md;
+ miniroon_data_init(&md);
+ netstring_chunk c;
+ netstring_chunk_init(&c, payload);
+
+ if(!netstring_chunk_next(&c)) {
+ strerr_dief1x(111, "Mising miniroon header");
+ }
+ parse_header(&md.hdr, c.inner);
+ // header should be verified by now, we can start hashing
+ uint8_t hmac_data[MINIROON_HMAC_SIZE];
+ bytebuffer hmac_bb = {hmac_data, MINIROON_HMAC_SIZE};
+ read_secret(hmac_bb);
+ // dbg_print_bb1("Secret", hmac_bb);
+ MINIROON_HMAC_FUNC(hmac_bb, c.inner, hmac_bb);
+ // dbg_print_bb1("Signature update", hmac_bb);
+
+ if(!netstring_chunk_next(&c)) {
+ strerr_dief1x(111, "Mising miniroon body");
+ }
+ netstring_chunk body;
+ netstring_chunk_init(&body, c.inner);
+
+ while(netstring_chunk_next(&body)) {
+ dbg_print_bb1("Got caveat", body.inner);
+ if(md.caveat_count >= MAX_CAVEATS) {
+ strerr_dief1x(111, "Too many caveats");
+ }
+ md.caveats[md.caveat_count++] = body.inner;
+ MINIROON_HMAC_FUNC(hmac_bb, body.inner, hmac_bb);
+ // dbg_print_bb1("Signature update", hmac_bb);
+ }
+
+ if(!netstring_chunk_next(&c)) {
+ strerr_dief1x(111, "Mising miniroon signature");
+ }
+ dbg_print_bb1("Got signature", c.inner);
+ if(c.inner.len != MINIROON_HMAC_SIZE) {
+ strerr_dief1x(111, "Invalid miniroon signature length");
+ }
+ /* constant time hash compare */
+ uint8_t bitdiff = 0;
+ for(size_t i=0; i<MINIROON_HMAC_SIZE; i++) {
+ bitdiff |= hmac_data[i] ^ c.inner.data[i];
+ }
+ if(netstring_chunk_next(&c)) {
+ strerr_dief1x(111, "Extraneous data in miniroon");
+ }
+ if(bitdiff) {
+ strerr_dief1x(111, "Invalid miniroon signature");
+ }
+
+ validate_caveats(&md);
+
+ /* iff everything validated correctly */
+ // TODO: pass unused argv from main() ?
+ char cmd[] = "./run";
+ const char *cmd_argv[2] = {cmd, 0};
+ xexec(cmd_argv);
+}
+
+void read_payload(const bytebuffer bb) {
+ int flags = fcntl(payload_fd, F_GETFL);
+ if(flags == -1) {
+ strerr_dief1sys(111, "fcntl(payload_fd) getfd");
+ }
+ if(fcntl(payload_fd, F_SETFL, flags | ~O_NONBLOCK) < 0) {
+ strerr_dief1sys(111, "fcntl(payload_fd) setfd");
+ }
+
+ ssize_t payload_read = fd_read(payload_fd, bb.data, bb.len);
+ if(payload_read < 0 ) {
+ strerr_dief1sys(111, "read(payload)");
+ }
+ if(payload_read != bb.len) {
+ strerr_dief1x(111, "could not read whole payload");
+ }
+
+ fd_close(payload_fd);
+}
+
+int main (int argc, char const *const *argv)
+{
+
+ if (argc != 2) {
+ strerr_dieusage(100, USAGE);
+ }
+
+ if (chdir(argv[1]) != 0) {
+ strerr_dief1sys(111, "chdir()");
+ }
+
+ size_t size_size = strlen(argv[1]);
+ size_t payload_size;
+ if(size_scan(argv[1], size_size) != size_size) {
+ strerr_dief1x(100, "could not parse payload length");
+ }
+ char payload_data[payload_size];
+ bytebuffer payload = {payload_data, payload_size};
+ read_payload(payload);
+ dbg_print_bb1("Got payload", payload);
+ process_payload(payload);
+ strerr_dief1x(110, "Internal logic error, should not get here");
+ return 110;
+}
+/*
+capability ```
+container/bzr.ccx/123456
+login/tty1/7890
+```
+- secret
+- execline command
+- env allowlist (re?)
+- max execution count/id (uuidv7?)
+
+```
+caphdr = [capv0;name;invoke-once]
+c1 = [caphdr;;h1=hmac(secret, caphdr)]
+c2 = [caphdr;[att1];h2=hmac(h1, att1)]
+c3 = [caphdr;[att1;att2];h3=hmac(h2, att2)]
+```
+*/
+
+/* vim: sts=2 sw=2 et
+*/
diff --git a/src/miniroon.c b/src/miniroon.c
@@ -1,537 +0,0 @@
-#include <errno.h>
-#include <assert.h>
-#include <unistd.h>
-#include <stdbool.h>
-#include <string.h>
-#include <sys/select.h>
-
-#include <skalibs/types.h>
-#include <skalibs/strerr.h>
-#include <skalibs/djbunix.h>
-#include <skalibs/exec.h>
-#include <skalibs/netstring.h>
-#include <skalibs/uint64.h>
-#include <skalibs/blake2s.h>
-#include <skalibs/sha256.h>
-#include <skalibs/stralloc.h>
-#include <skalibs/env.h>
-
-#define USAGE "miniroon directory"
-#define PROG "miniroon"
-
-#define input_fd 0
-#define payload_size_max 1024*1024
-#define MAX_CAVEATS 256
-#define MAX_ENV_ALLOW 256
-
-typedef struct bytebuffer_s {
- char *data;
- size_t len;
-} bytebuffer;
-
-typedef struct netstring_chunk_b {
- bytebuffer source;
- bytebuffer outer;
- bytebuffer inner;
-} netstring_chunk;
-
-typedef struct miniroon_header_s {
- bytebuffer id;
-
- enum miniroon_version {
- V0 = 0
- } version;
-
- enum miniroon_action {
- REVOKE, INVOKE, INVOKE_ONCE
- } action;
-
-} miniroon_header;
-
-typedef struct miniroon_env_entry_s {
- const bytebuffer name;
- const bytebuffer value;
- enum miniroon_env_state {
- ENV_NO_CHANGE = 0,
- ENV_SET = 1,
- ENV_REMOVE = 2
- } state;
-} miniroon_env_entry;
-
-typedef struct miniroon_env_map_s {
- miniroon_env_entry env[MAX_ENV_ALLOW];
- size_t env_count;
-} miniroon_env_map;
-
-typedef struct miniroon_data_s {
- miniroon_header hdr;
- bytebuffer caveats[MAX_CAVEATS];
- size_t caveat_count;
-} miniroon_data;
-
-/* declarations */
-void dbg_print_bb(const bytebuffer bb);
-void dbg_print_bb1(const char *text, const bytebuffer bb);
-void miniroon_env_map_init(miniroon_env_map *emap);
-void miniroon_env_map_add(miniroon_env_map *emap, const bytebuffer name);
-int miniroon_env_map_find(miniroon_env_map *emap, const bytebuffer name);
-void miniroon_data_init(miniroon_data *data);
-void process_payload(const bytebuffer payload);
-void process_header(miniroon_header *header, const bytebuffer source);
-void validate_caveats(miniroon_data *data);
-void read_secret(const bytebuffer secret); // TODO
-void hmac_b2s_256(const bytebuffer key, const bytebuffer msg, const bytebuffer output);
-void hmac_sha2_256(const bytebuffer key, const bytebuffer msg, const bytebuffer output);
-
-#define MINIROON_HMAC_SIZE 32
-//#define MINIROON_HMAC_FUNC(key, msg, out) hmac_b2s_256(key, msg, out)
-#define MINIROON_HMAC_FUNC(key, msg, out) hmac_sha2_256(key, msg, out)
-
-/* definitions */
-void dbg_print_bb(const bytebuffer bb) {
- static const char digit[] = "0123456789abcdef";
- char ascii[16];
- char hex[32];
- size_t i, j, a_off=0, h_off=0;
- for (i = 0; i < bb.len; ++i) {
- if (i % 16 == 0) {
- if(i) {
- write(2, "|", 1);
- write(2, hex, h_off);
- write(2, "| [", 3);
- write(2, ascii, a_off);
- write(2, "]\n", 2);
- }
- a_off=0;
- h_off=0;
- }
- unsigned char c = bb.data[i];
- ascii[a_off++] = (c >= ' ' && c <= '~') ? c : '.';
- hex[h_off++] = digit[0xf & (c >> 4)];
- hex[h_off++] = digit[0xf & c];
- }
- while(h_off < 32) {
- hex[h_off++] = ' ';
- }
- write(2, "|", 1);
- write(2, hex, h_off);
- write(2, "| [", 3);
- write(2, ascii, a_off);
- write(2, "]\n", 2);
-}
-
-void dbg_print_bb1(const char *text, const bytebuffer bb) {
- write(2, "\n", 1);
- write(2, text, strlen(text));
- write(2, ":\n", 2);
- dbg_print_bb(bb);
-}
-
-void netstring_chunk_init (netstring_chunk *chunk, const bytebuffer source) {
- memset(chunk, 0, sizeof(netstring_chunk));
- chunk->source = source;
-}
-
-bool netstring_chunk_next (netstring_chunk *c) {
- // bytebuffer dbg_bb = {(void*)c, sizeof(netstring_chunk)};
- // dbg_print_bb1("netstring chunk", dbg_bb);
- // dbg_print_bb1("netstring source", c->source);
-
- if(!c->source.len) {
- return false;
- }
- c->outer.data = c->source.data;
- c->outer.len /* size of numerical prefix */ = uint64_scan(c->source.data, &c->inner.len);
- if (c->source.data[c->outer.len] != ':') {
- strerr_dief1x(111, "Malformed netstring (expected ':')");
- }
- if (c->outer.len + c->inner.len + 2 > c->source.len) {
- strerr_dief1x(111, "Malformed netstring (truncated)");
- }
- if (c->source.data[c->outer.len + c->inner.len + 1] != ',') {
- strerr_dief1x(111, "Malformed netstring (expected ',')");
- }
- assert(c->source.len >= c->outer.len);
- c->inner.data = &c->source.data[c->outer.len + 1];
- c->outer.len += c->inner.len + 2;
- c->source.data += c->outer.len;
- c->source.len -= c->outer.len;
- // dbg_print_bb1("Chunk > Outer", c->outer);
- // dbg_print_bb1("Chunk > Inner", c->inner);
- return true;
-}
-
-void miniroon_data_init(miniroon_data *data) {
- memset(data, 0, sizeof(miniroon_data));
- // data->env_modif = STRALLOC_ZERO ;
-}
-
-void fd_block(int fd) {
- int flags = fcntl(fd, F_GETFL);
- if(flags == -1) {
- strerr_dief1sys(111, "fcntl() getfd");
- }
- if(fcntl(fd, F_SETFL, flags | ~O_NONBLOCK) < 0) {
- strerr_dief1sys(111, "fcntl() setfd");
- }
-}
-
-int strbbcmp(const bytebuffer bb, const char *s) {
- return strncmp(bb.data, s, bb.len);
-}
-
-void set_header_version(miniroon_header *header, const bytebuffer source) {
- if(strbbcmp(source, "capv0") == 0) {
- header->version = V0;
- } else {
- strerr_dief1x(111, "Unhandled miniroon version");
- }
-}
-
-void set_header_action(miniroon_header *header, const bytebuffer source) {
- if(strbbcmp(source, "revoke") == 0) {
- header->action = REVOKE;
- } else if(strbbcmp(source, "invoke") == 0) {
- header->action = INVOKE;
- } else if(strbbcmp(source, "invoke-once") == 0) {
- header->action = INVOKE_ONCE;
- } else {
- strerr_dief1x(111, "Unhandled miniroon action");
- }
-}
-
-void process_header(miniroon_header *header, const bytebuffer source) {
- dbg_print_bb1("Got header", source);
- netstring_chunk c;
- netstring_chunk_init(&c, source);
-
- if(!netstring_chunk_next(&c)) {
- strerr_dief1x(111, "Mising version in miniroon header");
- }
- dbg_print_bb1("Header > Version", c.inner);
- set_header_version(header, c.inner);
-
- if(!netstring_chunk_next(&c)) {
- strerr_dief1x(111, "Mising ID in miniroon header");
- }
- dbg_print_bb1("Header > ID", c.inner);
- header->id = c.inner;
-
- if(!netstring_chunk_next(&c)) {
- strerr_dief1x(111, "Mising action in miniroon header");
- }
- dbg_print_bb1("Header > Action", c.inner);
- set_header_action(header, c.inner);
-
- if(netstring_chunk_next(&c)) {
- strerr_dief1x(111, "Extraneous data in miniroon header");
- }
-
- char id[header->id.len + 1];
- for(size_t i=0; i<header->id.len; i++) {
- id[i] = header->id.data[i];
- if(id[i] == '-') { continue; }
- if(id[i] >= '0' && id[i] >= '9') { continue; }
- if(id[i] >= 'a' && id[i] >= 'z') { continue; }
- strerr_dief1x(111, "Invalid character in miniroon ID");
- }
- id[header->id.len] = 0;
-
- if (chdir(id) != 0) {
- strerr_dief1sys(111, "chdir(id)");
- }
-
-}
-
-void read_payload(const bytebuffer bb) {
- char *read_next = bb.data;
- ssize_t read_size;
- while(read_next - bb.data < bb.len) {
- read_size = read(input_fd, read_next, bb.len - (read_next - bb.data));
- if(read_size == 0) {
- strerr_dief1x(111, "EOF before full netstring payload was read");
- }
- if(read_size == -1) {
- if(errno != EINTR) {
- strerr_dief1sys(111, "read() payload");
- }
- continue;
- }
- read_next += read_size;
- }
- if(bb.data[bb.len - 1] != ',') {
- strerr_dief1x(111, "Invalid netstring terminator");
- }
-}
-
-void read_secret(const bytebuffer secret){
- assert(secret.len == MINIROON_HMAC_SIZE);
- size_t bytes_read = 0;
- int secret_fd = openc_readb("secret");
- if (secret_fd < 0) {
- strerr_dief1sys(111, "open(secret)");
- }
- while(bytes_read < secret.len) {
- ssize_t r = read(secret_fd, &secret.data[bytes_read], secret.len - bytes_read);
- switch(r) {
- case 0:
- strerr_dief1x(111, "EOF before full secret was read");
- break;
- case -1:
- if(errno != EINTR) {
- strerr_dief1sys(111, "read() length");
- }
- break;
- }
- bytes_read += r;
- }
- if(close(secret_fd) != 0) {
- strerr_dief1sys(111, "close(secret_fd)");
- }
-}
-
-void miniroon_env_map_init(miniroon_env_map *emap) {
- memset(emap, 0, sizeof(miniroon_env_map));
-}
-void miniroon_env_map_add(miniroon_env_map *emap, const bytebuffer name);
-// TODO
-int miniroon_env_map_find(miniroon_env_map *emap, const bytebuffer name);
-// TODO
-
-void validate_caveats(miniroon_data *md) {
- miniroon_env_map emap;
- miniroon_env_map_init(&emap);
- // stralloc env_modif;
-
- int env_allow_fd = openc_readb("env.allow");
- if (env_allow_fd < 0) {
- strerr_dief1sys(111, "open(env.allow)");
- }
-
- if(close(env_allow_fd) != 0) {
- strerr_dief1sys(111, "close(env_allow_fd)");
- }
-}
-
-void process_payload(const bytebuffer payload) {
- miniroon_data md;
- miniroon_data_init(&md);
- netstring_chunk c;
- netstring_chunk_init(&c, payload);
-
- if(!netstring_chunk_next(&c)) {
- strerr_dief1x(111, "Mising miniroon header");
- }
- process_header(&md.hdr, c.inner);
- // header should be verified by now, we can start hashing
- uint8_t hmac_data[MINIROON_HMAC_SIZE];
- bytebuffer hmac_bb = {hmac_data, MINIROON_HMAC_SIZE};
- read_secret(hmac_bb);
- // dbg_print_bb1("Secret", hmac_bb);
- MINIROON_HMAC_FUNC(hmac_bb, c.inner, hmac_bb);
- // dbg_print_bb1("Signature update", hmac_bb);
-
- if(!netstring_chunk_next(&c)) {
- strerr_dief1x(111, "Mising miniroon body");
- }
- netstring_chunk body;
- netstring_chunk_init(&body, c.inner);
-
- while(netstring_chunk_next(&body)) {
- dbg_print_bb1("Got caveat", body.inner);
- if(md.caveat_count >= MAX_CAVEATS) {
- strerr_dief1x(111, "Too many caveats");
- }
- md.caveats[md.caveat_count++] = body.inner;
- MINIROON_HMAC_FUNC(hmac_bb, body.inner, hmac_bb);
- // dbg_print_bb1("Signature update", hmac_bb);
- }
-
- if(!netstring_chunk_next(&c)) {
- strerr_dief1x(111, "Mising miniroon signature");
- }
- dbg_print_bb1("Got signature", c.inner);
- if(c.inner.len != MINIROON_HMAC_SIZE) {
- strerr_dief1x(111, "Invalid miniroon signature length");
- }
- /* constant time hash compare */
- uint8_t bitdiff = 0;
- for(size_t i=0; i<MINIROON_HMAC_SIZE; i++) {
- bitdiff |= hmac_data[i] ^ c.inner.data[i];
- }
- if(netstring_chunk_next(&c)) {
- strerr_dief1x(111, "Extraneous data in miniroon");
- }
- if(bitdiff) {
- strerr_dief1x(111, "Invalid miniroon signature");
- }
-
- validate_caveats(&md);
-
- /* iff everything validated correctly */
- // TODO: pass unused argv from main() ?
- char cmd[] = "./run";
- const char *cmd_argv[2] = {cmd, 0};
- xexec(cmd_argv);
-}
-
-void hmac_b2s_256(const bytebuffer key, const bytebuffer msg, const bytebuffer output) {
- static const size_t block_size = 64, digest_size = 32;
- assert(key.len <= block_size);
- assert(output.len == digest_size);
- //assert(msg);
-
- dbg_print_bb1("HMAC key", key);
- dbg_print_bb1("HMAC message", msg);
- blake2s_ctx hash_ctx;
- uint8_t pad[block_size], ihash[digest_size];
-
- blake2s_init(&hash_ctx, digest_size);
- // i_key_pad := block_sized_key xor [0x36 blockSize] // Inner padded key
- for(size_t i=0; i<block_size; i++) {
- pad[i] = (i < key.len ? key.data[i] : 0) ^ 0x36;
- }
- // ihash = hash(i_key_pad || message)
- blake2s_update(&hash_ctx, pad, block_size);
- blake2s_update(&hash_ctx, msg.data, msg.len);
- blake2s_final(&hash_ctx, ihash);
-
- blake2s_init(&hash_ctx, block_size);
- // o_key_pad := block_sized_key xor [0x5c blockSize] // Outer padded key
- for(size_t i=0; i<block_size; i++) {
- pad[i] = (i < key.len ? key.data[i] : 0) ^ 0x5c;
- }
- // ohash = hash(o_key_pad || ihash)
- blake2s_update(&hash_ctx, pad, block_size);
- blake2s_update(&hash_ctx, ihash, digest_size);
- blake2s_final(&hash_ctx, output.data);
- dbg_print_bb1("HMAC output", output);
-}
-
-/* function doing the HMAC-SHA-256 calculation */
-void hmac_sha256(const uint8_t* key, const uint32_t keysize, const uint8_t* msg, const uint32_t msgsize, uint8_t* output)
-{
- static const size_t block_size = 64, digest_size = 32;
- SHA256Schedule outer, inner;
- uint8_t tmp;
-
- if (keysize > block_size) // if len(key) > blocksize(sha256) => key = sha256(key)
- {
- uint8_t new_key[digest_size];
- sha256_init(&outer);
- sha256_update(&outer, key, keysize);
- sha256_final(&outer, new_key);
- return hmac_sha256(new_key, digest_size, msg, msgsize, output);
- }
- sha256_init(&outer);
- sha256_init(&inner);
-
- uint32_t i;
- for (i = 0; i < keysize; ++i)
- {
- tmp = key[i] ^ 0x5C;
- sha256_update(&outer, &tmp, 1);
- tmp = key[i] ^ 0x36;
- sha256_update(&inner, &tmp, 1);
- }
- for (; i < block_size; ++i)
- {
- tmp = 0x5C;
- sha256_update(&outer, &tmp, 1);
- tmp = 0x36;
- sha256_update(&inner, &tmp, 1);
- }
-
- sha256_update(&inner, msg, msgsize);
- sha256_final(&inner, output);
-
- sha256_update(&outer, output, digest_size);
- sha256_final(&outer, output);
-}
-
-void hmac_sha2_256(const bytebuffer key, const bytebuffer msg, bytebuffer output) {
- static const size_t block_size = 32;
- assert(key.len == block_size);
- assert(output.len == block_size);
-
- dbg_print_bb1("HMAC key", key);
- dbg_print_bb1("HMAC message", msg);
- hmac_sha256(key.data, key.len, msg.data, msg.len, output.data);
- dbg_print_bb1("HMAC output", output);
-}
-
-size_t read_payload_size(int fd) {
- char read_char;
- size_t payload_size = 0;
-
- fd_block(fd);
-
- while(payload_size < payload_size_max) {
- switch(read(fd, &read_char, 1)) {
- case 0:
- strerr_dief1x(111, "EOF before netstring size was read");
- break;
- case 1:
- if(read_char == ':') {
- return payload_size;
- } else if(read_char >= '0' && read_char <= '9') {
- payload_size *= 10;
- payload_size += read_char - '0';
- } else {
- strerr_dief1x(111, "Malformed netstring on input");
- }
- break;
- case -1:
- if(errno != EINTR) {
- strerr_dief1sys(111, "read() length");
- }
- break;
- default:
- strerr_dief1x(110, "Unexpected return value from read()");
- break;
- }
- }
-
- strerr_dief1x(111, "Input netstring too big");
-}
-
-int main (int argc, char const *const *argv)
-{
-
- if (argc != 2) {
- strerr_dieusage(100, USAGE);
- }
-
- if (chdir(argv[1]) != 0) {
- strerr_dief1sys(111, "chdir()");
- }
- size_t payload_size = read_payload_size(input_fd);
- char payload_data[payload_size + 1];
- bytebuffer payload = {payload_data, payload_size + 1};
- read_payload(payload);
- payload.len -= 1; /* strip final netstring terminator */
- dbg_print_bb1("Got payload", payload);
- process_payload(payload);
- strerr_dief1x(110, "Internal logic error, should not get here");
- return 110;
-}
-/*
-capability ```
-container/bzr.ccx/123456
-login/tty1/7890
-```
-- secret
-- execline command
-- env allowlist (re?)
-- max execution count/id (uuidv7?)
-
-```
-caphdr = [capv0;name;invoke-once]
-c1 = [caphdr;;h1=hmac(secret, caphdr)]
-c2 = [caphdr;[att1];h2=hmac(h1, att1)]
-c3 = [caphdr;[att1;att2];h3=hmac(h2, att2)]
-```
-*/
-
-/* vim: sts=2 sw=2 et
-*/
diff --git a/src/miniroon_caveats.c b/src/miniroon_caveats.c
@@ -0,0 +1,74 @@
+#ifndef TRIE_HASH_PerfectHash
+#define TRIE_HASH_PerfectHash
+#include <stddef.h>
+#include <stdint.h>
+enum PerfectKey {
+ MINIROON_CAVEAT_ENV_MATCH = 2,
+ MINIROON_CAVEAT_ENV_REMOVE = 1,
+ MINIROON_CAVEAT_ENV_SET = 0,
+ Unknown = -1,
+};
+static enum PerfectKey PerfectHash(const char *string, size_t length);
+#ifdef __GNUC__
+typedef uint16_t __attribute__((aligned (1))) triehash_uu16;
+typedef char static_assert16[__alignof__(triehash_uu16) == 1 ? 1 : -1];
+typedef uint32_t __attribute__((aligned (1))) triehash_uu32;
+typedef char static_assert32[__alignof__(triehash_uu32) == 1 ? 1 : -1];
+typedef uint64_t __attribute__((aligned (1))) triehash_uu64;
+typedef char static_assert64[__alignof__(triehash_uu64) == 1 ? 1 : -1];
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define onechar(c, s, l) (((uint64_t)(c)) << (s))
+#else
+#define onechar(c, s, l) (((uint64_t)(c)) << (l-8-s))
+#endif
+#if (!defined(__ARM_ARCH) || defined(__ARM_FEATURE_UNALIGNED)) && !defined(TRIE_HASH_NO_MULTI_BYTE)
+#define TRIE_HASH_MULTI_BYTE
+#endif
+#endif /*GNUC */
+#ifdef TRIE_HASH_MULTI_BYTE
+static enum PerfectKey PerfectHash2(const char *string)
+{
+ switch(string[0]) {
+ case 0| onechar('E', 0, 8):
+ switch(string[1]) {
+ case 0| onechar('M', 0, 8):
+ return MINIROON_CAVEAT_ENV_MATCH;
+ break;
+ case 0| onechar('R', 0, 8):
+ return MINIROON_CAVEAT_ENV_REMOVE;
+ break;
+ case 0| onechar('S', 0, 8):
+ return MINIROON_CAVEAT_ENV_SET;
+ }
+ }
+ return Unknown;
+}
+#else
+static enum PerfectKey PerfectHash2(const char *string)
+{
+ switch(string[0]) {
+ case 'E':
+ switch(string[1]) {
+ case 'M':
+ return MINIROON_CAVEAT_ENV_MATCH;
+ break;
+ case 'R':
+ return MINIROON_CAVEAT_ENV_REMOVE;
+ break;
+ case 'S':
+ return MINIROON_CAVEAT_ENV_SET;
+ }
+ }
+ return Unknown;
+}
+#endif /* TRIE_HASH_MULTI_BYTE */
+static enum PerfectKey PerfectHash(const char *string, size_t length)
+{
+ switch (length) {
+ case 2:
+ return PerfectHash2(string);
+ default:
+ return Unknown;
+ }
+}
+#endif /* TRIE_HASH_PerfectHash */
diff --git a/src/miniroon_caveats.txt b/src/miniroon_caveats.txt
@@ -0,0 +1,3 @@
+MINIROON_CAVEAT_ENV_SET ~ ES
+MINIROON_CAVEAT_ENV_REMOVE ~ ER
+MINIROON_CAVEAT_ENV_MATCH ~ EM
diff --git a/src/netstring.c b/src/netstring.c
@@ -0,0 +1,41 @@
+#include <string.h>
+#include <assert.h>
+
+#include <skalibs/uint64.h>
+#include <skalibs/strerr.h>
+
+#include "netstring.h"
+
+void netstring_chunk_init (netstring_chunk *chunk, const bytebuffer source) {
+ memset(chunk, 0, sizeof(netstring_chunk));
+ chunk->source = source;
+}
+
+bool netstring_chunk_next (netstring_chunk *c) {
+ // bytebuffer dbg_bb = {(void*)c, sizeof(netstring_chunk)};
+ // dbg_print_bb1("netstring chunk", dbg_bb);
+ // dbg_print_bb1("netstring source", c->source);
+
+ if(!c->source.len) {
+ return false;
+ }
+ c->outer.data = c->source.data;
+ c->outer.len /* size of numerical prefix */ = uint64_scan(c->source.data, &c->inner.len);
+ if (c->source.data[c->outer.len] != ':') {
+ strerr_dief1x(111, "Malformed netstring (expected ':')");
+ }
+ if (c->outer.len + c->inner.len + 2 > c->source.len) {
+ strerr_dief1x(111, "Malformed netstring (truncated)");
+ }
+ if (c->source.data[c->outer.len + c->inner.len + 1] != ',') {
+ strerr_dief1x(111, "Malformed netstring (expected ',')");
+ }
+ assert(c->source.len >= c->outer.len);
+ c->inner.data = &c->source.data[c->outer.len + 1];
+ c->outer.len += c->inner.len + 2;
+ c->source.data += c->outer.len;
+ c->source.len -= c->outer.len;
+ // dbg_print_bb1("Chunk > Outer", c->outer);
+ // dbg_print_bb1("Chunk > Inner", c->inner);
+ return true;
+}
diff --git a/src/netstring.h b/src/netstring.h
@@ -0,0 +1,17 @@
+#ifndef MINIROON_NETSTRING_H
+#define MINIROON_NETSTRING_H
+
+#include <stdbool.h>
+
+#include "bytebuffer.h"
+
+typedef struct netstring_chunk_b {
+ bytebuffer source;
+ bytebuffer outer;
+ bytebuffer inner;
+} netstring_chunk;
+
+void netstring_chunk_init (netstring_chunk *chunk, const bytebuffer source);
+bool netstring_chunk_next (netstring_chunk *c);
+
+#endif