chibipub

experimental activitypub node in C
git clone git://jb55.com/chibipub
Log | Files | Refs | README | LICENSE

sigcheck.c (14722B)


      1 
      2 #define SCRATCH_SIZE 134217728
      3 #define MAX_PARALLEL 10
      4 
      5 #include <sys/mman.h>
      6 #include <sys/stat.h>
      7 #include <sys/types.h>
      8 #include <stdlib.h>
      9 #include <assert.h>
     10 #include <unistd.h>
     11 #include <openssl/rsa.h>
     12 #include <openssl/pem.h>
     13 
     14 #include "json.h"
     15 #include "debug.h"
     16 #include "ubjson.h"
     17 #include "hash.h"
     18 #include "hex.h"
     19 #include "base64.h"
     20 #include "io.h"
     21 #include "sigcheck.h"
     22 #include "util.h"
     23 #include "inbox.h"
     24 #include "errors.h"
     25 
     26 #include "sha256/sha256.h"
     27 
     28 #include <ctype.h>
     29 #include <curl/curl.h>
     30 
     31 struct keyid_pubkey {
     32 	unsigned char *data;
     33 	int len;
     34 };
     35 
     36 enum key_writer_flags {
     37 	KW_FAILED = 1 << 0,
     38 	KW_STARTED = 1 << 1,
     39 };
     40 
     41 struct key_writer {
     42 	CURL *curl;
     43 	struct cursor *arena;
     44 	struct keyid_pubkey *pubkey;
     45 	const char *keyid;
     46 	struct cursor slice;
     47 	struct errors errs;
     48 	int flags;
     49 };
     50 
     51 static int is_delete_activity(struct ubjson *ubjson)
     52 {
     53 	struct json val;
     54 	static const char *type_path[] = {"type"};
     55 
     56 	if (!ubjson_lookup(ubjson, type_path, ARRAY_SIZE(type_path), &val)) {
     57 		printf("unusual: couldn't determine object type\n");
     58 		return 1;
     59 	}
     60 
     61 	return val.type == JSON_STRING && !memcmp(val.string, "Delete", val.len);
     62 }
     63 
     64 
     65 static int get_cached_pubkey(const char *keyid, int keyid_len,
     66 		struct cursor *arena, unsigned char **pubkey, int *pubkey_size)
     67 {
     68 	unsigned char hash[32];
     69 	char hash_str[64];
     70 	char path[2048] = {0};
     71 	int ok;
     72 
     73 	// no objects dir, definitely don't have any cached pubkeys
     74 	if (!dir_exists(".chibipub/objects")) {
     75 		return 0;
     76 	}
     77 
     78 	hashdata32((unsigned char *)keyid, keyid_len, hash, sizeof(hash));
     79 	if (!hex_bytes(hash, 32, hash_str, 64)) {
     80 		assert(0);
     81 	}
     82 
     83 	sprintf(path, ".chibipub/objects/%c%c/%.*s",
     84 			hash_str[0], hash_str[1], 64, hash_str);
     85 
     86 	if (access(path, F_OK)) {
     87 		debug("key cache '%s' doesn't exist", path);
     88 		return 0;
     89 	}
     90 
     91 	*pubkey = arena->p;
     92 	ok = read_file(path, arena->p, arena->end - arena->p, pubkey_size);
     93 
     94 	arena->p += *pubkey_size;
     95 	return ok;
     96 }
     97 
     98 static int verify_signature_rsa(unsigned char *pubkey, int pubkey_len,
     99 		unsigned char *sig, int sig_len,
    100 		unsigned char *sigbuf, int sigbuf_len)
    101 {
    102 	unsigned char hash[32];
    103 
    104 	RSA *rsa = NULL;
    105 	BIO *keybio;
    106 
    107 	keybio = BIO_new_mem_buf((void*)pubkey, pubkey_len);
    108 	if (!keybio) {
    109 		return 0;
    110 	}
    111 
    112 	rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa, 0, 0);
    113 
    114 	if (!rsa) {
    115 		fprintf(stderr, "verify_signature_rsa: PEM read failed\n");
    116 		return 0;
    117 	}
    118 
    119 	sha256_hash(hash, sigbuf, sigbuf_len);
    120 
    121 	return RSA_verify(NID_sha256, hash, 32, sig, sig_len, rsa);
    122 }
    123 
    124 static int verify_signature(struct cursor cur, struct cursor arena)
    125 {
    126 	struct ubjson ubjson;
    127 	struct json sigbuf, keyid, sig;
    128 	unsigned char *pubkey, *sig_data;
    129 	int pubkey_size;
    130 	size_t sig_len;
    131 
    132 	init_ubjson(&ubjson, cur.start, cur.p - cur.start);
    133 	ubjson.data_end = cur.p;
    134 
    135 	if (is_delete_activity(&ubjson))
    136 		return 2;
    137 
    138 	static const char *sig_path[] = {"@wssig"};
    139 	if (!ubjson_lookup(&ubjson, sig_path, ARRAY_SIZE(sig_path), &sig)) {
    140 		note_error(&ubjson.errs, "@wssig field not found");
    141 		return 0;
    142 	}
    143 
    144 	static const char *sb_path[] = {"@wssigbuf"};
    145 	if (!ubjson_lookup(&ubjson, sb_path, ARRAY_SIZE(sb_path), &sigbuf)) {
    146 		note_error(&ubjson.errs, "@wssigbuf field not found");
    147 		return 0;
    148 	}
    149 
    150 	static const char *keyid_path[] = {"@wskeyid"};
    151 	if (!ubjson_lookup(&ubjson, keyid_path, ARRAY_SIZE(keyid_path), &keyid)) {
    152 		note_error(&ubjson.errs, "keyid not fond");
    153 		return 0;
    154 	}
    155 
    156 	if (!get_cached_pubkey(keyid.string, keyid.len, &arena, &pubkey, &pubkey_size)) {
    157 		note_error(&ubjson.errs, "no cached pubkey for '%.*s'", keyid.len, keyid.string);
    158 		return 0;
    159 	}
    160 
    161 	sig_data = arena.p;
    162 	if (!base64_decode((const unsigned char*)sig.string,
    163 			   sig.len, arena.p, arena.end - arena.p,
    164 			   &sig_len)) {
    165 		note_error(&ubjson.errs, "base64 decode signature");
    166 		return 0;
    167 	}
    168 
    169 	return verify_signature_rsa(
    170 			pubkey, pubkey_size,
    171 			sig_data, sig_len,
    172 			(unsigned char*)sigbuf.string, sigbuf.len);
    173 }
    174 
    175 static inline int push_keyid(struct cursor *c, const char *keyid, int keyid_len)
    176 {
    177 	struct keyid_pubkey pubkey = {0};
    178 	return push_int(c, keyid_len)
    179 		&& push_data(c, (unsigned char *)keyid, keyid_len)
    180 		&& push_byte(c, 0)
    181 		&& push_data(c, (unsigned char *)&pubkey, sizeof(pubkey)); // reserved for pubkey data index
    182 }
    183 
    184 static int pull_keyid(struct cursor *c, const char **keyid_str, int *keyid_len,
    185 		struct keyid_pubkey **pubkey)
    186 {
    187 	if (!pull_int(c, keyid_len)) {
    188 		return 0;
    189 	}
    190 
    191 	*keyid_str = (const char*)c->p;
    192 	c->p += (*keyid_len) + 1; /* plus zero */
    193 	*pubkey = (struct keyid_pubkey*)c->p;
    194 	c->p += sizeof(struct keyid_pubkey);
    195 
    196 	return 1;
    197 }
    198 
    199 static int has_keyid(struct cursor *keyids, const char *keyid, int keyid_len)
    200 {
    201 	struct cursor scan;
    202 	int size;
    203 	const char *keyid_str;
    204 	struct keyid_pubkey *offset;
    205 
    206 	scan.start = keyids->start;
    207 	scan.p = keyids->start;
    208 	scan.end = keyids->p;
    209 
    210 	while (scan.p < scan.end) {
    211 		if (!pull_keyid(&scan, &keyid_str, &size, &offset)) {
    212 			return 0;
    213 		}
    214 		if (size != keyid_len) {
    215 			continue;
    216 		}
    217 		if (!memcmp(keyid, keyid_str, keyid_len)) {
    218 			return 1;
    219 		}
    220 	}
    221 
    222 	return 0;
    223 }
    224 
    225 /*
    226 static int print_keyids(struct cursor keyids)
    227 {
    228 	int size;
    229 	const char *keyid_str;
    230 	struct keyid_pubkey *pubkey;
    231 
    232 	keyids.end = keyids.p;
    233 	keyids.p = keyids.start;
    234 
    235 	printf("keyids\n");
    236 	while (keyids.p < keyids.end) {
    237 		if (!pull_keyid(&keyids, &keyid_str, &size, &pubkey))
    238 			return 0;
    239 		printf("%.*s\n", size, keyid_str);
    240 	}
    241 
    242 	return 1;
    243 }
    244 */
    245 
    246 static int gather_keyids(unsigned char *json, int json_len,
    247 		struct cursor *arena, struct cursor *keyids_cur)
    248 {
    249 	static const char *path[] = {"@wskeyid"};
    250 	const int ubjson_mem_size = 1024 * 256;
    251 
    252 	struct ubjson ubjson;
    253 	struct json val;
    254 	struct json_parser parser;
    255 	struct json_handlers handlers;
    256 	unsigned char *ubjson_mem;
    257 
    258 	ubjson_mem = cursor_alloc(arena, ubjson_mem_size);
    259 	make_cursor(arena->p, arena->end, keyids_cur);
    260 	init_ubjson(&ubjson, ubjson_mem, ubjson_mem_size);
    261 	make_ubjson_handlers(&handlers, &ubjson.cur);
    262 	init_json_parser(&parser, json, json_len, &handlers);
    263 
    264 	while (parse_json(&parser)) {
    265 		init_ubjson(&ubjson, ubjson_mem, ubjson_mem_size);
    266 		ubjson.data_end = ubjson.cur.p;
    267 
    268 		if (is_delete_activity(&ubjson))
    269 			continue;
    270 
    271 		if (!ubjson_lookup(&ubjson, path, ARRAY_SIZE(path), &val)) {
    272 			note_error(&parser.errs, "@wskeyid not found");
    273 			return 0;
    274 		}
    275 
    276 		if (has_keyid(keyids_cur, val.string, val.len)) {
    277 			continue;
    278 		}
    279 
    280 		if (!push_keyid(keyids_cur, val.string, val.len)) {
    281 			note_error(&parser.errs, "push_keyid");
    282 			return 0;
    283 		}
    284 	}
    285 
    286 	keyids_cur->end = keyids_cur->p;
    287 	keyids_cur->p = keyids_cur->start;
    288 	arena->p = keyids_cur->end;
    289 
    290 	return 1;
    291 }
    292 
    293 static size_t write_cb(char *data, size_t n, size_t l, void *userp)
    294 {
    295 	double dcl;
    296 	int cl;
    297 	int res;
    298 	size_t size = n*l;
    299 	struct key_writer *writer = (struct key_writer *)userp;
    300 
    301 	if (!(writer->flags & KW_STARTED)) {
    302 		res = curl_easy_getinfo(writer->curl,
    303 				CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dcl);
    304 		if (res) {
    305 			fprintf(stderr, "'%s' download failed, content-length unknown\n",
    306 					writer->keyid);
    307 			writer->flags |= KW_FAILED;
    308 			return 0;
    309 		}
    310 
    311 		cl = (int)dcl;
    312 		if (cl == -1) {
    313 			cl = 1024*1024;
    314 		}
    315 
    316 		writer->pubkey->data = cursor_alloc(writer->arena, cl);
    317 		if (!writer->pubkey->data) {
    318 			fprintf(stderr, "cursor_alloc failed, remaining (%ld) + %d (%ld total)\n",
    319 				writer->arena->end - writer->arena->p,
    320 				cl, writer->arena->end - writer->arena->start);
    321 			writer->flags |= KW_FAILED;
    322 			return 0;
    323 		}
    324 
    325 		writer->flags |= KW_STARTED;
    326 		writer->pubkey->len = 0;
    327 		make_cursor(writer->pubkey->data, writer->pubkey->data + cl,
    328 				&writer->slice);
    329 	}
    330 
    331 	if (!push_data(&writer->slice, (unsigned char*)data, size)) {
    332 		writer->flags |= KW_FAILED;
    333 		return 0;
    334 	}
    335 
    336 	writer->pubkey->len += size;
    337 
    338 	return size;
    339 }
    340 
    341 static int add_signature_transfer(CURLM *cm, struct key_writer *writer)
    342 {
    343 	struct curl_slist *list = NULL;
    344 
    345 	CURL *eh = curl_easy_init();
    346 	writer->curl = eh;
    347 	curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_cb);
    348 	curl_easy_setopt(eh, CURLOPT_WRITEDATA, writer);
    349 	curl_easy_setopt(eh, CURLOPT_URL, writer->keyid);
    350 	curl_easy_setopt(eh, CURLOPT_PRIVATE, writer);
    351 
    352 	list = curl_slist_append(list, "Accept: application/ld+json");
    353 	curl_easy_setopt(eh, CURLOPT_HTTPHEADER, list);
    354 
    355 	curl_multi_add_handle(cm, eh);
    356 
    357 	return 1;
    358 }
    359 
    360 static void prepare_transfers(CURLM **cm)
    361 {
    362 	curl_global_init(CURL_GLOBAL_ALL);
    363 	*cm = curl_multi_init();
    364 	curl_multi_setopt(*cm, CURLMOPT_MAXCONNECTS, (long)10);
    365 }
    366 
    367 static int get_keyid_hash(const char *keyid, struct cursor *arena,
    368 		char **hash_str)
    369 {
    370 	unsigned char *hash;
    371 
    372 	if (!(*hash_str = cursor_alloc(arena, 65))) {
    373 		return 0;
    374 	}
    375 
    376 	if (!(hash = cursor_alloc(arena, 32))) {
    377 		return 0;
    378 	}
    379 
    380 	hashdata32((unsigned char*)keyid, strlen(keyid), hash, 32);
    381 	if (!hex_bytes(hash, 32, *hash_str, 64)) {
    382 		return 0;
    383 	}
    384 
    385 	arena->p -= 32;
    386 
    387 	return 1;
    388 }
    389 
    390 static int write_pubkey_cache(struct key_writer *writer, struct cursor arena)
    391 {
    392 	char *hash_str;
    393 	char *path;
    394 
    395 	if (!get_keyid_hash(writer->keyid, &arena, &hash_str)) {
    396 		note_error(&writer->errs, "get_keyid_hash");
    397 		return 0;
    398 	}
    399 
    400 	path = (char*)arena.p;
    401 
    402 	if (!(push_str(&arena, ".chibipub/objects/") &&
    403 		push_sized_str(&arena, hash_str, 2) &&
    404 		push_byte(&arena, 0) &&
    405 		mkdirp(path)))
    406 	{
    407 		note_error(&writer->errs, "mkdir -p '%s'", path);
    408 		return 0;
    409 	}
    410 
    411 	// continue path
    412 	arena.p--;
    413 
    414 	if (!(push_byte(&arena,'/') &&
    415 		push_sized_str(&arena, hash_str, 64) &&
    416 		push_byte(&arena, 0)))
    417 	{
    418 		note_error(&writer->errs, "push path oob");
    419 		return 0;
    420 	}
    421 
    422 	if (!write_file(path, (unsigned char*)writer->pubkey->data,
    423 				writer->pubkey->len)) {
    424 		note_error(&writer->errs, "write_file '%s' pubkey failed", path);
    425 		return 0;
    426 	}
    427 
    428 	return 1;
    429 }
    430 
    431 static int handle_pubkey_response(struct cursor arena,
    432 		struct key_writer *writer, unsigned char *json, int json_len)
    433 {
    434 	unsigned char *start;
    435 	struct ubjson ubjson;
    436 	struct json_handlers handler;
    437 	struct json_parser parser;
    438 	struct json val;
    439 	static const char *path[] = {"publicKey", "publicKeyPem"};
    440 
    441 	start = arena.p;
    442 
    443 	make_ubjson_handlers(&handler, &arena);
    444 	init_json_parser(&parser, json, json_len, &handler);
    445 	if (!parse_json(&parser)) {
    446 		note_error(&writer->errs, "keyid '%s' activity json parse failed",
    447 				writer->keyid);
    448 		return 0;
    449 	}
    450 
    451 	init_ubjson(&ubjson, start, arena.p - start);
    452 	ubjson.data_end = arena.p;
    453 
    454 	if (!ubjson_lookup(&ubjson, path, ARRAY_SIZE(path), &val)) {
    455 		note_error(&writer->errs,
    456 			"keyid '%s' could not find 'publicKeyPem' field",
    457 			writer->keyid);
    458 		return 0;
    459 	}
    460 
    461 	writer->pubkey->data = (unsigned char*)val.string;
    462 	writer->pubkey->len = val.len;
    463 
    464 	if (!write_pubkey_cache(writer, arena)) {
    465 		note_error(&writer->errs, "failed to write pubkey");
    466 		return 0;
    467 	}
    468 
    469 	return 1;
    470 }
    471 
    472 static int handle_transfer_msg(struct cursor *arena, CURLM *cm, CURLMsg *msg, int *transfers,
    473 		struct key_writer *to_fetch, int n_to_fetch)
    474 {
    475 	struct key_writer *writer;
    476 	int ok = 1;
    477 
    478 	if (msg->msg == CURLMSG_DONE) {
    479 		CURL *e = msg->easy_handle;
    480 
    481 		curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &writer);
    482 
    483 		fprintf(stderr, "R: %d - %s <%s>\n",
    484 				msg->data.result,
    485 				curl_easy_strerror(msg->data.result),
    486 				writer->keyid);
    487 
    488 		if (msg->data.result == CURLE_OK) {
    489 			ok = handle_pubkey_response(*arena, writer,
    490 					writer->pubkey->data,
    491 					writer->pubkey->len);
    492 		}
    493 
    494 		curl_multi_remove_handle(cm, e);
    495 		curl_easy_cleanup(e);
    496 	} else {
    497 		fprintf(stderr, "E: CURLMsg (%d)\n", msg->msg);
    498 	}
    499 
    500 	if (*transfers < n_to_fetch) {
    501 		add_signature_transfer(cm, &to_fetch[*transfers]);
    502 		*transfers = *transfers + 1;
    503 	}
    504 
    505 	return ok;
    506 }
    507 
    508 static int perform_transfers(struct cursor *arena, CURLM *cm, int transfers,
    509 		struct key_writer *to_fetch, int n_to_fetch)
    510 {
    511 	CURLMsg *msg;
    512 	int still_alive = 1;
    513 	int msgs_left = -1;
    514 
    515 	do {
    516 		curl_multi_perform(cm, &still_alive);
    517 
    518 		while ((msg = curl_multi_info_read(cm, &msgs_left))) {
    519 			handle_transfer_msg(arena,
    520 				cm, msg, &transfers, to_fetch, n_to_fetch);
    521 		}
    522 
    523 		if (still_alive) {
    524 			curl_multi_wait(cm, NULL, 0, 1000, NULL);
    525 		}
    526 	} while (still_alive || (transfers < n_to_fetch));
    527 
    528 	curl_multi_cleanup(cm);
    529 	curl_global_cleanup();
    530 
    531 	return 1;
    532 }
    533 
    534 static int count_keyids(struct cursor scan, int *count)
    535 {
    536 	int size;
    537 	const char *keyid_str;
    538 	struct keyid_pubkey *pubkey;
    539 
    540 	*count = 0;
    541 
    542 	while (scan.p < scan.end) {
    543 		if (!pull_keyid(&scan, &keyid_str, &size, &pubkey)) {
    544 			return 0;
    545 		}
    546 		*count = *count + 1;
    547 	}
    548 
    549 	return 1;
    550 }
    551 
    552 static int fetch_signatures(unsigned char *json, int json_len,
    553 		struct cursor *arena)
    554 {
    555 	struct cursor keyids;
    556 	struct cursor scan;
    557 	int size, transfers;
    558 	int n_to_fetch, n_keyids;
    559 	const char *keyid_str;
    560 	struct key_writer *to_fetch, *kw;
    561 	struct keyid_pubkey *pubkey;
    562 	CURLM *cm;
    563 	n_to_fetch = 0;
    564 
    565 	if (!gather_keyids(json, json_len, arena, &keyids)) {
    566 		return 0;
    567 	}
    568 
    569 	prepare_transfers(&cm);
    570 
    571 	scan.p = scan.start = keyids.start;
    572 	scan.end = keyids.end;
    573 
    574 	if (!count_keyids(scan, &n_keyids)) {
    575 		return 0;
    576 	}
    577 
    578 	if (!(to_fetch = cursor_alloc(arena, n_keyids * sizeof(struct key_writer)))) {
    579 		return 0;
    580 	}
    581 
    582 	while (scan.p < scan.end) {
    583 
    584 		if (!pull_keyid(&scan, &keyid_str, &size, &pubkey)) {
    585 			return 0;
    586 		}
    587 
    588 		if (get_cached_pubkey(keyid_str, strlen(keyid_str), arena,
    589 					&pubkey->data, &pubkey->len)) {
    590 			/*
    591 			printf("got cached pubkey of size %d... do something\n",
    592 					pubkey->len);
    593 					*/
    594 			continue;
    595 		}
    596 
    597 		pubkey->len = 0;
    598 
    599 		kw = &to_fetch[n_to_fetch++];
    600 		init_errors(&kw->errs);
    601 		kw->keyid = keyid_str;
    602 		kw->arena = arena;
    603 		kw->pubkey = pubkey;
    604 		kw->flags = 0;
    605 	}
    606 
    607 	for (transfers = 0; transfers < MAX_PARALLEL && transfers < n_to_fetch; transfers++) {
    608 		add_signature_transfer(cm, &to_fetch[transfers]);
    609 	}
    610 
    611 	if (!perform_transfers(arena, cm, transfers, to_fetch, n_to_fetch)) {
    612 		return 0;
    613 	}
    614 
    615 	return 1;
    616 }
    617 
    618 int sigcheck(struct sigcheck *check)
    619 {
    620 	int count = 0, res;
    621 	unsigned char *p, *start, *scratch;
    622 	size_t flen;
    623 	struct json_parser jsonp;
    624 	struct json_handlers handlers;
    625 	struct cursor out_cur;
    626 
    627 	scratch = malloc(SCRATCH_SIZE);
    628 	map_file(check->activity_file, &p, &flen);
    629 
    630 	init_json_handlers(&handlers);
    631 	make_cursor(scratch, scratch + SCRATCH_SIZE, &out_cur);
    632 	make_ubjson_handlers(&handlers, &out_cur);
    633 	init_json_parser(&jsonp, p, flen, &handlers);
    634 
    635 	if (!fetch_signatures(p, flen, &out_cur)) {
    636 		note_error(&jsonp.errs, "fatal error fetching signature");
    637 		return 0;
    638 	}
    639 
    640 	make_cursor(scratch, scratch + SCRATCH_SIZE, &out_cur);
    641 	make_ubjson_handlers(&handlers, &out_cur);
    642 	init_json_parser(&jsonp, p, flen, &handlers);
    643 
    644 	start = out_cur.p;
    645 	while (parse_json(&jsonp)) {
    646 		count++;
    647 		fprintf(stderr, "[%d] parse success\r", count);
    648 
    649 		res = verify_signature(out_cur, out_cur);
    650 
    651 		if (res == 0) {
    652 			printf("bad signature #%d\n", count);
    653 		} else if (res == 1) {
    654 			printf("good signature #%d\r", count);
    655 		}
    656 
    657 		// overwrite last ubjson
    658 		out_cur.p = start;
    659 	}
    660 
    661 	printf("\n");
    662 
    663 	munmap(p, flen);
    664 
    665 	return 1;
    666 }