miniroon

Simplistic macaroon-based authorization for Unix systems
git clone https://ccx.te2000.cz/git/miniroon
Log | Files | Refs

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:
M.gitignore | 1+
Msrc/Makefile | 31++++++++++++++++++++-----------
Asrc/bytebuffer.c | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/bytebuffer.h | 16++++++++++++++++
Asrc/hmac_b2s_256.c | 37+++++++++++++++++++++++++++++++++++++
Asrc/hmac_b2s_256.h | 8++++++++
Asrc/hmac_sha2_256.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/hmac_sha2_256.h | 8++++++++
Asrc/miniroon-header.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/miniroon-header.h | 21+++++++++++++++++++++
Asrc/miniroon-read.c | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/miniroon-verify.c | 245+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/miniroon.c | 537-------------------------------------------------------------------------------
Asrc/miniroon_caveats.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/miniroon_caveats.txt | 3+++
Asrc/netstring.c | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/netstring.h | 17+++++++++++++++++
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