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 */