diff options
-rw-r--r-- | src/or/include.am | 2 | ||||
-rw-r--r-- | src/or/keypin.c | 391 | ||||
-rw-r--r-- | src/or/keypin.h | 44 | ||||
-rw-r--r-- | src/test/include.am | 1 | ||||
-rw-r--r-- | src/test/test.c | 2 | ||||
-rw-r--r-- | src/test/test_keypin.c | 255 |
6 files changed, 695 insertions, 0 deletions
diff --git a/src/or/include.am b/src/or/include.am index a85b0dde05..68f091b3a9 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -53,6 +53,7 @@ LIBTOR_A_SOURCES = \ src/or/entrynodes.c \ src/or/ext_orport.c \ src/or/hibernate.c \ + src/or/keypin.c \ src/or/main.c \ src/or/microdesc.c \ src/or/networkstatus.c \ @@ -161,6 +162,7 @@ ORHEADERS = \ src/or/geoip.h \ src/or/entrynodes.h \ src/or/hibernate.h \ + src/or/keypin.h \ src/or/main.h \ src/or/microdesc.h \ src/or/networkstatus.h \ diff --git a/src/or/keypin.c b/src/or/keypin.c new file mode 100644 index 0000000000..87e49cdff6 --- /dev/null +++ b/src/or/keypin.c @@ -0,0 +1,391 @@ +/* Copyright (c) 2014, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define KEYPIN_PRIVATE + +#include "orconfig.h" +#include "compat.h" +#include "crypto.h" +#include "di_ops.h" +#include "ht.h" +#include "keypin.h" +#include "siphash.h" +#include "torint.h" +#include "torlog.h" +#include "util.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#ifdef _WIN32 +#include <io.h> +#endif + +/** + * @file keypin.c + * @brief Key-pinning for RSA and Ed25519 identity keys at directory + * authorities. + * + * This module implements a key-pinning mechanism to ensure that it's safe + * to use RSA keys as identitifers even as we migrate to Ed25519 keys. It + * remembers, for every Ed25519 key we've seen, what the associated Ed25519 + * key is. This way, if we see a different Ed25519 key with that RSA key, + * we'll know that there's a mismatch. + * + * We persist these entries to disk using a simple format, where each line + * has a base64-encoded RSA SHA1 hash, then a base64-endoded Ed25519 key. + * Empty lines, misformed lines, and lines beginning with # are + * ignored. Lines beginning with @ are reserved for future extensions. + */ + +static int keypin_journal_append_entry(const uint8_t *rsa_id_digest, + const uint8_t *ed25519_id_key); + +static HT_HEAD(rsamap, keypin_ent_st) the_rsa_map = HT_INITIALIZER(); +static HT_HEAD(edmap, keypin_ent_st) the_ed_map = HT_INITIALIZER(); + +/** Hashtable helper: compare two keypin table entries and return true iff + * they have the same RSA key IDs. */ +static INLINE int +keypin_ents_eq_rsa(const keypin_ent_t *a, const keypin_ent_t *b) +{ + return tor_memeq(a->rsa_id, b->rsa_id, sizeof(a->rsa_id)); +} + +/** Hashtable helper: hash a keypin table entries based on its RSA key ID */ +static INLINE unsigned +keypin_ent_hash_rsa(const keypin_ent_t *a) +{ +return (unsigned) siphash24g(a->rsa_id, sizeof(a->rsa_id)); +} + +/** Hashtable helper: compare two keypin table entries and return true iff + * they have the same ed25519 keys */ +static INLINE int +keypin_ents_eq_ed(const keypin_ent_t *a, const keypin_ent_t *b) +{ + return tor_memeq(a->ed25519_key, b->ed25519_key, sizeof(a->ed25519_key)); +} + +/** Hashtable helper: hash a keypin table entries based on its ed25519 key */ +static INLINE unsigned +keypin_ent_hash_ed(const keypin_ent_t *a) +{ +return (unsigned) siphash24g(a->ed25519_key, sizeof(a->ed25519_key)); +} + +HT_PROTOTYPE(rsamap, keypin_ent_st, rsamap_node, keypin_ent_hash_rsa, + keypin_ents_eq_rsa); +HT_GENERATE2(rsamap, keypin_ent_st, rsamap_node, keypin_ent_hash_rsa, + keypin_ents_eq_rsa, 0.6, tor_reallocarray, tor_free_); + +HT_PROTOTYPE(edmap, keypin_ent_st, edmap_node, keypin_ent_hash_ed, + keypin_ents_eq_ed); +HT_GENERATE2(edmap, keypin_ent_st, edmap_node, keypin_ent_hash_ed, + keypin_ents_eq_ed, 0.6, tor_reallocarray, tor_free_); + +/** + * Check whether we already have an entry in the key pinning table for a + * router with RSA ID digest <b>rsa_id_digest</b> or for ed25519 key + * <b>ed25519_id_key</b>. If we have an entry that matches both keys, + * return KEYPIN_FOUND. If we find an entry that matches one key but + * not the other, return KEYPIN_MISMATCH. If we have no entry for either + * key, add such an entry to the table and return KEYPIN_ADDED. + */ +int +keypin_check_and_add(const uint8_t *rsa_id_digest, + const uint8_t *ed25519_id_key) +{ + keypin_ent_t search, *ent; + memset(&search, 0, sizeof(search)); + memcpy(search.rsa_id, rsa_id_digest, sizeof(search.rsa_id)); + memcpy(search.ed25519_key, ed25519_id_key, sizeof(search.ed25519_key)); + + /* Search by RSA key digest first */ + ent = HT_FIND(rsamap, &the_rsa_map, &search); + if (ent) { + tor_assert(fast_memeq(ent->rsa_id, rsa_id_digest, sizeof(ent->rsa_id))); + if (tor_memeq(ent->ed25519_key, ed25519_id_key,sizeof(ent->ed25519_key))) { + return KEYPIN_FOUND; /* Match on both keys. Great. */ + } else { + return KEYPIN_MISMATCH; /* Found RSA with different Ed key */ + } + } + + /* See if we know a different RSA key for this ed key */ + ent = HT_FIND(edmap, &the_ed_map, &search); + if (ent) { + /* If we got here, then the ed key matches and the RSA doesn't */ + tor_assert(fast_memeq(ent->ed25519_key, ed25519_id_key, + sizeof(ent->ed25519_key))); + tor_assert(fast_memneq(ent->rsa_id, rsa_id_digest, sizeof(ent->rsa_id))); + return KEYPIN_MISMATCH; + } + + /* Okay, this one is new to us. */ + ent = tor_memdup(&search, sizeof(search)); + keypin_add_entry_to_map(ent); + keypin_journal_append_entry(rsa_id_digest, ed25519_id_key); + return KEYPIN_ADDED; +} + +/** + * Helper: add <b>ent</b> to the hash tables. + */ +MOCK_IMPL(STATIC void, +keypin_add_entry_to_map, (keypin_ent_t *ent)) +{ + HT_INSERT(rsamap, &the_rsa_map, ent); + HT_INSERT(edmap, &the_ed_map, ent); +} + +/** + * Check whether we already have an entry in the key pinning table for a + * router with RSA ID digest <b>rsa_id_digest</b>. If we have no such entry, + * return KEYPIN_NOT_FOUND. If we find an entry that matches the RSA key but + * which has an ed25519 key, return KEYPIN_MISMATCH. + */ +int +keypin_check_lone_rsa(const uint8_t *rsa_id_digest) +{ + keypin_ent_t search, *ent; + memset(&search, 0, sizeof(search)); + memcpy(search.rsa_id, rsa_id_digest, sizeof(search.rsa_id)); + + /* Search by RSA key digest first */ + ent = HT_FIND(rsamap, &the_rsa_map, &search); + if (ent) { + return KEYPIN_MISMATCH; + } else { + return KEYPIN_NOT_FOUND; + } +} + +/** Open fd to the keypinning journal file. */ +static int keypin_journal_fd = -1; + +/** Open the key-pinning journal to append to <b>fname</b>. Return 0 on + * success, -1 on failure. */ +int +keypin_open_journal(const char *fname) +{ + /* O_SYNC ??*/ + int fd = tor_open_cloexec(fname, O_WRONLY|O_CREAT, 0600); + if (fd < 0) + goto err; + + if (tor_fd_seekend(fd) < 0) + goto err; + + /* Add a newline in case the last line was only partially written */ + if (write(fd, "\n", 1) < 1) + goto err; + + /* Add something about when we opened this file. */ + char buf[80]; + char tbuf[ISO_TIME_LEN+1]; + format_iso_time(tbuf, approx_time()); + tor_snprintf(buf, sizeof(buf), "@opened-at %s\n", tbuf); + if (write_all(fd, buf, strlen(buf), 0) < 0) + goto err; + + keypin_journal_fd = fd; + return 0; + err: + if (fd >= 0) + close(fd); + return -1; +} + +/** Close the keypinning journal file. */ +int +keypin_close_journal(void) +{ + if (keypin_journal_fd >= 0) + close(keypin_journal_fd); + keypin_journal_fd = -1; + return 0; +} + +/** Length of a keypinning journal line, including terminating newline. */ +#define JOURNAL_LINE_LEN (BASE64_DIGEST_LEN + BASE64_DIGEST256_LEN + 2) + +/** Add an entry to the keypinning journal to map <b>rsa_id_digest</b> and + * <b>ed25519_id_key</b>. */ +static int +keypin_journal_append_entry(const uint8_t *rsa_id_digest, + const uint8_t *ed25519_id_key) +{ + if (keypin_journal_fd == -1) + return -1; + char line[JOURNAL_LINE_LEN]; + digest_to_base64(line, (const char*)rsa_id_digest); + line[BASE64_DIGEST_LEN] = ' '; + digest256_to_base64(line + BASE64_DIGEST_LEN + 1, + (const char*)ed25519_id_key); + line[BASE64_DIGEST_LEN+1+BASE64_DIGEST256_LEN] = '\n'; + + if (write_all(keypin_journal_fd, line, JOURNAL_LINE_LEN, 0)<0) { + log_warn(LD_DIRSERV, "Error while adding a line to the key-pinning " + "journal: %s", strerror(errno)); + keypin_close_journal(); + return -1; + } + + return 0; +} + +/** Load a journal from the <b>size</b>-byte region at <b>data</b>. Return 0 + * on success, -1 on failure. */ +STATIC int +keypin_load_journal_impl(const char *data, size_t size) +{ + const char *start = data, *end = data + size, *next; + + int n_corrupt_lines = 0; + int n_entries = 0; + int n_duplicates = 0; + int n_conflicts = 0; + + for (const char *cp = start; cp < end; cp = next) { + const char *eol = memchr(cp, '\n', end-cp); + const char *eos = eol ? eol : end; + const size_t len = eos - cp; + + next = eol ? eol + 1 : end; + + if (len == 0) { + continue; + } + + if (*cp == '@') { + /* Lines that start with @ are reserved. Ignore for now. */ + continue; + } + if (*cp == '#') { + /* Lines that start with # are comments. */ + continue; + } + + /* Is it the right length? (The -1 here is for the newline.) */ + if (len != JOURNAL_LINE_LEN - 1) { + /* Lines with a bad length are corrupt unless they are empty. + * Ignore them either way */ + for (const char *s = cp; s < eos; ++s) { + if (! TOR_ISSPACE(*s)) { + ++n_corrupt_lines; + break; + } + } + continue; + } + + keypin_ent_t *ent = keypin_parse_journal_line(cp); + + if (ent == NULL) { + ++n_corrupt_lines; + continue; + } + + const keypin_ent_t *ent2; + if ((ent2 = HT_FIND(rsamap, &the_rsa_map, ent))) { + if (fast_memeq(ent2->ed25519_key, ent->ed25519_key, DIGEST256_LEN)) { + ++n_duplicates; + } else { + ++n_conflicts; + } + tor_free(ent); + continue; + } else if (HT_FIND(edmap, &the_ed_map, ent)) { + tor_free(ent); + ++n_conflicts; + continue; + } + + keypin_add_entry_to_map(ent); + ++n_entries; + } + + int severity = (n_corrupt_lines || n_duplicates) ? LOG_WARN : LOG_INFO; + tor_log(severity, LD_DIRSERV, + "Loaded %d entries from keypin journal. " + "Found %d corrupt lines, %d duplicates, and %d conflicts.", + n_entries, n_corrupt_lines, n_duplicates, n_conflicts); + + return 0; +} + +/** + * Load a journal from the file called <b>fname</b>. Return 0 on success, + * -1 on failure. + */ +int +keypin_load_journal(const char *fname) +{ + tor_mmap_t *map = tor_mmap_file(fname); + if (!map) { + if (errno == ENOENT) + return 0; + else + return -1; + } + int r = keypin_load_journal_impl(map->data, map->size); + tor_munmap_file(map); + return r; +} + +/** Parse a single keypinning journal line entry from <b>cp</b>. The input + * does not need to be NUL-terminated, but it <em>does</em> need to have + * KEYPIN_JOURNAL_LINE_LEN -1 bytes available to read. Return a new entry + * on success, and NULL on failure. + */ +STATIC keypin_ent_t * +keypin_parse_journal_line(const char *cp) +{ + /* XXXX assumes !USE_OPENSSL_BASE64 */ + keypin_ent_t *ent = tor_malloc_zero(sizeof(keypin_ent_t)); + + if (base64_decode((char*)ent->rsa_id, sizeof(ent->rsa_id), + cp, BASE64_DIGEST_LEN) != DIGEST_LEN || + cp[BASE64_DIGEST_LEN] != ' ' || + base64_decode((char*)ent->ed25519_key, sizeof(ent->ed25519_key), + cp+BASE64_DIGEST_LEN+1, BASE64_DIGEST256_LEN) != DIGEST256_LEN) { + tor_free(ent); + return NULL; + } else { + return ent; + } +} + +/** Remove all entries from the keypinning table.*/ +void +keypin_clear(void) +{ + int bad_entries = 0; + { + keypin_ent_t **ent, **next, *this; + for (ent = HT_START(rsamap, &the_rsa_map); ent != NULL; ent = next) { + this = *ent; + next = HT_NEXT_RMV(rsamap, &the_rsa_map, ent); + + keypin_ent_t *other_ent = HT_REMOVE(edmap, &the_ed_map, this); + bad_entries += (other_ent != this); + + tor_free(this); + } + } + bad_entries += HT_SIZE(&the_ed_map); + + HT_CLEAR(edmap,&the_ed_map); + HT_CLEAR(rsamap,&the_rsa_map); + + if (bad_entries) { + log_warn(LD_BUG, "Found %d discrepencies in the the keypin database.", + bad_entries); + } +} + diff --git a/src/or/keypin.h b/src/or/keypin.h new file mode 100644 index 0000000000..16a0775208 --- /dev/null +++ b/src/or/keypin.h @@ -0,0 +1,44 @@ +/* Copyright (c) 2014, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_KEYPIN_H +#define TOR_KEYPIN_H + +#include "testsupport.h" + +int keypin_check_and_add(const uint8_t *rsa_id_digest, + const uint8_t *ed25519_id_key); + +int keypin_open_journal(const char *fname); +int keypin_close_journal(void); +int keypin_load_journal(const char *fname); +void keypin_clear(void); +int keypin_check_lone_rsa(const uint8_t *rsa_id_digest); + +#define KEYPIN_FOUND 0 +#define KEYPIN_ADDED 1 +#define KEYPIN_MISMATCH -1 +#define KEYPIN_NOT_FOUND -2 + +#ifdef KEYPIN_PRIVATE + +/** + * In-memory representation of a key-pinning table entry. + */ +typedef struct keypin_ent_st { + HT_ENTRY(keypin_ent_st) rsamap_node; + HT_ENTRY(keypin_ent_st) edmap_node; + /** SHA1 hash of the RSA key */ + uint8_t rsa_id[DIGEST_LEN]; + /** Ed2219 key. */ + uint8_t ed25519_key[DIGEST256_LEN]; +} keypin_ent_t; + +STATIC keypin_ent_t * keypin_parse_journal_line(const char *cp); +STATIC int keypin_load_journal_impl(const char *data, size_t size); + +MOCK_DECL(STATIC void, keypin_add_entry_to_map, (keypin_ent_t *ent)); +#endif + +#endif + diff --git a/src/test/include.am b/src/test/include.am index 44f63a7230..b433fc50a9 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -44,6 +44,7 @@ src_test_test_SOURCES = \ src/test/test_extorport.c \ src/test/test_hs.c \ src/test/test_introduce.c \ + src/test/test_keypin.c \ src/test/test_logging.c \ src/test/test_microdesc.c \ src/test/test_nodelist.c \ diff --git a/src/test/test.c b/src/test/test.c index 0524a6978f..cd8aa3f014 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1136,6 +1136,7 @@ extern struct testcase_t guardfraction_tests[]; extern struct testcase_t extorport_tests[]; extern struct testcase_t hs_tests[]; extern struct testcase_t introduce_tests[]; +extern struct testcase_t keypin_tests[]; extern struct testcase_t logging_tests[]; extern struct testcase_t microdesc_tests[]; extern struct testcase_t nodelist_tests[]; @@ -1181,6 +1182,7 @@ struct testgroup_t testgroups[] = { { "extorport/", extorport_tests }, { "hs/", hs_tests }, { "introduce/", introduce_tests }, + { "keypin/", keypin_tests }, { "nodelist/", nodelist_tests }, { "oom/", oom_tests }, { "options/", options_tests }, diff --git a/src/test/test_keypin.c b/src/test/test_keypin.c new file mode 100644 index 0000000000..afd4ca201d --- /dev/null +++ b/src/test/test_keypin.c @@ -0,0 +1,255 @@ +/* Copyright (c) 2014, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" +#define KEYPIN_PRIVATE +#include "or.h" +#include "keypin.h" +#include "util.h" + +#include "test.h" + +static void +test_keypin_parse_line(void *arg) +{ + (void)arg; + keypin_ent_t *ent = NULL; + + /* Good line */ + ent = keypin_parse_journal_line( + "aGVyZSBpcyBhIGdvb2Qgc2hhMSE " + "VGhpcyBlZDI1NTE5IHNjb2ZmcyBhdCB0aGUgc2hhMS4"); + tt_assert(ent); + tt_mem_op(ent->rsa_id, ==, "here is a good sha1!", 20); + tt_mem_op(ent->ed25519_key, ==, "This ed25519 scoffs at the sha1.", 32); + tor_free(ent); ent = NULL; + + /* Good line with extra stuff we will ignore. */ + ent = keypin_parse_journal_line( + "aGVyZSBpcyBhIGdvb2Qgc2hhMSE " + "VGhpcyBlZDI1NTE5IHNjb2ZmcyBhdCB0aGUgc2hhMS4helloworld"); + tt_assert(ent); + tt_mem_op(ent->rsa_id, ==, "here is a good sha1!", 20); + tt_mem_op(ent->ed25519_key, ==, "This ed25519 scoffs at the sha1.", 32); + tor_free(ent); ent = NULL; + + /* Bad line: no space in the middle. */ + ent = keypin_parse_journal_line( + "aGVyZSBpcyBhIGdvb2Qgc2hhMSE?" + "VGhpcyBlZDI1NTE5IHNjb2ZmcyBhdCB0aGUgc2hhMS4"); + tt_assert(! ent); + + /* Bad line: bad base64 in RSA ID */ + ent = keypin_parse_journal_line( + "aGVyZSBpcyBhIGdv!2Qgc2hhMSE " + "VGhpcyBlZDI1NTE5IHNjb2ZmcyBhdCB0aGUgc2hhMS4"); + tt_assert(! ent); + + /* Bad line: bad base64 in Ed25519 */ + ent = keypin_parse_journal_line( + "aGVyZSBpcyBhIGdvb2Qgc2hhMSE " + "VGhpcyBlZDI1NTE5IHNjb2ZmcyB!dCB0aGUgc2hhMS4"); + tt_assert(! ent); + + done: + tor_free(ent); +} + +static smartlist_t *mock_addent_got = NULL; +static void +mock_addent(keypin_ent_t *ent) +{ + smartlist_add(mock_addent_got, ent); + keypin_add_entry_to_map__real(ent); +} + +static void +test_keypin_parse_file(void *arg) +{ + (void)arg; + + mock_addent_got = smartlist_new(); + MOCK(keypin_add_entry_to_map, mock_addent); + + /* Simple, minimal, correct example. */ + const char data1[] = +"PT09PT09PT09PT09PT09PT09PT0 PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0\n" +"TG9yYXggaXBzdW0gZ3J1dnZ1bHU cyB0aG5lZWQgYW1ldCwgc25lcmdlbGx5IG9uY2UtbGU\n" +"ciBsZXJraW0sIHNlZCBkbyBiYXI YmFsb290IHRlbXBvciBnbHVwcGl0dXMgdXQgbGFib3I\n" +"ZSBldCB0cnVmZnVsYSBtYWduYSA YWxpcXVhLiBVdCBlbmltIGFkIGdyaWNrbGUtZ3Jhc3M\n" +"dmVuaWFtLCBxdWlzIG1pZmYtbXU ZmZlcmVkIGdhLXp1bXBjbyBsYWJvcmlzIG5pc2kgdXQ\n" +"Y3J1ZmZ1bHVzIGV4IGVhIHNjaGw b3BwaXR5IGNvbnNlcXVhdC4gRHVpcyBhdXRlIHNuYXI\n" +"Z2dsZSBpbiBzd29tZWVzd2FucyA aW4gdm9sdXB0YXRlIGF4ZS1oYWNrZXIgZXNzZSByaXA\n" +"cHVsdXMgY3J1bW1paSBldSBtb28 ZiBudWxsYSBzbnV2di5QTFVHSFBMT1ZFUlhZWlpZLi4\n"; + + tt_int_op(0, ==, keypin_load_journal_impl(data1, strlen(data1))); + tt_int_op(8, ==, smartlist_len(mock_addent_got)); + keypin_ent_t *ent = smartlist_get(mock_addent_got, 2); + tt_mem_op(ent->rsa_id, ==, "r lerkim, sed do bar", 20); + tt_mem_op(ent->ed25519_key, ==, "baloot tempor gluppitus ut labor", 32); + + /* More complex example: weird lines, bogus lines, + duplicate/conflicting lines */ + const char data2[] = + "PT09PT09PT09PT09PT09PT09PT0 PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0\n" + "# This is a comment.\n" + " \n" + "QXQgdGhlIGVuZCBvZiB0aGUgeWU YXIgS3VycmVta2FybWVycnVrIHNhaWQgdG8gaGltLCA\n" + "IllvdSBoYXZlIG1hZGUgYSBnb28 ZCBiZWdpbm5pbmcuIiBCdXQgbm8gbW9yZS4gV2l6YXI\n" + "\n" + "ZHMgc3BlYWsgdHJ1dGgsIGFuZCA aXQgd2FzIHRydWUgdGhhdCBhbGwgdGhlIG1hc3Rlcgo\n" + "@reserved for a future extension \n" + "eSBvZiBOYW1lcyB0aGF0IEdlZCA aGFkIHRvaWxlZCbyB3aW4gdGhhdCB5ZWFyIHdhcyA\n" + "eSBvZiBOYW1lcyB0aGF0IEdlZCA aGFkIHRvaWxlZCbyB3aW4gdGhhdCB5ZWFyIHdhcy" + "A line too long\n" + "dGhlIG1lcmUgc3RhcnQgb2Ygd2g YXQgaGUgbXVzdCBnbyBvb!BsZWFybmluZy4uLi4uLi4\n" + "ZHMgc3BlYWsgdaJ1dGgsIGFuZCA aXQgd2FzIHRydWUgdGhhdCBhbGwgdGhlIG1hc3Rlcgo\n" + "ZHMgc3BlYWsgdHJ1dGgsIGFuZCA aXQgd2FzIHRydaUgdGhhdCBhbGwgdGhlIG1hc3Rlcgo\n" + ; + + tt_int_op(0, ==, keypin_load_journal_impl(data2, strlen(data2))); + tt_int_op(11, ==, smartlist_len(mock_addent_got)); + ent = smartlist_get(mock_addent_got, 9); + tt_mem_op(ent->rsa_id, ==, "\"You have made a goo", 20); + tt_mem_op(ent->ed25519_key, ==, "d beginning.\" But no more. Wizar", 32); + + ent = smartlist_get(mock_addent_got, 10); + tt_mem_op(ent->rsa_id, ==, "ds speak truth, and ", 20); + tt_mem_op(ent->ed25519_key, ==, "it was true that all the master\n", 32); + + /* File truncated before NL */ + const char data3[] = + "Tm8gZHJhZ29uIGNhbiByZXNpc3Q IHRoZSBmYXNjaW5hdGlvbiBvZiByaWRkbGluZyB0YWw"; + tt_int_op(0, ==, keypin_load_journal_impl(data3, strlen(data3))); + tt_int_op(12, ==, smartlist_len(mock_addent_got)); + ent = smartlist_get(mock_addent_got, 11); + tt_mem_op(ent->rsa_id, ==, "No dragon can resist", 20); + tt_mem_op(ent->ed25519_key, ==, " the fascination of riddling tal", 32); + + done: + keypin_clear(); + smartlist_free(mock_addent_got); +} + +#define ADD(a,b) keypin_check_and_add((const uint8_t*)(a),(const uint8_t*)(b)) +#define LONE_RSA(a) keypin_check_lone_rsa((const uint8_t*)(a)) + +static void +test_keypin_add_entry(void *arg) +{ + (void)arg; + keypin_clear(); + + tt_int_op(KEYPIN_ADDED, ==, ADD("ambassadors-at-large", + "bread-and-butter thing-in-itself")); + tt_int_op(KEYPIN_ADDED, ==, ADD("gentleman-adventurer", + "cloak-and-dagger what's-his-face")); + + tt_int_op(KEYPIN_FOUND, ==, ADD("ambassadors-at-large", + "bread-and-butter thing-in-itself")); + tt_int_op(KEYPIN_FOUND, ==, ADD("ambassadors-at-large", + "bread-and-butter thing-in-itself")); + tt_int_op(KEYPIN_FOUND, ==, ADD("gentleman-adventurer", + "cloak-and-dagger what's-his-face")); + + tt_int_op(KEYPIN_ADDED, ==, ADD("Johnnies-come-lately", + "run-of-the-mill root-mean-square")); + + tt_int_op(KEYPIN_MISMATCH, ==, ADD("gentleman-adventurer", + "hypersentimental closefistedness")); + + tt_int_op(KEYPIN_MISMATCH, ==, ADD("disestablismentarian", + "cloak-and-dagger what's-his-face")); + + tt_int_op(KEYPIN_FOUND, ==, ADD("gentleman-adventurer", + "cloak-and-dagger what's-his-face")); + + tt_int_op(KEYPIN_NOT_FOUND, ==, LONE_RSA("Llanfairpwllgwyngyll")); + tt_int_op(KEYPIN_MISMATCH, ==, LONE_RSA("Johnnies-come-lately")); + + done: + keypin_clear(); +} + +static void +test_keypin_journal(void *arg) +{ + (void)arg; + char *contents = NULL; + const char *fname = get_fname("keypin-journal"); + + tt_int_op(0, ==, keypin_load_journal(fname)); /* ENOENT is okay */ + update_approx_time(1217709000); + tt_int_op(0, ==, keypin_open_journal(fname)); + + tt_int_op(KEYPIN_ADDED, ==, ADD("king-of-the-herrings", + "good-for-nothing attorney-at-law")); + tt_int_op(KEYPIN_ADDED, ==, ADD("yellowish-red-yellow", + "salt-and-pepper high-muck-a-muck")); + tt_int_op(KEYPIN_FOUND, ==, ADD("yellowish-red-yellow", + "salt-and-pepper high-muck-a-muck")); + keypin_close_journal(); + keypin_clear(); + + tt_int_op(0, ==, keypin_load_journal(fname)); + update_approx_time(1231041600); + tt_int_op(0, ==, keypin_open_journal(fname)); + tt_int_op(KEYPIN_FOUND, ==, ADD("yellowish-red-yellow", + "salt-and-pepper high-muck-a-muck")); + tt_int_op(KEYPIN_ADDED, ==, ADD("theatre-in-the-round", + "holier-than-thou jack-in-the-box")); + tt_int_op(KEYPIN_ADDED, ==, ADD("no-deposit-no-return", + "across-the-board will-o-the-wisp")); + tt_int_op(KEYPIN_MISMATCH, ==, ADD("intellectualizations", + "salt-and-pepper high-muck-a-muck")); + keypin_close_journal(); + keypin_clear(); + + tt_int_op(0, ==, keypin_load_journal(fname)); + update_approx_time(1412278354); + tt_int_op(0, ==, keypin_open_journal(fname)); + tt_int_op(KEYPIN_FOUND, ==, ADD("yellowish-red-yellow", + "salt-and-pepper high-muck-a-muck")); + tt_int_op(KEYPIN_MISMATCH, ==, ADD("intellectualizations", + "salt-and-pepper high-muck-a-muck")); + tt_int_op(KEYPIN_FOUND, ==, ADD("theatre-in-the-round", + "holier-than-thou jack-in-the-box")); + tt_int_op(KEYPIN_MISMATCH, ==, ADD("counterrevolutionary", + "holier-than-thou jack-in-the-box")); + tt_int_op(KEYPIN_MISMATCH, ==, ADD("no-deposit-no-return", + "floccinaucinihilipilificationism")); + keypin_close_journal(); + + contents = read_file_to_str(fname, RFTS_BIN, NULL); + tt_assert(contents); + tt_str_op(contents,==, + "\n" + "@opened-at 2008-08-02 20:30:00\n" + "a2luZy1vZi10aGUtaGVycmluZ3M Z29vZC1mb3Itbm90aGluZyBhdHRvcm5leS1hdC1sYXc\n" + "eWVsbG93aXNoLXJlZC15ZWxsb3c c2FsdC1hbmQtcGVwcGVyIGhpZ2gtbXVjay1hLW11Y2s\n" + "\n" + "@opened-at 2009-01-04 04:00:00\n" + "dGhlYXRyZS1pbi10aGUtcm91bmQ aG9saWVyLXRoYW4tdGhvdSBqYWNrLWluLXRoZS1ib3g\n" + "bm8tZGVwb3NpdC1uby1yZXR1cm4 YWNyb3NzLXRoZS1ib2FyZCB3aWxsLW8tdGhlLXdpc3A\n" + "\n" + "@opened-at 2014-10-02 19:32:34\n"); + + done: + tor_free(contents); + keypin_clear(); +} + +#undef ADD +#undef LONE_RSA + +#define TEST(name, flags) \ + { #name , test_keypin_ ## name, (flags), NULL, NULL } + +struct testcase_t keypin_tests[] = { + TEST( parse_line, 0 ), + TEST( parse_file, TT_FORK ), + TEST( add_entry, TT_FORK ), + TEST( journal, TT_FORK ), + END_OF_TESTCASES +}; + |