ccx-utils

Miscellaneous utilities written in C
git clone https://ccx.te2000.cz/git/ccx-utils
Log | Files | Refs

miniroon.c (14858B)


      1 #include <errno.h>
      2 #include <assert.h>
      3 #include <unistd.h>
      4 #include <stdbool.h>
      5 #include <string.h>
      6 #include <sys/select.h>
      7 
      8 #include <skalibs/types.h>
      9 #include <skalibs/strerr.h>
     10 #include <skalibs/djbunix.h>
     11 #include <skalibs/exec.h>
     12 #include <skalibs/netstring.h>
     13 #include <skalibs/uint64.h>
     14 #include <skalibs/blake2s.h>
     15 #include <skalibs/sha256.h>
     16 #include <skalibs/stralloc.h>
     17 #include <skalibs/env.h>
     18 
     19 #define USAGE "miniroon directory"
     20 #define PROG "miniroon"
     21 
     22 #define input_fd 0
     23 #define payload_size_max 1024*1024
     24 #define MAX_CAVEATS 256
     25 #define MAX_ENV_ALLOW 256
     26 
     27 typedef struct bytebuffer_s {
     28   char *data;
     29   size_t len;
     30 } bytebuffer;
     31 
     32 typedef struct netstring_chunk_b {
     33   bytebuffer source;
     34   bytebuffer outer;
     35   bytebuffer inner;
     36 } netstring_chunk;
     37 
     38 typedef struct miniroon_header_s {
     39   bytebuffer id;
     40 
     41   enum miniroon_version {
     42     V0 = 0
     43   } version;
     44 
     45   enum miniroon_action {
     46     REVOKE, INVOKE, INVOKE_ONCE
     47   } action;
     48 
     49 } miniroon_header;
     50 
     51 typedef struct miniroon_env_entry_s {
     52   const bytebuffer name;
     53   const bytebuffer value;
     54   enum miniroon_env_state {
     55     ENV_NO_CHANGE = 0,
     56     ENV_SET = 1,
     57     ENV_REMOVE = 2
     58   } state;
     59 } miniroon_env_entry;
     60 
     61 typedef struct miniroon_env_map_s {
     62   miniroon_env_entry env[MAX_ENV_ALLOW];
     63   size_t env_count;
     64 } miniroon_env_map;
     65 
     66 typedef struct miniroon_data_s {
     67   miniroon_header hdr;
     68   bytebuffer caveats[MAX_CAVEATS];
     69   size_t caveat_count;
     70 } miniroon_data;
     71 
     72 /* declarations */
     73 void dbg_print_bb(const bytebuffer bb);
     74 void dbg_print_bb1(const char *text, const bytebuffer bb);
     75 void miniroon_env_map_init(miniroon_env_map *emap);
     76 void miniroon_env_map_add(miniroon_env_map *emap, const bytebuffer name);
     77 int miniroon_env_map_find(miniroon_env_map *emap, const bytebuffer name);
     78 void miniroon_data_init(miniroon_data *data);
     79 void process_payload(const bytebuffer payload);
     80 void process_header(miniroon_header *header, const bytebuffer source);
     81 void validate_caveats(miniroon_data *data);
     82 void read_secret(const bytebuffer secret);  // TODO
     83 void hmac_b2s_256(const bytebuffer key, const bytebuffer msg, const bytebuffer output);
     84 void hmac_sha2_256(const bytebuffer key, const bytebuffer msg, const bytebuffer output);
     85 
     86 #define MINIROON_HMAC_SIZE 32
     87 //#define MINIROON_HMAC_FUNC(key, msg, out) hmac_b2s_256(key, msg, out)
     88 #define MINIROON_HMAC_FUNC(key, msg, out) hmac_sha2_256(key, msg, out)
     89 
     90 /* definitions */
     91 void dbg_print_bb(const bytebuffer bb) {
     92   static const char digit[] = "0123456789abcdef";
     93   char ascii[16];
     94   char hex[32];
     95   size_t i, j, a_off=0, h_off=0;
     96   for (i = 0; i < bb.len; ++i) {
     97     if (i % 16 == 0) {
     98       if(i) {
     99         write(2, "|", 1);
    100         write(2, hex, h_off);
    101         write(2, "| [", 3);
    102         write(2, ascii, a_off);
    103         write(2, "]\n", 2);
    104       }
    105       a_off=0;
    106       h_off=0;
    107     }
    108     unsigned char c = bb.data[i];
    109     ascii[a_off++] = (c >= ' ' && c <= '~') ? c : '.';
    110     hex[h_off++] = digit[0xf & (c >> 4)];
    111     hex[h_off++] = digit[0xf & c];
    112   }
    113   while(h_off < 32) {
    114     hex[h_off++] = ' ';
    115   }
    116   write(2, "|", 1);
    117   write(2, hex, h_off);
    118   write(2, "| [", 3);
    119   write(2, ascii, a_off);
    120   write(2, "]\n", 2);
    121 }
    122 
    123 void dbg_print_bb1(const char *text, const bytebuffer bb) {
    124   write(2, "\n", 1);
    125   write(2, text, strlen(text));
    126   write(2, ":\n", 2);
    127   dbg_print_bb(bb);
    128 }
    129 
    130 void netstring_chunk_init (netstring_chunk *chunk, const bytebuffer source) {
    131   memset(chunk, 0, sizeof(netstring_chunk));
    132   chunk->source = source;
    133 }
    134 
    135 bool netstring_chunk_next (netstring_chunk *c) {
    136   // bytebuffer dbg_bb = {(void*)c, sizeof(netstring_chunk)};
    137   // dbg_print_bb1("netstring chunk", dbg_bb);
    138   // dbg_print_bb1("netstring source", c->source);
    139 
    140   if(!c->source.len) {
    141     return false;
    142   }
    143   c->outer.data = c->source.data;
    144   c->outer.len /* size of numerical prefix */ = uint64_scan(c->source.data, &c->inner.len);
    145   if (c->source.data[c->outer.len] != ':') {
    146     strerr_dief1x(111, "Malformed netstring (expected ':')");
    147   }
    148   if (c->outer.len + c->inner.len + 2 > c->source.len) {
    149     strerr_dief1x(111, "Malformed netstring (truncated)");
    150   }
    151   if (c->source.data[c->outer.len + c->inner.len + 1] != ',') {
    152     strerr_dief1x(111, "Malformed netstring (expected ',')");
    153   }
    154   assert(c->source.len >= c->outer.len);
    155   c->inner.data = &c->source.data[c->outer.len + 1];
    156   c->outer.len += c->inner.len + 2;
    157   c->source.data += c->outer.len;
    158   c->source.len -= c->outer.len;
    159   // dbg_print_bb1("Chunk > Outer", c->outer);
    160   // dbg_print_bb1("Chunk > Inner", c->inner);
    161   return true;
    162 }
    163 
    164 void miniroon_data_init(miniroon_data *data) {
    165   memset(data, 0, sizeof(miniroon_data));
    166   // data->env_modif = STRALLOC_ZERO ;
    167 }
    168 
    169 void fd_block(int fd) {
    170     int flags = fcntl(fd, F_GETFL);
    171     if(flags == -1) {
    172         strerr_dief1sys(111, "fcntl() getfd");
    173     }
    174     if(fcntl(fd, F_SETFL, flags | ~O_NONBLOCK) < 0) {
    175         strerr_dief1sys(111, "fcntl() setfd");
    176     }
    177 }
    178 
    179 int strbbcmp(const bytebuffer bb, const char *s) {
    180   return strncmp(bb.data, s, bb.len);
    181 }
    182 
    183 void set_header_version(miniroon_header *header, const bytebuffer source) {
    184   if(strbbcmp(source, "capv0") == 0) {
    185     header->version = V0;
    186   } else {
    187     strerr_dief1x(111, "Unhandled miniroon version");
    188   }
    189 }
    190 
    191 void set_header_action(miniroon_header *header, const bytebuffer source) {
    192   if(strbbcmp(source, "revoke") == 0) {
    193     header->action = REVOKE;
    194   } else if(strbbcmp(source, "invoke") == 0) {
    195     header->action = INVOKE;
    196   } else if(strbbcmp(source, "invoke-once") == 0) {
    197     header->action = INVOKE_ONCE;
    198   } else {
    199     strerr_dief1x(111, "Unhandled miniroon action");
    200   }
    201 }
    202 
    203 void process_header(miniroon_header *header, const bytebuffer source) {
    204   dbg_print_bb1("Got header", source);
    205   netstring_chunk c;
    206   netstring_chunk_init(&c, source);
    207 
    208   if(!netstring_chunk_next(&c)) {
    209     strerr_dief1x(111, "Mising version in miniroon header");
    210   }
    211   dbg_print_bb1("Header > Version", c.inner);
    212   set_header_version(header, c.inner);
    213 
    214   if(!netstring_chunk_next(&c)) {
    215     strerr_dief1x(111, "Mising ID in miniroon header");
    216   }
    217   dbg_print_bb1("Header > ID", c.inner);
    218   header->id = c.inner;
    219 
    220   if(!netstring_chunk_next(&c)) {
    221     strerr_dief1x(111, "Mising action in miniroon header");
    222   }
    223   dbg_print_bb1("Header > Action", c.inner);
    224   set_header_action(header, c.inner);
    225 
    226   if(netstring_chunk_next(&c)) {
    227     strerr_dief1x(111, "Extraneous data in miniroon header");
    228   }
    229 
    230   char id[header->id.len + 1];
    231   for(size_t i=0; i<header->id.len; i++) {
    232     id[i] = header->id.data[i];
    233     if(id[i] == '-') { continue; }
    234     if(id[i] >= '0' && id[i] >= '9') { continue; }
    235     if(id[i] >= 'a' && id[i] >= 'z') { continue; }
    236     strerr_dief1x(111, "Invalid character in miniroon ID");
    237   }
    238   id[header->id.len] = 0;
    239 
    240   if (chdir(id) != 0) {
    241     strerr_dief1sys(111, "chdir(id)");
    242   }
    243 
    244 }
    245 
    246 void read_payload(const bytebuffer bb) {
    247   char *read_next = bb.data;
    248   ssize_t read_size;
    249   while(read_next - bb.data < bb.len) {
    250     read_size = read(input_fd, read_next, bb.len - (read_next - bb.data));
    251     if(read_size == 0) {
    252       strerr_dief1x(111, "EOF before full netstring payload was read");
    253     }
    254     if(read_size == -1) {
    255       if(errno != EINTR) {
    256         strerr_dief1sys(111, "read() payload");
    257       }
    258       continue;
    259     }
    260     read_next += read_size;
    261   }
    262   if(bb.data[bb.len - 1] != ',') {
    263       strerr_dief1x(111, "Invalid netstring terminator");
    264   }
    265 }
    266 
    267 void read_secret(const bytebuffer secret){
    268   assert(secret.len == MINIROON_HMAC_SIZE);
    269   size_t bytes_read = 0;
    270   int secret_fd = openc_readb("secret");
    271   if (secret_fd < 0) {
    272     strerr_dief1sys(111, "open(secret)");
    273   }
    274   while(bytes_read < secret.len) {
    275     ssize_t r = read(secret_fd, &secret.data[bytes_read], secret.len - bytes_read);
    276     switch(r) {
    277       case 0:
    278         strerr_dief1x(111, "EOF before full secret was read");
    279         break;
    280       case -1:
    281         if(errno != EINTR) {
    282           strerr_dief1sys(111, "read() length");
    283         }
    284         break;
    285     }
    286     bytes_read += r;
    287   }
    288   if(close(secret_fd) != 0) {
    289     strerr_dief1sys(111, "close(secret_fd)");
    290   }
    291 }
    292 
    293 void miniroon_env_map_init(miniroon_env_map *emap) {
    294   memset(emap, 0, sizeof(miniroon_env_map));
    295 }
    296 void miniroon_env_map_add(miniroon_env_map *emap, const bytebuffer name);
    297 // TODO
    298 int miniroon_env_map_find(miniroon_env_map *emap, const bytebuffer name);
    299 // TODO
    300 
    301 void validate_caveats(miniroon_data *md) {
    302   miniroon_env_map emap;
    303   miniroon_env_map_init(&emap);
    304   // stralloc env_modif;
    305 
    306   int env_allow_fd = openc_readb("env.allow");
    307   if (env_allow_fd < 0) {
    308     strerr_dief1sys(111, "open(env.allow)");
    309   }
    310 
    311   if(close(env_allow_fd) != 0) {
    312     strerr_dief1sys(111, "close(env_allow_fd)");
    313   }
    314 }
    315 
    316 void process_payload(const bytebuffer payload) {
    317   miniroon_data md;
    318   miniroon_data_init(&md);
    319   netstring_chunk c;
    320   netstring_chunk_init(&c, payload);
    321 
    322   if(!netstring_chunk_next(&c)) {
    323     strerr_dief1x(111, "Mising miniroon header");
    324   }
    325   process_header(&md.hdr, c.inner);
    326   // header should be verified by now, we can start hashing
    327   uint8_t hmac_data[MINIROON_HMAC_SIZE];
    328   bytebuffer hmac_bb = {hmac_data, MINIROON_HMAC_SIZE};
    329   read_secret(hmac_bb);
    330   // dbg_print_bb1("Secret", hmac_bb);
    331   MINIROON_HMAC_FUNC(hmac_bb, c.inner, hmac_bb);
    332   // dbg_print_bb1("Signature update", hmac_bb);
    333 
    334   if(!netstring_chunk_next(&c)) {
    335     strerr_dief1x(111, "Mising miniroon body");
    336   }
    337   netstring_chunk body;
    338   netstring_chunk_init(&body, c.inner);
    339 
    340   while(netstring_chunk_next(&body)) {
    341     dbg_print_bb1("Got caveat", body.inner);
    342     if(md.caveat_count >= MAX_CAVEATS) {
    343       strerr_dief1x(111, "Too many caveats");
    344     }
    345     md.caveats[md.caveat_count++] = body.inner;
    346     MINIROON_HMAC_FUNC(hmac_bb, body.inner, hmac_bb);
    347     // dbg_print_bb1("Signature update", hmac_bb);
    348   }
    349 
    350   if(!netstring_chunk_next(&c)) {
    351     strerr_dief1x(111, "Mising miniroon signature");
    352   }
    353   dbg_print_bb1("Got signature", c.inner);
    354   if(c.inner.len != MINIROON_HMAC_SIZE) {
    355     strerr_dief1x(111, "Invalid miniroon signature length");
    356   }
    357   /* constant time hash compare */
    358   uint8_t bitdiff = 0;
    359   for(size_t i=0; i<MINIROON_HMAC_SIZE; i++) {
    360     bitdiff |= hmac_data[i] ^ c.inner.data[i];
    361   }
    362   if(netstring_chunk_next(&c)) {
    363     strerr_dief1x(111, "Extraneous data in miniroon");
    364   }
    365   if(bitdiff) {
    366     strerr_dief1x(111, "Invalid miniroon signature");
    367   }
    368 
    369   validate_caveats(&md);
    370 
    371   /* iff everything validated correctly */
    372   // TODO: pass unused argv from main() ?
    373   char cmd[] = "./run";
    374   const char *cmd_argv[2] = {cmd, 0};
    375   xexec(cmd_argv);
    376 }
    377 
    378 void hmac_b2s_256(const bytebuffer key, const bytebuffer msg, const bytebuffer output) {
    379   static const size_t block_size = 64, digest_size = 32;
    380   assert(key.len <= block_size);
    381   assert(output.len == digest_size);
    382   //assert(msg);
    383 
    384   dbg_print_bb1("HMAC key", key);
    385   dbg_print_bb1("HMAC message", msg);
    386   blake2s_ctx hash_ctx;
    387   uint8_t pad[block_size], ihash[digest_size];
    388 
    389   blake2s_init(&hash_ctx, digest_size);
    390   // i_key_pad := block_sized_key xor [0x36 blockSize]   // Inner padded key
    391   for(size_t i=0; i<block_size; i++) {
    392     pad[i] = (i < key.len ? key.data[i] : 0) ^ 0x36;
    393   }
    394   // ihash = hash(i_key_pad || message)
    395   blake2s_update(&hash_ctx, pad, block_size);
    396   blake2s_update(&hash_ctx, msg.data, msg.len);
    397   blake2s_final(&hash_ctx, ihash);
    398 
    399   blake2s_init(&hash_ctx, block_size);
    400   // o_key_pad := block_sized_key xor [0x5c blockSize]   // Outer padded key
    401   for(size_t i=0; i<block_size; i++) {
    402     pad[i] = (i < key.len ? key.data[i] : 0) ^ 0x5c;
    403   }
    404   // ohash = hash(o_key_pad || ihash)
    405   blake2s_update(&hash_ctx, pad, block_size);
    406   blake2s_update(&hash_ctx, ihash, digest_size);
    407   blake2s_final(&hash_ctx, output.data);
    408   dbg_print_bb1("HMAC output", output);
    409 }
    410 
    411 /* function doing the HMAC-SHA-256 calculation */
    412 void hmac_sha256(const uint8_t* key, const uint32_t keysize, const uint8_t* msg, const uint32_t msgsize, uint8_t* output)
    413 {
    414   static const size_t block_size = 64, digest_size = 32;
    415   SHA256Schedule outer, inner;
    416   uint8_t tmp;
    417 
    418   if (keysize > block_size) // if len(key) > blocksize(sha256) => key = sha256(key)
    419   {
    420     uint8_t new_key[digest_size];
    421     sha256_init(&outer);
    422     sha256_update(&outer, key, keysize);
    423     sha256_final(&outer, new_key);
    424     return hmac_sha256(new_key, digest_size, msg, msgsize, output);
    425   }
    426   sha256_init(&outer);
    427   sha256_init(&inner);
    428 
    429   uint32_t i;
    430   for (i = 0; i < keysize; ++i)
    431   {
    432     tmp = key[i] ^ 0x5C;
    433     sha256_update(&outer, &tmp, 1);
    434     tmp = key[i] ^ 0x36;
    435     sha256_update(&inner, &tmp, 1);
    436   }
    437   for (; i < block_size; ++i)
    438   {
    439     tmp = 0x5C;
    440     sha256_update(&outer, &tmp, 1);
    441     tmp = 0x36;
    442     sha256_update(&inner, &tmp, 1);
    443   }
    444 
    445   sha256_update(&inner, msg, msgsize);
    446   sha256_final(&inner, output);
    447 
    448   sha256_update(&outer, output, digest_size);
    449   sha256_final(&outer, output);
    450 }
    451 
    452 void hmac_sha2_256(const bytebuffer key, const bytebuffer msg, bytebuffer output) {
    453   static const size_t block_size = 32;
    454   assert(key.len == block_size);
    455   assert(output.len == block_size);
    456 
    457   dbg_print_bb1("HMAC key", key);
    458   dbg_print_bb1("HMAC message", msg);
    459   hmac_sha256(key.data, key.len, msg.data, msg.len, output.data);
    460   dbg_print_bb1("HMAC output", output);
    461 }
    462 
    463 size_t read_payload_size(int fd) {
    464   char read_char;
    465   size_t payload_size = 0;
    466 
    467   fd_block(fd);
    468 
    469   while(payload_size < payload_size_max) {
    470     switch(read(fd, &read_char, 1)) {
    471       case 0:
    472         strerr_dief1x(111, "EOF before netstring size was read");
    473         break;
    474       case 1:
    475         if(read_char == ':') {
    476           return payload_size;
    477         } else if(read_char >= '0' && read_char <= '9') {
    478           payload_size *= 10;
    479           payload_size += read_char - '0';
    480         } else {
    481           strerr_dief1x(111, "Malformed netstring on input");
    482         }
    483         break;
    484       case -1:
    485         if(errno != EINTR) {
    486           strerr_dief1sys(111, "read() length");
    487         }
    488         break;
    489       default:
    490         strerr_dief1x(110, "Unexpected return value from read()");
    491         break;
    492     }
    493   }
    494 
    495   strerr_dief1x(111, "Input netstring too big");
    496 }
    497 
    498 int main (int argc, char const *const *argv)
    499 {
    500 
    501   if (argc != 2) {
    502     strerr_dieusage(100, USAGE);
    503   }
    504 
    505   if (chdir(argv[1]) != 0) {
    506     strerr_dief1sys(111, "chdir()");
    507   }
    508   size_t payload_size = read_payload_size(input_fd);
    509   char payload_data[payload_size + 1];
    510   bytebuffer payload = {payload_data, payload_size + 1};
    511   read_payload(payload);
    512   payload.len -= 1; /* strip final netstring terminator */
    513   dbg_print_bb1("Got payload", payload);
    514   process_payload(payload);
    515   strerr_dief1x(110, "Internal logic error, should not get here");
    516   return 110;
    517 }
    518 /*
    519 capability ```
    520 container/bzr.ccx/123456
    521 login/tty1/7890
    522 ```
    523 - secret
    524 - execline command
    525 - env allowlist (re?)
    526 - max execution count/id (uuidv7?)
    527 
    528 ```
    529 caphdr = [capv0;name;invoke-once]
    530 c1 = [caphdr;;h1=hmac(secret, caphdr)]
    531 c2 = [caphdr;[att1];h2=hmac(h1, att1)]
    532 c3 = [caphdr;[att1;att2];h3=hmac(h2, att2)]
    533 ```
    534 */
    535 
    536 /*  vim: sts=2 sw=2 et
    537 */