commit deca2e241bc1d924cc914e6c8e995f42f7e31bd7
parent 8044145b1a5e7bd1d4b18c167f8c1b8081d39c50
Author: William Casarin <jb55@jb55.com>
Date: Sun, 14 Feb 2021 15:42:14 -0800
sigcheck: pubkey fetching actually working
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
M | src/io.c | | | 53 | +++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/io.h | | | 3 | +++ |
M | src/sigcheck.c | | | 141 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ |
M | src/ubjson.c | | | 34 | +++++++++++++++++++++++++++++++--- |
4 files changed, 208 insertions(+), 23 deletions(-)
diff --git a/src/io.c b/src/io.c
@@ -2,6 +2,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
+#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
@@ -22,6 +23,27 @@ int map_file(const char *filename, unsigned char **p, size_t *flen)
return *p != MAP_FAILED;
}
+int write_file(const char *filename, unsigned char *out, size_t len)
+{
+ FILE *file = NULL;
+ size_t written;
+
+ file = fopen(filename, "wb");
+ if (file == NULL)
+ return 0;
+
+ written = fwrite(out, 1, len, file);
+
+ if (written != len) {
+ fprintf(stderr, "written != len, %ld != %ld\n", written, len);
+ fclose(file);
+ return 0;
+ }
+
+ fclose(file);
+ return 1;
+}
+
int read_fd(FILE *fd, unsigned char *buf, int buflen, int *written)
{
unsigned char *p = buf;
@@ -49,3 +71,34 @@ int read_file(const char *filename, unsigned char *out, int out_len, int *writte
return read_fd(file, out, out_len, written);
}
+
+int dir_exists(const char *path)
+{
+ struct stat st;
+
+ if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+// todo: make this less dumb
+int mkdirp(const char *path)
+{
+ int status;
+
+ switch (fork()) {
+ case -1:
+ return 0;
+ case 0:
+ /* child, run man command. */
+ execlp("mkdir", "mkdir", "-p", path, (char *)NULL);
+ return 0;
+ default:
+ break;
+ }
+
+ wait(&status);
+ return WIFEXITED(status) && WEXITSTATUS(status) == 0;
+}
diff --git a/src/io.h b/src/io.h
@@ -9,5 +9,8 @@ int map_file(const char *filename, unsigned char **p, size_t *flen);
int read_fd(FILE *fd, unsigned char *buf, int buflen, int *written);
int read_file(const char *filename, unsigned char *out, int out_len, int *written);
+int write_file(const char *filename, unsigned char *data, size_t len);
+int dir_exists(const char *path);
+int mkdirp(const char *path);
#endif /* CHIBIPUB_IO */
diff --git a/src/sigcheck.c b/src/sigcheck.c
@@ -37,6 +37,7 @@ struct key_writer {
struct keyid_pubkey *pubkey;
const char *keyid;
struct cursor slice;
+ struct errors errs;
int flags;
};
@@ -172,7 +173,7 @@ static int gather_keyids(unsigned char *json, int json_len,
return 1;
}
-static int hex_bytes(unsigned char *bytes, int n_bytes, unsigned char *buf,
+static int hex_bytes(unsigned char *bytes, int n_bytes, char *buf,
int buf_size)
{
static const char *hex = "0123456789abcdef";
@@ -190,25 +191,18 @@ static int hex_bytes(unsigned char *bytes, int n_bytes, unsigned char *buf,
return 1;
}
-static int get_cached_pubkey(unsigned char *keyid, int size,
- unsigned char *buf, int buflen, int *pubkey_size)
+static int get_cached_pubkey(const char *keyid, unsigned char *buf, int buflen, int *pubkey_size)
{
unsigned char hash[32];
- unsigned char hash_str[64];
- char path[128] = {0};
- mode_t mode = 0777;
+ char hash_str[64];
+ char path[2048] = {0};
- // if we sucessfully created a directory, then we definitely
- // don't have any cached pubkeys
- if (0 == mkdir(".chibipub", mode)) {
+ // no objects dir, definitely don't have any cached pubkeys
+ if (!dir_exists(".chibipub/objects")) {
return 0;
}
- if (0 == mkdir(".chibipub/objects", mode)) {
- return 0;
- }
-
- blake3_hash(keyid, size, hash);
+ blake3_hash((unsigned char *)keyid, strlen(keyid), hash);
if (!hex_bytes(hash, 32, hash_str, 64)) {
assert(0);
}
@@ -276,12 +270,18 @@ static size_t write_cb(char *data, size_t n, size_t l, void *userp)
static int add_signature_transfer(CURLM *cm, struct key_writer *writer)
{
+ struct curl_slist *list = NULL;
+
CURL *eh = curl_easy_init();
writer->curl = eh;
curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(eh, CURLOPT_WRITEDATA, writer);
curl_easy_setopt(eh, CURLOPT_URL, writer->keyid);
curl_easy_setopt(eh, CURLOPT_PRIVATE, writer);
+
+ list = curl_slist_append(list, "Accept: application/ld+json");
+ curl_easy_setopt(eh, CURLOPT_HTTPHEADER, list);
+
curl_multi_add_handle(cm, eh);
return 1;
@@ -294,10 +294,104 @@ static void prepare_transfers(CURLM **cm)
curl_multi_setopt(*cm, CURLMOPT_MAXCONNECTS, (long)10);
}
-static int handle_transfer_msg(CURLM *cm, CURLMsg *msg, int *transfers,
+static int write_pubkey_cache(struct key_writer *writer, struct cursor arena)
+{
+ unsigned char *hash;
+ char *hash_str;
+ char *path;
+
+ if (!(hash = cursor_alloc(&arena, 32))) {
+ return 0;
+ }
+
+ blake3_hash((unsigned char*)writer->keyid, strlen(writer->keyid), hash);
+
+ if (!(hash_str = cursor_alloc(&arena, 64))) {
+ note_error(&writer->errs, "alloc hash_str");
+ return 0;
+ }
+
+ if (!hex_bytes(hash, 32, hash_str, 64)) {
+ note_error(&writer->errs, "hex_bytes");
+ return 0;
+ }
+
+ path = (char*)arena.p;
+
+ if (!(push_str(&arena, ".chibipub/objects/") &&
+ push_sized_str(&arena, hash_str, 2) &&
+ push_byte(&arena, 0) &&
+ mkdirp(path)))
+ {
+ note_error(&writer->errs, "mkdir -p '%s'", path);
+ return 0;
+ }
+
+ // continue path
+ arena.p--;
+
+ if (!(push_byte(&arena,'/') &&
+ push_sized_str(&arena, hash_str, 64) &&
+ push_byte(&arena, 0)))
+ {
+ note_error(&writer->errs, "push path oob");
+ return 0;
+ }
+
+ if (!write_file(path, writer->pubkey->data, writer->pubkey->len)) {
+ note_error(&writer->errs, "write_file '%s' pubkey failed", path);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int handle_pubkey_response(struct cursor arena,
+ struct key_writer *writer, unsigned char *json, int json_len)
+{
+ unsigned char *start;
+ struct ubjson ubjson;
+ struct json_handlers handler;
+ struct json_parser parser;
+ struct json val;
+ static const char *path[] = {"publicKey", "publicKeyPem"};
+
+ start = arena.p;
+
+ make_ubjson_handlers(&handler, &arena);
+ init_json_parser(&parser, json, json_len, &handler);
+ if (!parse_json(&parser)) {
+ note_error(&writer->errs, "keyid '%s' activity json parse failed",
+ writer->keyid);
+ return 0;
+ }
+
+ init_ubjson(&ubjson, start, arena.p - start);
+ ubjson.data_end = arena.p;
+
+ if (!ubjson_lookup(&ubjson, path, ARRAY_SIZE(path), &val)) {
+ note_error(&writer->errs,
+ "keyid '%s' could not find 'publicKeyPem' field",
+ writer->keyid);
+ return 0;
+ }
+
+ writer->pubkey->data = (unsigned char*)val.string;
+ writer->pubkey->len = val.len;
+
+ if (!write_pubkey_cache(writer, arena)) {
+ note_error(&writer->errs, "failed to write pubkey");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int handle_transfer_msg(struct cursor *arena, CURLM *cm, CURLMsg *msg, int *transfers,
struct key_writer *to_fetch, int n_to_fetch)
{
struct key_writer *writer;
+ int ok = 1;
if (msg->msg == CURLMSG_DONE) {
CURL *e = msg->easy_handle;
@@ -309,6 +403,12 @@ static int handle_transfer_msg(CURLM *cm, CURLMsg *msg, int *transfers,
curl_easy_strerror(msg->data.result),
writer->keyid);
+ if (msg->data.result == CURLE_OK) {
+ ok = handle_pubkey_response(*arena, writer,
+ writer->pubkey->data,
+ writer->pubkey->len);
+ }
+
curl_multi_remove_handle(cm, e);
curl_easy_cleanup(e);
} else {
@@ -320,10 +420,10 @@ static int handle_transfer_msg(CURLM *cm, CURLMsg *msg, int *transfers,
*transfers = *transfers + 1;
}
- return 1;
+ return ok;
}
-static int perform_transfers(CURLM *cm, int transfers,
+static int perform_transfers(struct cursor *arena, CURLM *cm, int transfers,
struct key_writer *to_fetch, int n_to_fetch)
{
CURLMsg *msg;
@@ -334,7 +434,7 @@ static int perform_transfers(CURLM *cm, int transfers,
curl_multi_perform(cm, &still_alive);
while ((msg = curl_multi_info_read(cm, &msgs_left))) {
- handle_transfer_msg(
+ handle_transfer_msg(arena,
cm, msg, &transfers, to_fetch, n_to_fetch);
}
@@ -403,7 +503,7 @@ static int fetch_signatures(unsigned char *json, int json_len,
return 0;
}
- if (get_cached_pubkey(scan.p, size, arena->p,
+ if (get_cached_pubkey(keyid_str, arena->p,
arena->end - arena->p,
&pubkey->len)) {
pubkey->data = arena->p;
@@ -417,6 +517,7 @@ static int fetch_signatures(unsigned char *json, int json_len,
pubkey->len = 0;
kw = &to_fetch[n_to_fetch++];
+ init_errors(&kw->errs);
kw->keyid = keyid_str;
kw->arena = arena;
kw->pubkey = pubkey;
@@ -427,7 +528,7 @@ static int fetch_signatures(unsigned char *json, int json_len,
add_signature_transfer(cm, &to_fetch[transfers]);
}
- if (!perform_transfers(cm, transfers, to_fetch, n_to_fetch)) {
+ if (!perform_transfers(arena, cm, transfers, to_fetch, n_to_fetch)) {
return 0;
}
diff --git a/src/ubjson.c b/src/ubjson.c
@@ -161,16 +161,32 @@ static int count_skipped(unsigned char *text, int size)
return skipped;
}
+static char escaped_char(char c)
+{
+ switch (c) {
+ case 'n': return '\n';
+ case 't': return '\t';
+ case 'r': return '\r';
+ case 'b': return '\b';
+ case 'f': return '\f';
+ }
+ return c;
+}
+
static int push_unescaped(struct cursor *cur, unsigned char *text, int size)
{
unsigned char *p;
+ unsigned char c;
for (p = text; p < text+size; p++) {
if (*p == '\\' && (p+1 < text+size) && is_escape_char(*(p+1))) {
p++;
+ c = (unsigned char)escaped_char((char)*p);
+ } else {
+ c = *p;
}
- if (!push_byte(cur, *p)) {
+ if (!push_byte(cur, c)) {
return 0;
}
}
@@ -217,7 +233,7 @@ static inline void copy_ubjson(struct ubjson *src, struct ubjson *dst) {
dst->data_end = src->data_end;
}
-static int parse_ubjson_object(struct ubjson *ubjson, struct json *val)
+static int parse_ubjson_container(struct ubjson *ubjson, struct json *val, char token)
{
char c;
c = 0;
@@ -226,7 +242,7 @@ static int parse_ubjson_object(struct ubjson *ubjson, struct json *val)
assert(ubjson != &val->container);
copy_ubjson(ubjson, &val->container);
- if (!parse_char(&ubjson->cur, &c, '{')) {
+ if (!parse_char(&ubjson->cur, &c, token)) {
note_error(&ubjson->errs, "expected '{' tag, got '%c'", c);
return 0;
}
@@ -250,6 +266,16 @@ static int parse_ubjson_object(struct ubjson *ubjson, struct json *val)
return 1;
}
+static inline int parse_ubjson_object(struct ubjson *ubjson, struct json *val)
+{
+ return parse_ubjson_container(ubjson, val, '{');
+}
+
+static inline int parse_ubjson_array(struct ubjson *ubjson, struct json *val)
+{
+ return parse_ubjson_container(ubjson, val, '[');
+}
+
static int parse_ubjson_number(struct ubjson *ubjson, struct json *val)
{
if (!parse_ubjson_sized_len(ubjson, &val->number_int, &val->len)) {
@@ -297,6 +323,8 @@ int parse_ubjson_value(struct ubjson *ubjson, struct json *val)
return parse_ubjson_string(ubjson, val);
} else if (tag == '{') {
return parse_ubjson_object(ubjson, val);
+ } else if (tag == '[') {
+ return parse_ubjson_array(ubjson, val);
} else if (is_number_tag(tag)) {
return parse_ubjson_number(ubjson, val);
} else if (tag == 'F' || tag == 'T') {